From 25a4ae6a3cc02f4679f437559d33d02a1a17e6ba Mon Sep 17 00:00:00 2001 From: Yanbing Wang Date: Tue, 5 Sep 2017 20:43:24 -0700 Subject: [PATCH] Source snapshot from Powershell/openssh-portable:latestw_all --- .skipped-commit-ids | 2 + Makefile.in | 4 +- README | 8 +- appveyor.yml | 2 +- auth-pam.c | 26 + auth.c | 99 +--- auth.h | 6 +- auth2-pubkey.c | 308 +----------- authfd.c | 3 +- authfile.c | 3 +- clientloop.c | 8 +- compat.c | 11 +- configure.ac | 1 + contrib/win32/openssh/OpenSSHUtils.psm1 | 4 +- contrib/win32/openssh/libssh.vcxproj | 4 +- contrib/win32/openssh/libssh.vcxproj.filters | 49 +- contrib/win32/openssh/version.rc | Bin 4066 -> 4066 bytes contrib/win32/win32compat/inc/grp.h | 12 +- contrib/win32/win32compat/inc/signal.h | 4 + contrib/win32/win32compat/misc.c | 255 +++++++++- contrib/win32/win32compat/misc_internal.h | 5 +- contrib/win32/win32compat/pwd.c | 2 + contrib/win32/win32compat/shell-host.c | 3 +- contrib/win32/win32compat/signal.c | 7 +- .../win32compat/ssh-agent/agent-request.h | 2 +- contrib/win32/win32compat/tncon.c | 5 +- groupaccess.c | 83 +-- misc.c | 474 +++++++++++++++++- misc.h | 22 +- openbsd-compat/bsd-err.c | 6 + openbsd-compat/explicit_bzero.c | 4 + openbsd-compat/port-tun.c | 85 ++-- packet.c | 4 +- platform-misc.c | 35 ++ platform.c | 16 - regress/pesterTests/KeyUtils.Tests.ps1 | 17 +- regress/pesterTests/SSHDConfig.tests.ps1 | 367 ++++++++++---- regress/pesterTests/testdata/SSHD_Config | 119 +++++ servconf.c | 26 + serverloop.c | 18 +- session.c | 87 +--- session.h | 4 +- sftp-client.c | 4 +- ssh-add.c | 3 +- ssh-agent.c | 315 +++++++----- ssh-keygen.1 | 9 +- ssh-keygen.c | 111 ++-- ssh-rsa.c | 37 +- ssh.c | 11 +- ssh_config.5 | 11 +- sshconnect.c | 3 +- sshconnect2.c | 7 +- sshd.c | 3 +- sshd_config.5 | 10 +- sshkey.c | 14 +- sshkey.h | 5 +- 56 files changed, 1764 insertions(+), 979 deletions(-) create mode 100644 platform-misc.c create mode 100644 regress/pesterTests/testdata/SSHD_Config diff --git a/.skipped-commit-ids b/.skipped-commit-ids index 9febbfb..7c03c9d 100644 --- a/.skipped-commit-ids +++ b/.skipped-commit-ids @@ -19,3 +19,5 @@ fe5b31f69a60d47171836911f144acff77810217 Makefile.inc bits ea80f445e819719ccdcb237022cacfac990fdc5c Makefile.inc warning flags b92c93266d8234d493857bb822260dacf4366157 moduli-gen.sh tweak b25bf747544265b39af74fe0716dc8d9f5b63b95 Updated moduli +1bd41cba06a7752de4df304305a8153ebfb6b0ac rsa.[ch] already removed +e39b3902fe1d6c4a7ba6a3c58e072219f3c1e604 Makefile changes diff --git a/Makefile.in b/Makefile.in index 29d539a..c52ce19 100644 --- a/Makefile.in +++ b/Makefile.in @@ -81,7 +81,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ cipher-ctr.o cleanup.o \ compat.o crc32.o fatal.o hostfile.o \ log.o match.o moduli.o nchan.o packet.o opacket.o \ - readpass.o rsa.o ttymodes.o xmalloc.o addrmatch.o \ + readpass.o ttymodes.o xmalloc.o addrmatch.o \ atomicio.o key.o dispatch.o mac.o uidswap.o uuencode.o misc.o utf8.o \ monitor_fdpass.o rijndael.o ssh-dss.o ssh-ecdsa.o ssh-rsa.o dh.o \ msg.o progressmeter.o dns.o entropy.o gss-genr.o umac.o umac128.o \ @@ -92,7 +92,7 @@ LIBSSH_OBJS=${LIBOPENSSH_OBJS} \ kex.o kexdh.o kexgex.o kexecdh.o kexc25519.o \ kexdhc.o kexgexc.o kexecdhc.o kexc25519c.o \ kexdhs.o kexgexs.o kexecdhs.o kexc25519s.o \ - platform-pledge.o platform-tracing.o + platform-pledge.o platform-tracing.o platform-misc.o SSHOBJS= ssh.o readconf.o clientloop.o sshtty.o \ sshconnect.o sshconnect2.o mux.o diff --git a/README b/README index bda8525..fb59f1b 100644 --- a/README +++ b/README @@ -30,7 +30,8 @@ The PAM support is now more functional than the popular packages of commercial ssh-1.2.x. It checks "account" and "session" modules for all logins, not just when using password authentication. -OpenSSH depends on Zlib[3], OpenSSL[4] and optionally PAM[5]. +OpenSSH depends on Zlib[3], OpenSSL[4], and optionally PAM[5] and +libedit[6] There is now several mailing lists for this port of OpenSSH. Please refer to https://www.openssh.com/list.html for details on how to join. @@ -38,7 +39,7 @@ refer to https://www.openssh.com/list.html for details on how to join. Please send bug reports and patches to the mailing list openssh-unix-dev@mindrot.org. The list is open to posting by unsubscribed users. Code contribution are welcomed, but please follow the OpenBSD -style guidelines[6]. +style guidelines[7]. Please refer to the INSTALL document for information on how to install OpenSSH on your system. @@ -61,4 +62,5 @@ References - [5] http://www.openpam.org http://www.kernel.org/pub/linux/libs/pam/ (PAM also is standard on Solaris and HP-UX 11) -[6] http://man.openbsd.org/style.9 +[6] http://thrysoee.dk/editline/ (portable version) +[7] http://man.openbsd.org/style.9 diff --git a/appveyor.yml b/appveyor.yml index b5f33f8..fec95f3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 0.0.19.0.{build} +version: 0.0.20.0.{build} image: Visual Studio 2015 branches: diff --git a/auth-pam.c b/auth-pam.c index 9574d9a..de29c04 100644 --- a/auth-pam.c +++ b/auth-pam.c @@ -926,6 +926,27 @@ finish_pam(void) sshpam_cleanup(); } +static void +expose_authinfo(const char *caller) +{ + char *auth_info; + + /* + * Expose authentication information to PAM. + * The enviornment variable is versioned. Please increment the + * version suffix if the format of session_info changes. + */ + if (sshpam_authctxt->session_info == NULL) + auth_info = xstrdup(""); + else if ((auth_info = sshbuf_dup_string( + sshpam_authctxt->session_info)) == NULL) + fatal("%s: sshbuf_dup_string failed", __func__); + + debug2("%s: auth information in SSH_AUTH_INFO_0", caller); + do_pam_putenv("SSH_AUTH_INFO_0", auth_info); + free(auth_info); +} + u_int do_pam_account(void) { @@ -933,6 +954,8 @@ do_pam_account(void) if (sshpam_account_status != -1) return (sshpam_account_status); + expose_authinfo(__func__); + sshpam_err = pam_acct_mgmt(sshpam_handle, 0); debug3("PAM: %s pam_acct_mgmt = %d (%s)", __func__, sshpam_err, pam_strerror(sshpam_handle, sshpam_err)); @@ -1057,6 +1080,9 @@ void do_pam_session(void) { debug3("PAM: opening session"); + + expose_authinfo(__func__); + sshpam_err = pam_set_item(sshpam_handle, PAM_CONV, (const void *)&store_conv); if (sshpam_err != PAM_SUCCESS) diff --git a/auth.c b/auth.c index 07c3d8e..81b1eb6 100644 --- a/auth.c +++ b/auth.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.c,v 1.122 2017/06/24 06:34:38 djm Exp $ */ +/* $OpenBSD: auth.c,v 1.123 2017/08/18 05:36:45 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -43,9 +43,6 @@ #ifdef USE_SHADOW #include #endif -#ifdef HAVE_LIBGEN_H -#include -#endif #include #include #include @@ -508,98 +505,6 @@ check_key_in_hostfiles(struct passwd *pw, struct sshkey *key, const char *host, return host_status; } -/* - * Check a given path for security. This is defined as all components - * of the path to the file must be owned by either the owner of - * of the file or root and no directories must be group or world writable. - * - * XXX Should any specific check be done for sym links ? - * - * Takes a file name, its stat information (preferably from fstat() to - * avoid races), the uid of the expected owner, their home directory and an - * error buffer plus max size as arguments. - * - * Returns 0 on success and -1 on failure - */ -int -auth_secure_path(const char *name, struct stat *stp, const char *pw_dir, - uid_t uid, char *err, size_t errlen) -{ - char buf[PATH_MAX], homedir[PATH_MAX]; - char *cp; - int comparehome = 0; - struct stat st; - - if (realpath(name, buf) == NULL) { - snprintf(err, errlen, "realpath %s failed: %s", name, - strerror(errno)); - return -1; - } - if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL) - comparehome = 1; - - if (!S_ISREG(stp->st_mode)) { - snprintf(err, errlen, "%s is not a regular file", buf); - return -1; - } - if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) || - (stp->st_mode & 022) != 0) { - snprintf(err, errlen, "bad ownership or modes for file %s", - buf); - return -1; - } - - /* for each component of the canonical path, walking upwards */ - for (;;) { - if ((cp = dirname(buf)) == NULL) { - snprintf(err, errlen, "dirname() failed"); - return -1; - } - strlcpy(buf, cp, sizeof(buf)); - - if (stat(buf, &st) < 0 || - (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) || - (st.st_mode & 022) != 0) { - snprintf(err, errlen, - "bad ownership or modes for directory %s", buf); - return -1; - } - - /* If are past the homedir then we can stop */ - if (comparehome && strcmp(homedir, buf) == 0) - break; - - /* - * dirname should always complete with a "/" path, - * but we can be paranoid and check for "." too - */ - if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) - break; - } - return 0; -} - -/* - * Version of secure_path() that accepts an open file descriptor to - * avoid races. - * - * Returns 0 on success and -1 on failure - */ -static int -secure_filename(FILE *f, const char *file, struct passwd *pw, - char *err, size_t errlen) -{ - struct stat st; - - /* check the open file to avoid races */ - if (fstat(fileno(f), &st) < 0) { - snprintf(err, errlen, "cannot stat file %s: %s", - file, strerror(errno)); - return -1; - } - return auth_secure_path(file, &st, pw->pw_dir, pw->pw_uid, err, errlen); -} - static FILE * auth_openfile(const char *file, struct passwd *pw, int strict_modes, int log_missing, char *file_type) @@ -646,7 +551,7 @@ auth_openfile(const char *file, struct passwd *pw, int strict_modes, return NULL; } if (strict_modes && - secure_filename(f, file, pw, line, sizeof(line)) != 0) { + safe_path_fd(fileno(f), file, pw, line, sizeof(line)) != 0) { fclose(f); logit("Authentication refused: %s", line); auth_debug_add("Ignored %s: %s", file_type, line); diff --git a/auth.h b/auth.h index 805318e..9aedbc1 100644 --- a/auth.h +++ b/auth.h @@ -1,4 +1,4 @@ -/* $OpenBSD: auth.h,v 1.92 2017/06/24 06:34:38 djm Exp $ */ +/* $OpenBSD: auth.h,v 1.93 2017/08/18 05:36:45 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. @@ -149,10 +149,6 @@ void auth2_record_info(Authctxt *authctxt, const char *, ...) __attribute__((__nonnull__ (2))); void auth2_update_session_info(Authctxt *, const char *, const char *); -struct stat; -int auth_secure_path(const char *, struct stat *, const char *, uid_t, - char *, size_t); - #ifdef KRB5 int auth_krb5(Authctxt *authctxt, krb5_data *auth, char **client, krb5_data *); int auth_krb5_tgt(Authctxt *authctxt, krb5_data *tgt); diff --git a/auth2-pubkey.c b/auth2-pubkey.c index 2fc318b..42d8e71 100644 --- a/auth2-pubkey.c +++ b/auth2-pubkey.c @@ -1,4 +1,4 @@ -/* $OpenBSD: auth2-pubkey.c,v 1.68 2017/06/24 06:34:38 djm Exp $ */ +/* $OpenBSD: auth2-pubkey.c,v 1.70 2017/08/18 05:48:04 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * @@ -27,7 +27,6 @@ #include #include -#include #include #include @@ -249,293 +248,6 @@ done: return authenticated; } -/* - * Splits 's' into an argument vector. Handles quoted string and basic - * escape characters (\\, \", \'). Caller must free the argument vector - * and its members. - */ -static int -split_argv(const char *s, int *argcp, char ***argvp) -{ - int r = SSH_ERR_INTERNAL_ERROR; - int argc = 0, quote, i, j; - char *arg, **argv = xcalloc(1, sizeof(*argv)); - - *argvp = NULL; - *argcp = 0; - - for (i = 0; s[i] != '\0'; i++) { - /* Skip leading whitespace */ - if (s[i] == ' ' || s[i] == '\t') - continue; - - /* Start of a token */ - quote = 0; - if (s[i] == '\\' && - (s[i + 1] == '\'' || s[i + 1] == '\"' || s[i + 1] == '\\')) - i++; - else if (s[i] == '\'' || s[i] == '"') - quote = s[i++]; - - argv = xreallocarray(argv, (argc + 2), sizeof(*argv)); - arg = argv[argc++] = xcalloc(1, strlen(s + i) + 1); - argv[argc] = NULL; - - /* Copy the token in, removing escapes */ - for (j = 0; s[i] != '\0'; i++) { - if (s[i] == '\\') { - if (s[i + 1] == '\'' || - s[i + 1] == '\"' || - s[i + 1] == '\\') { - i++; /* Skip '\' */ - arg[j++] = s[i]; - } else { - /* Unrecognised escape */ - arg[j++] = s[i]; - } - } else if (quote == 0 && (s[i] == ' ' || s[i] == '\t')) - break; /* done */ - else if (quote != 0 && s[i] == quote) - break; /* done */ - else - arg[j++] = s[i]; - } - if (s[i] == '\0') { - if (quote != 0) { - /* Ran out of string looking for close quote */ - r = SSH_ERR_INVALID_FORMAT; - goto out; - } - break; - } - } - /* Success */ - *argcp = argc; - *argvp = argv; - argc = 0; - argv = NULL; - r = 0; - out: - if (argc != 0 && argv != NULL) { - for (i = 0; i < argc; i++) - free(argv[i]); - free(argv); - } - return r; -} - -/* - * Reassemble an argument vector into a string, quoting and escaping as - * necessary. Caller must free returned string. - */ -static char * -assemble_argv(int argc, char **argv) -{ - int i, j, ws, r; - char c, *ret; - struct sshbuf *buf, *arg; - - if ((buf = sshbuf_new()) == NULL || (arg = sshbuf_new()) == NULL) - fatal("%s: sshbuf_new failed", __func__); - - for (i = 0; i < argc; i++) { - ws = 0; - sshbuf_reset(arg); - for (j = 0; argv[i][j] != '\0'; j++) { - r = 0; - c = argv[i][j]; - switch (c) { - case ' ': - case '\t': - ws = 1; - r = sshbuf_put_u8(arg, c); - break; - case '\\': - case '\'': - case '"': - if ((r = sshbuf_put_u8(arg, '\\')) != 0) - break; - /* FALLTHROUGH */ - default: - r = sshbuf_put_u8(arg, c); - break; - } - if (r != 0) - fatal("%s: sshbuf_put_u8: %s", - __func__, ssh_err(r)); - } - if ((i != 0 && (r = sshbuf_put_u8(buf, ' ')) != 0) || - (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0) || - (r = sshbuf_putb(buf, arg)) != 0 || - (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0)) - fatal("%s: buffer error: %s", __func__, ssh_err(r)); - } - if ((ret = malloc(sshbuf_len(buf) + 1)) == NULL) - fatal("%s: malloc failed", __func__); - memcpy(ret, sshbuf_ptr(buf), sshbuf_len(buf)); - ret[sshbuf_len(buf)] = '\0'; - sshbuf_free(buf); - sshbuf_free(arg); - return ret; -} - -/* - * Runs command in a subprocess. Returns pid on success and a FILE* to the - * subprocess' stdout or 0 on failure. - * NB. "command" is only used for logging. - */ -static pid_t -subprocess(const char *tag, struct passwd *pw, const char *command, - int ac, char **av, FILE **child) -{ -#ifdef WINDOWS - logit("AuthorizedPrincipalsCommand and AuthorizedKeysCommand are not supported in Windows yet"); - return 0; -#else /* !WINDOWS */ - FILE *f; - struct stat st; - int devnull, p[2], i; - pid_t pid; - char *cp, errmsg[512]; - u_int envsize; - char **child_env; - - *child = NULL; - - debug3("%s: %s command \"%s\" running as %s", __func__, - tag, command, pw->pw_name); - - /* Verify the path exists and is safe-ish to execute */ - if (*av[0] != '/') { - error("%s path is not absolute", tag); - return 0; - } - temporarily_use_uid(pw); - if (stat(av[0], &st) < 0) { - error("Could not stat %s \"%s\": %s", tag, - av[0], strerror(errno)); - restore_uid(); - return 0; - } - if (auth_secure_path(av[0], &st, NULL, 0, - errmsg, sizeof(errmsg)) != 0) { - error("Unsafe %s \"%s\": %s", tag, av[0], errmsg); - restore_uid(); - return 0; - } - - /* - * Run the command; stderr is left in place, stdout is the - * authorized_keys output. - */ - if (pipe(p) != 0) { - error("%s: pipe: %s", tag, strerror(errno)); - restore_uid(); - return 0; - } - - /* - * Don't want to call this in the child, where it can fatal() and - * run cleanup_exit() code. - */ - restore_uid(); - - switch ((pid = fork())) { - case -1: /* error */ - error("%s: fork: %s", tag, strerror(errno)); - close(p[0]); - close(p[1]); - return 0; - case 0: /* child */ - /* Prepare a minimal environment for the child. */ - envsize = 5; - child_env = xcalloc(sizeof(*child_env), envsize); - child_set_env(&child_env, &envsize, "PATH", _PATH_STDPATH); - child_set_env(&child_env, &envsize, "USER", pw->pw_name); - child_set_env(&child_env, &envsize, "LOGNAME", pw->pw_name); - child_set_env(&child_env, &envsize, "HOME", pw->pw_dir); - if ((cp = getenv("LANG")) != NULL) - child_set_env(&child_env, &envsize, "LANG", cp); - - for (i = 0; i < NSIG; i++) - signal(i, SIG_DFL); - - if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { - error("%s: open %s: %s", tag, _PATH_DEVNULL, - strerror(errno)); - _exit(1); - } - /* Keep stderr around a while longer to catch errors */ - if (dup2(devnull, STDIN_FILENO) == -1 || - dup2(p[1], STDOUT_FILENO) == -1) { - error("%s: dup2: %s", tag, strerror(errno)); - _exit(1); - } - closefrom(STDERR_FILENO + 1); - - /* Don't use permanently_set_uid() here to avoid fatal() */ - if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) { - error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid, - strerror(errno)); - _exit(1); - } - if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) { - error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid, - strerror(errno)); - _exit(1); - } - /* stdin is pointed to /dev/null at this point */ - if (dup2(STDIN_FILENO, STDERR_FILENO) == -1) { - error("%s: dup2: %s", tag, strerror(errno)); - _exit(1); - } - - execve(av[0], av, child_env); - error("%s exec \"%s\": %s", tag, command, strerror(errno)); - _exit(127); - default: /* parent */ - break; - } - - close(p[1]); - if ((f = fdopen(p[0], "r")) == NULL) { - error("%s: fdopen: %s", tag, strerror(errno)); - close(p[0]); - /* Don't leave zombie child */ - kill(pid, SIGTERM); - while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) - ; - return 0; - } - /* Success */ - debug3("%s: %s pid %ld", __func__, tag, (long)pid); - *child = f; - return pid; -#endif /* !WINDOWS */ -} - -/* Returns 0 if pid exited cleanly, non-zero otherwise */ -static int -exited_cleanly(pid_t pid, const char *tag, const char *cmd) -{ - int status; - - while (waitpid(pid, &status, 0) == -1) { - if (errno != EINTR) { - error("%s: waitpid: %s", tag, strerror(errno)); - return -1; - } - } - if (WIFSIGNALED(status)) { - error("%s %s exited on signal %d", tag, cmd, WTERMSIG(status)); - return -1; - } else if (WEXITSTATUS(status) != 0) { - error("%s %s failed, status %d", tag, cmd, WEXITSTATUS(status)); - return -1; - } - return 0; -} - static int match_principals_option(const char *principal_list, struct sshkey_cert *cert) { @@ -668,7 +380,7 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key) } /* Turn the command into an argument vector */ - if (split_argv(options.authorized_principals_command, &ac, &av) != 0) { + if (argv_split(options.authorized_principals_command, &ac, &av) != 0) { error("AuthorizedPrincipalsCommand \"%s\" contains " "invalid quotes", command); goto out; @@ -717,10 +429,11 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key) av[i] = tmp; } /* Prepare a printable command for logs, etc. */ - command = assemble_argv(ac, av); + command = argv_assemble(ac, av); if ((pid = subprocess("AuthorizedPrincipalsCommand", pw, command, - ac, av, &f)) == 0) + ac, av, &f, + SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD)) == 0) goto out; uid_swapped = 1; @@ -731,7 +444,7 @@ match_principals_command(struct passwd *user_pw, const struct sshkey *key) fclose(f); f = NULL; - if (exited_cleanly(pid, "AuthorizedPrincipalsCommand", command) != 0) + if (exited_cleanly(pid, "AuthorizedPrincipalsCommand", command, 0) != 0) goto out; /* Read completed successfully */ @@ -1008,7 +721,7 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key) } /* Turn the command into an argument vector */ - if (split_argv(options.authorized_keys_command, &ac, &av) != 0) { + if (argv_split(options.authorized_keys_command, &ac, &av) != 0) { error("AuthorizedKeysCommand \"%s\" contains invalid quotes", command); goto out; @@ -1032,7 +745,7 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key) av[i] = tmp; } /* Prepare a printable command for logs, etc. */ - command = assemble_argv(ac, av); + command = argv_assemble(ac, av); /* * If AuthorizedKeysCommand was run without arguments @@ -1049,7 +762,8 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key) } if ((pid = subprocess("AuthorizedKeysCommand", pw, command, - ac, av, &f)) == 0) + ac, av, &f, + SSH_SUBPROCESS_STDOUT_CAPTURE|SSH_SUBPROCESS_STDERR_DISCARD)) == 0) goto out; uid_swapped = 1; @@ -1060,7 +774,7 @@ user_key_command_allowed2(struct passwd *user_pw, struct sshkey *key) fclose(f); f = NULL; - if (exited_cleanly(pid, "AuthorizedKeysCommand", command) != 0) + if (exited_cleanly(pid, "AuthorizedKeysCommand", command, 0) != 0) goto out; /* Read completed successfully */ diff --git a/authfd.c b/authfd.c index 2e94140..c025a29 100644 --- a/authfd.c +++ b/authfd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: authfd.c,v 1.104 2017/06/28 01:09:22 djm Exp $ */ +/* $OpenBSD: authfd.c,v 1.105 2017/07/01 13:50:45 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -51,7 +51,6 @@ #include "xmalloc.h" #include "ssh.h" -#include "rsa.h" #include "sshbuf.h" #include "sshkey.h" #include "authfd.h" diff --git a/authfile.c b/authfile.c index 2f54757..1b6484b 100644 --- a/authfile.c +++ b/authfile.c @@ -1,4 +1,4 @@ -/* $OpenBSD: authfile.c,v 1.126 2017/05/31 09:15:42 deraadt Exp $ */ +/* $OpenBSD: authfile.c,v 1.127 2017/07/01 13:50:45 djm Exp $ */ /* * Copyright (c) 2000, 2013 Markus Friedl. All rights reserved. * @@ -42,7 +42,6 @@ #include "ssh.h" #include "log.h" #include "authfile.h" -#include "rsa.h" #include "misc.h" #include "atomicio.h" #include "sshkey.h" diff --git a/clientloop.c b/clientloop.c index c623131..b4a58b0 100644 --- a/clientloop.c +++ b/clientloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: clientloop.c,v 1.300 2017/06/23 07:24:48 mestre Exp $ */ +/* $OpenBSD: clientloop.c,v 1.301 2017/07/14 03:18:21 dtucker Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1422,8 +1422,10 @@ client_loop(int have_pty, int escape_char_arg, int ssh2_chan_id) exit_status = 0; } - if (received_signal) - fatal("Killed by signal %d.", (int) received_signal); + if (received_signal) { + verbose("Killed by signal %d.", (int) received_signal); + cleanup_exit(0); + } /* * In interactive mode (with pseudo tty) display a message indicating diff --git a/compat.c b/compat.c index 156a5ea..d82135e 100644 --- a/compat.c +++ b/compat.c @@ -1,4 +1,4 @@ -/* $OpenBSD: compat.c,v 1.103 2017/04/30 23:13:25 djm Exp $ */ +/* $OpenBSD: compat.c,v 1.104 2017/07/25 09:22:25 dtucker Exp $ */ /* * Copyright (c) 1999, 2000, 2001, 2002 Markus Friedl. All rights reserved. * @@ -177,9 +177,12 @@ compat_datafellows(const char *version) "TTSSH/2.72*", SSH_BUG_HOSTKEYS }, { "WinSCP_release_4*," "WinSCP_release_5.0*," - "WinSCP_release_5.1*," - "WinSCP_release_5.5*," - "WinSCP_release_5.6*," + "WinSCP_release_5.1," + "WinSCP_release_5.1.*," + "WinSCP_release_5.5," + "WinSCP_release_5.5.*," + "WinSCP_release_5.6," + "WinSCP_release_5.6.*," "WinSCP_release_5.7," "WinSCP_release_5.7.1," "WinSCP_release_5.7.2," diff --git a/configure.ac b/configure.ac index 18079ac..49c5caa 100644 --- a/configure.ac +++ b/configure.ac @@ -980,6 +980,7 @@ mips-sony-bsd|mips-sony-newsos4) AC_DEFINE([BROKEN_SETREUID]) AC_DEFINE([BROKEN_SETREGID]) AC_DEFINE([PASSWD_NEEDS_USERNAME]) + AC_DEFINE([BROKEN_TCGETATTR_ICANON]) TEST_SHELL=$SHELL # let configure find us a capable shell case "$host" in *-*-sysv5SCO_SV*) # SCO OpenServer 6.x diff --git a/contrib/win32/openssh/OpenSSHUtils.psm1 b/contrib/win32/openssh/OpenSSHUtils.psm1 index a68fd85..a2a52d1 100644 --- a/contrib/win32/openssh/OpenSSHUtils.psm1 +++ b/contrib/win32/openssh/OpenSSHUtils.psm1 @@ -168,7 +168,9 @@ function Repair-AuthorizedKeyPermission $userProfilePath = $properties.ProfileImagePath } $userProfilePath = $userProfilePath.Replace("\", "\\") - $fullPath -match "^$userProfilePath[\\|\W|\w]+authorized_keys$" + if ( $properties.PSChildName -notmatch '\.bak$') { + $fullPath -match "^$userProfilePath\\[\\|\W|\w]+authorized_keys$" + } } if($profileItem) { diff --git a/contrib/win32/openssh/libssh.vcxproj b/contrib/win32/openssh/libssh.vcxproj index 795c49b..98c5e75 100644 --- a/contrib/win32/openssh/libssh.vcxproj +++ b/contrib/win32/openssh/libssh.vcxproj @@ -248,9 +248,6 @@ - - true - @@ -275,6 +272,7 @@ + diff --git a/contrib/win32/openssh/libssh.vcxproj.filters b/contrib/win32/openssh/libssh.vcxproj.filters index 6174812..45f43dd 100644 --- a/contrib/win32/openssh/libssh.vcxproj.filters +++ b/contrib/win32/openssh/libssh.vcxproj.filters @@ -14,17 +14,14 @@ - - - @@ -60,7 +57,6 @@ - @@ -83,7 +79,15 @@ - + + + + + + + + + @@ -245,15 +249,6 @@ Source Files - - Source Files - - - Source Files - - - Source Files - Source Files @@ -272,15 +267,6 @@ Source Files - - Source Files - - - Source Files - - - Source Files - Source Files @@ -329,15 +315,6 @@ Source Files - - Source Files - - - Source Files - - - Source Files - Source Files @@ -361,11 +338,7 @@ - - Header Files - - - Header Files - + + \ No newline at end of file diff --git a/contrib/win32/openssh/version.rc b/contrib/win32/openssh/version.rc index bfe76721a19e60801bfd8adb10cc7b23bae95fc1..240081ef379cbb06845ca088db6b06c5dc2ce965 100644 GIT binary patch delta 52 wcmaDP|44qrBo1aH27}2{ISfJc=93(fOsqf#gWlwPF3CyUn-HAM7r2F30D|BRegFUf delta 52 wcmaDP|44qrBo1an2FuA)ISfJc=93(fOsqf#gWlwPF3CyUn-HAM7r2F30ES= +#include "sys/types.h" -char *group_from_gid(gid_t gid, int nogroup); +typedef enum { + LOCAL_GROUP = 0, + DOMAIN_GROUP = 1, + GLOBAL_UNIVERSAL_GROUP = 2 +} group_type; + +char ** getusergroups(const char *user, int *numgroups); +void populate_user_groups(char **group_name, int *group_index, DWORD groupsread, DWORD totalgroups, LPBYTE buf, group_type groupType); +void print_user_groups(const char *user, char **user_groups, int num_user_groups); #endif diff --git a/contrib/win32/win32compat/inc/signal.h b/contrib/win32/win32compat/inc/signal.h index 5eeba77..abaa5b7 100644 --- a/contrib/win32/win32compat/inc/signal.h +++ b/contrib/win32/win32compat/inc/signal.h @@ -91,4 +91,8 @@ int w32_sigprocmask(int how, const sigset_t *set, sigset_t *oldset); #define SIG_IGN W32_SIG_IGN #define SIG_ERR W32_SIG_ERR +/* TOTO - implement http://www.manpagez.com/man/3/sys_siglist/*/ +#undef NSIG +#define NSIG 0 + #endif \ No newline at end of file diff --git a/contrib/win32/win32compat/misc.c b/contrib/win32/win32compat/misc.c index 5b76466..488689a 100644 --- a/contrib/win32/win32compat/misc.c +++ b/contrib/win32/win32compat/misc.c @@ -33,6 +33,7 @@ #include #include #include +#include #include "inc\unistd.h" #include "inc\sys\stat.h" @@ -49,6 +50,7 @@ #include "debug.h" #include "w32fd.h" #include "inc\string.h" +#include "inc\grp.h" static char* s_programdir = NULL; @@ -1046,8 +1048,259 @@ readpassphrase(const char *prompt, char *outBuf, size_t outBufLen, int flags) return outBuf; } -void invalid_parameter_handler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) +void +invalid_parameter_handler(const wchar_t* expression, const wchar_t* function, const wchar_t* file, unsigned int line, uintptr_t pReserved) { debug3("Invalid parameter in function: %ls. File: %ls Line: %d.", function, file, line); debug3("Expression: %s", expression); } + +int +get_machine_domain_name(wchar_t *domain, int size) +{ + LPWKSTA_INFO_100 pBuf = NULL; + NET_API_STATUS nStatus; + LPWSTR pszServerName = NULL; + + nStatus = NetWkstaGetInfo(pszServerName, 100, (LPBYTE *)&pBuf); + if (nStatus != NERR_Success) { + error("Unable to fetch the machine domain, error:%d\n", nStatus); + return 0; + } + + debug3("Machine domain:%ls", pBuf->wki100_langroup); + wcscpy_s(domain, size, pBuf->wki100_langroup); + + if (pBuf != NULL) + NetApiBufferFree(pBuf); + + return 1; +} + +/* + * This method will fetch all the groups (listed below) even if the user is indirectly a member. + * - Local machine groups + * - Domain groups + * - global group + * - universal groups +*/ +char ** +getusergroups(const char *user, int *ngroups) +{ + LPGROUP_USERS_INFO_0 local_groups = NULL; + LPGROUP_USERS_INFO_0 domain_groups = NULL; + LPGROUP_USERS_INFO_0 global_universal_groups = NULL; + DWORD num_local_groups_read = 0; + DWORD total_local_groups = 0; + DWORD num_domain_groups_read = 0; + DWORD total_domain_groups = 0; + DWORD num_global_universal_groups_read = 0; + DWORD total_global_universal_groups = 0; + + DWORD flags = LG_INCLUDE_INDIRECT; + NET_API_STATUS nStatus; + wchar_t *user_name_utf16 = NULL; + char *user_domain = NULL; + LPWSTR dc_name_utf16 = NULL; + char **user_groups = NULL; + int num_user_groups = 0; + wchar_t machine_domain_name_utf16[DNLEN + 1] = { 0 }; + wchar_t local_user_fmt_utf16[UNLEN + DNLEN + 2] = { 0 }; + size_t local_user_fmt_len = UNLEN + DNLEN + 2; + char *user_name = NULL; + + user_name = malloc(strlen(user)+1); + if(!user_name) { + error("failed to allocate memory!"); + goto cleanup; + } + + memcpy(user_name, user, strlen(user)+1); + + if (user_domain = strchr(user_name, '@')) { + char *t = user_domain; + user_domain++; + *t='\0'; + } + + user_name_utf16 = utf8_to_utf16(user_name); + if (!user_name_utf16) { + error("utf8_to_utf16 failed! for %s", user_name); + goto cleanup; + } + + /* Fetch groups on the Local machine */ + if(get_machine_domain_name(machine_domain_name_utf16, DNLEN+1)) { + if (machine_domain_name_utf16) { + if(!machine_domain_name) + machine_domain_name = utf16_to_utf8(machine_domain_name_utf16); + + if (user_domain) { + wcscpy_s(local_user_fmt_utf16, local_user_fmt_len, machine_domain_name_utf16); + wcscat_s(local_user_fmt_utf16, local_user_fmt_len, L"\\"); + } + + wcscat_s(local_user_fmt_utf16, local_user_fmt_len, user_name_utf16); + nStatus = NetUserGetLocalGroups(NULL, + local_user_fmt_utf16, + 0, + flags, + (LPBYTE *)&local_groups, + MAX_PREFERRED_LENGTH, + &num_local_groups_read, + &total_local_groups); + + if (NERR_Success != nStatus) + error("Failed to get local groups on this machine, error: %d\n", nStatus); + } + } + + if (user_domain) { + /* Fetch Domain groups */ + nStatus = NetGetDCName(NULL, machine_domain_name_utf16, (LPBYTE *)&dc_name_utf16); + if (NERR_Success == nStatus) { + debug3("domain controller name: %ls", dc_name_utf16); + + nStatus = NetUserGetLocalGroups(dc_name_utf16, + user_name_utf16, + 0, + flags, + (LPBYTE *)&domain_groups, + MAX_PREFERRED_LENGTH, + &num_domain_groups_read, + &total_domain_groups); + + if (NERR_Success != nStatus) + error("Failed to get domain groups from DC:%s error: %d\n", dc_name_utf16, nStatus); + } + else + error("Failed to get the domain controller name, error: %d\n", nStatus); + + /* Fetch global, universal groups */ + nStatus = NetUserGetGroups(dc_name_utf16, + user_name_utf16, + 0, + (LPBYTE *)&global_universal_groups, + MAX_PREFERRED_LENGTH, + &num_global_universal_groups_read, + &total_global_universal_groups); + + if (NERR_Success != nStatus) + error("Failed to get global,universal groups from DC:%ls error: %d\n", dc_name_utf16, nStatus); + } + + int total_user_groups = num_local_groups_read + num_domain_groups_read + num_global_universal_groups_read; + + /* populate the output */ + user_groups = malloc(total_user_groups * sizeof(*user_groups)); + + populate_user_groups(user_groups, &num_user_groups, num_local_groups_read, total_local_groups, (LPBYTE) local_groups, LOCAL_GROUP); + if (user_domain) { + populate_user_groups(user_groups, &num_user_groups, num_domain_groups_read, total_domain_groups, (LPBYTE)domain_groups, DOMAIN_GROUP); + populate_user_groups(user_groups, &num_user_groups, num_global_universal_groups_read, total_global_universal_groups, (LPBYTE)global_universal_groups, GLOBAL_UNIVERSAL_GROUP); + } + + for (int i = 0; i < num_user_groups; i++) + to_lower_case(user_groups[i]); + + print_user_groups(user, user_groups, num_user_groups); + + cleanup: + if(local_groups) + NetApiBufferFree(local_groups); + + if(domain_groups) + NetApiBufferFree(domain_groups); + + if(global_universal_groups) + NetApiBufferFree(global_universal_groups); + + if(dc_name_utf16) + NetApiBufferFree(dc_name_utf16); + + if(user_name_utf16) + free(user_name_utf16); + + if(user_name) + free(user_name); + + *ngroups = num_user_groups; + return user_groups; +} + +/* This method will return in "group@domain" format */ +char * +append_domain_to_groupname(char *groupname) +{ + if(!groupname) return NULL; + + int len = (int) strlen(machine_domain_name) + (int) strlen(groupname) + 2; + char *groupname_with_domain = malloc(len); + if(!groupname_with_domain) { + error("failed to allocate memory!"); + return NULL; + } + + strcpy_s(groupname_with_domain, len, groupname); + strcat_s(groupname_with_domain, len, "@"); + strcat_s(groupname_with_domain, len, machine_domain_name); + + groupname_with_domain[len-1]= '\0'; + + return groupname_with_domain; +} + +void +populate_user_groups(char **group_name, int *group_index, DWORD groupsread, DWORD totalgroups, LPBYTE buf, group_type groupType) +{ + if(0 == groupsread) return; + char *user_group_name = NULL; + + if (groupType == GLOBAL_UNIVERSAL_GROUP) { + LPGROUP_USERS_INFO_0 pTmpBuf = (LPGROUP_USERS_INFO_0)buf; + for (DWORD i = 0; (i < groupsread) && pTmpBuf; i++, pTmpBuf++) { + if (!(user_group_name = utf16_to_utf8(pTmpBuf->grui0_name))) { + error("utf16_to_utf8 failed to convert:%ls", pTmpBuf->grui0_name); + return; + } + + group_name[*group_index] = append_domain_to_groupname(user_group_name); + if(group_name[*group_index]) + (*group_index)++; + } + } else { + LPLOCALGROUP_USERS_INFO_0 pTmpBuf = (LPLOCALGROUP_USERS_INFO_0)buf; + for (DWORD i = 0; (i < groupsread) && pTmpBuf; i++, pTmpBuf++) { + if (!(user_group_name = utf16_to_utf8(pTmpBuf->lgrui0_name))) { + error("utf16_to_utf8 failed to convert:%ls", pTmpBuf->lgrui0_name); + return; + } + + if(groupType == DOMAIN_GROUP) + group_name[*group_index] = append_domain_to_groupname(user_group_name); + else + group_name[*group_index] = user_group_name; + + if (group_name[*group_index]) + (*group_index)++; + } + } + + if (groupsread < totalgroups) + error("groupsread:%d totalgroups:%d groupType:%d", groupsread, totalgroups, groupType); +} + +void +print_user_groups(const char *user, char **user_groups, int num_user_groups) +{ + debug3("Group list for user:%s", user); + for(int i=0; i < num_user_groups; i++) + debug3("group name:%s", user_groups[i]); +} + +void +to_lower_case(char *s) +{ + for (; *s; s++) + *s = tolower((u_char)*s); +} diff --git a/contrib/win32/win32compat/misc_internal.h b/contrib/win32/win32compat/misc_internal.h index a416f21..a2141ac 100644 --- a/contrib/win32/win32compat/misc_internal.h +++ b/contrib/win32/win32compat/misc_internal.h @@ -31,4 +31,7 @@ int errno_from_Win32Error(int); void unix_time_to_file_time(ULONG, LPFILETIME); void file_time_to_unix_time(const LPFILETIME, time_t *); int file_attr_to_st_mode(wchar_t * path, DWORD attributes); -void invalid_parameter_handler(const wchar_t *, const wchar_t *, const wchar_t *, unsigned int, uintptr_t); \ No newline at end of file +void invalid_parameter_handler(const wchar_t *, const wchar_t *, const wchar_t *, unsigned int, uintptr_t); +static char *machine_domain_name; +void to_lower_case(char *s); +int get_machine_domain_name(wchar_t *domain, int size); \ No newline at end of file diff --git a/contrib/win32/win32compat/pwd.c b/contrib/win32/win32compat/pwd.c index e60096b..1647e5a 100644 --- a/contrib/win32/win32compat/pwd.c +++ b/contrib/win32/win32compat/pwd.c @@ -203,6 +203,8 @@ get_passwd(const char *user_utf8, LPWSTR user_sid) goto done; } } + + to_lower_case(uname_upn); pw.pw_name = uname_upn; uname_upn = NULL; pw.pw_dir = pw_home_utf8; diff --git a/contrib/win32/win32compat/shell-host.c b/contrib/win32/win32compat/shell-host.c index 621099e..ab9596b 100644 --- a/contrib/win32/win32compat/shell-host.c +++ b/contrib/win32/win32compat/shell-host.c @@ -164,7 +164,6 @@ struct key_translation keys[] = { { L"\x1bOQ", VK_F2, 0 , 0 , 0}, { L"\x1bOR", VK_F3, 0 , 0 , 0}, { L"\x1bOS", VK_F4, 0 , 0 , 0}, - { L"\x1b?", VK_OEM_2, L'?' , 0 , SHIFT_PRESSED | LEFT_ALT_PRESSED}, { L"\x1", VK_A, L'\x1' , 0 , LEFT_CTRL_PRESSED}, { L"\x2", VK_B, L'\x2' , 0 , LEFT_CTRL_PRESSED}, //{ L"\x3", VK_C, L'\x3' , 0 , LEFT_CTRL_PRESSED}, /* Control + C is handled differently */ @@ -311,7 +310,7 @@ SendKeyStrokeEx(HANDLE hInput, int vKey, wchar_t character, DWORD ctrlState, BOO ir.EventType = KEY_EVENT; ir.Event.KeyEvent.bKeyDown = keyDown; - ir.Event.KeyEvent.wRepeatCount = 0; + ir.Event.KeyEvent.wRepeatCount = 1; ir.Event.KeyEvent.wVirtualKeyCode = vKey; ir.Event.KeyEvent.wVirtualScanCode = MapVirtualKeyA(vKey, MAPVK_VK_TO_VSC); ir.Event.KeyEvent.dwControlKeyState = ctrlState; diff --git a/contrib/win32/win32compat/signal.c b/contrib/win32/win32compat/signal.c index 02c8011..17fd9b3 100644 --- a/contrib/win32/win32compat/signal.c +++ b/contrib/win32/win32compat/signal.c @@ -31,9 +31,10 @@ #include #include "w32fd.h" #include "signal_internal.h" -#include "inc\signal.h" #include "debug.h" +/* Apply caution while changing this order of inclusion of below 2 signal.h headers */ +#include "inc\signal.h" #undef signal #undef raise #undef SIGINT @@ -46,7 +47,11 @@ #undef SIG_DFL #undef SIG_IGN #undef SIG_ERR +#undef NSIG #include +#undef NSIG +#define NSIG 0 + /* pending signals to be processed */ sigset_t pending_signals; diff --git a/contrib/win32/win32compat/ssh-agent/agent-request.h b/contrib/win32/win32compat/ssh-agent/agent-request.h index 2ee6222..4bc92a7 100644 --- a/contrib/win32/win32compat/ssh-agent/agent-request.h +++ b/contrib/win32/win32compat/ssh-agent/agent-request.h @@ -3,9 +3,9 @@ typedef unsigned short u_int16_t; typedef unsigned int u_int32_t; typedef unsigned __int64 u_int64_t; #define __attribute__(a) -#include "rsa.h" #include "sshbuf.h" #include "sshkey.h" +#include #include "authfd.h" #include "digest.h" diff --git a/contrib/win32/win32compat/tncon.c b/contrib/win32/win32compat/tncon.c index b9c5bd9..3b3a396 100644 --- a/contrib/win32/win32compat/tncon.c +++ b/contrib/win32/win32compat/tncon.c @@ -155,7 +155,7 @@ ReadConsoleForTermEmul(HANDLE hInput, char *destin, int destinlen) switch (InputRecord.Event.KeyEvent.uChar.UnicodeChar) { case 0xd: if (pParams->nReceiveCRLF == ENUM_LF) - NetWriteString2(pParams->Socket, "\n", 1, 0); + NetWriteString2(pParams->Socket, "\r", 1, 0); else NetWriteString2(pParams->Socket, "\r\n", 2, 0); break; @@ -208,9 +208,6 @@ ReadConsoleForTermEmul(HANDLE hInput, char *destin, int destinlen) case VK_ESCAPE: NetWriteString2(pParams->Socket, (char *)ESCAPE_KEY, 1, 0); break; - case VK_OEM_2: - NetWriteString2(pParams->Socket, (char *)SHIFT_ALT_Q, 2, 0); - break; case VK_SHIFT: case VK_CONTROL: case VK_CAPITAL: diff --git a/groupaccess.c b/groupaccess.c index 1f6180c..0886c05 100644 --- a/groupaccess.c +++ b/groupaccess.c @@ -54,84 +54,13 @@ int ga_init(const char *user, gid_t base) { #ifdef WINDOWS -#pragma warning(push, 3) - LPLOCALGROUP_USERS_INFO_0 local_groups_info = NULL, tmp_groups_info; - wchar_t *user_utf16 = NULL, *full_name_utf16 = NULL, *udom_utf16 = NULL, *tmp; - char *group_utf8 = NULL; - DWORD i = 0, j = 0; - DWORD entries_read = 0, total_entries = 0, full_name_len = 0, index = 0; - NET_API_STATUS nStatus; - - if (ngroups > 0) - ga_free(); - - if ((user_utf16 = utf8_to_utf16(user)) == NULL) { - errno = ENOMEM; - goto done; - } - - full_name_len = (DWORD)wcslen(user_utf16) + 1; - if ((full_name_utf16 = malloc(full_name_len * sizeof(wchar_t))) == NULL) { - errno = ENOMEM; - goto done; - } - - if ((tmp = wcschr(user_utf16, L'@')) != NULL) { - udom_utf16 = tmp + 1; - *tmp = L'\0'; - index = (DWORD)wcslen(udom_utf16) + 1; - wmemcpy(full_name_utf16, udom_utf16, index); - full_name_utf16[wcslen(udom_utf16)] = L'\\'; - } - wmemcpy(full_name_utf16 + index, user_utf16, wcslen(user_utf16) + 1); - - nStatus = NetUserGetLocalGroups(NULL, - full_name_utf16, - 0, - LG_INCLUDE_INDIRECT, - (LPBYTE *)&local_groups_info, - MAX_PREFERRED_LENGTH, - &entries_read, - &total_entries); - - if (NERR_Success != nStatus) { - error("NetUserGetLocalGroups() failed with error: %u", - nStatus); - errno = ENOENT; - goto done; - } - - if (entries_read != total_entries) { - error("NetUserGetLocalGroups(): entries_read (%u) is not equal to " - "total_entries (%u) for user %.100s", entries_read, total_entries, user); - errno = ENOENT; - goto done; - } - - if ((tmp_groups_info = local_groups_info) != NULL) { - groups_byname = xcalloc(entries_read, sizeof(*groups_byname)); - for (i = 0, j = 0; i < total_entries; i++) - { - if ((group_utf8 = utf16_to_utf8(tmp_groups_info->lgrui0_name)) == NULL) { - errno = ENOMEM; - goto done; - } - groups_byname[j++] = group_utf8; - tmp_groups_info++; - } - } - -done: - if(user_utf16 != NULL) - free(user_utf16); - if(full_name_utf16 != NULL) - free(full_name_utf16); - if (local_groups_info != NULL) - NetApiBufferFree(local_groups_info); - -#pragma warning(pop) + ngroups = 0; + groups_byname = NULL; + groups_byname = getusergroups(user, &ngroups); + return ngroups; #else /* !WINDOWS */ + gid_t *groups_bygid; int i, j; struct group *gr; @@ -153,8 +82,8 @@ done: if ((gr = getgrgid(groups_bygid[i])) != NULL) groups_byname[j++] = xstrdup(gr->gr_name); free(groups_bygid); -#endif /* !WINDOWS */ return (ngroups = j); +#endif /* !WINDOWS */ } /* diff --git a/misc.c b/misc.c index 83da001..c407fcd 100644 --- a/misc.c +++ b/misc.c @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.c,v 1.110 2017/05/31 09:15:42 deraadt Exp $ */ +/* $OpenBSD: misc.c,v 1.113 2017/08/18 05:48:04 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2005,2006 Damien Miller. All rights reserved. @@ -29,10 +29,16 @@ #include #include #include +#include #include +#include #include #include +#ifdef HAVE_LIBGEN_H +# include +#endif +#include #include #include #include @@ -61,6 +67,10 @@ #include "misc.h" #include "log.h" #include "ssh.h" +#include "sshbuf.h" +#include "ssherr.h" +#include "uidswap.h" +#include "platform.h" /* remove newline at end of string */ char * @@ -1105,6 +1115,7 @@ static const struct { const char *name; int value; } ipqos[] = { + { "none", INT_MAX }, /* can't use 0 here; that's CS0 */ { "af11", IPTOS_DSCP_AF11 }, { "af12", IPTOS_DSCP_AF12 }, { "af13", IPTOS_DSCP_AF13 }, @@ -1303,3 +1314,464 @@ daemonized(void) return 1; } #endif /* !WINDOWS */ + +/* + * Splits 's' into an argument vector. Handles quoted string and basic + * escape characters (\\, \", \'). Caller must free the argument vector + * and its members. + */ +int +argv_split(const char *s, int *argcp, char ***argvp) +{ + int r = SSH_ERR_INTERNAL_ERROR; + int argc = 0, quote, i, j; + char *arg, **argv = xcalloc(1, sizeof(*argv)); + + *argvp = NULL; + *argcp = 0; + + for (i = 0; s[i] != '\0'; i++) { + /* Skip leading whitespace */ + if (s[i] == ' ' || s[i] == '\t') + continue; + + /* Start of a token */ + quote = 0; + if (s[i] == '\\' && + (s[i + 1] == '\'' || s[i + 1] == '\"' || s[i + 1] == '\\')) + i++; + else if (s[i] == '\'' || s[i] == '"') + quote = s[i++]; + + argv = xreallocarray(argv, (argc + 2), sizeof(*argv)); + arg = argv[argc++] = xcalloc(1, strlen(s + i) + 1); + argv[argc] = NULL; + + /* Copy the token in, removing escapes */ + for (j = 0; s[i] != '\0'; i++) { + if (s[i] == '\\') { + if (s[i + 1] == '\'' || + s[i + 1] == '\"' || + s[i + 1] == '\\') { + i++; /* Skip '\' */ + arg[j++] = s[i]; + } else { + /* Unrecognised escape */ + arg[j++] = s[i]; + } + } else if (quote == 0 && (s[i] == ' ' || s[i] == '\t')) + break; /* done */ + else if (quote != 0 && s[i] == quote) + break; /* done */ + else + arg[j++] = s[i]; + } + if (s[i] == '\0') { + if (quote != 0) { + /* Ran out of string looking for close quote */ + r = SSH_ERR_INVALID_FORMAT; + goto out; + } + break; + } + } + /* Success */ + *argcp = argc; + *argvp = argv; + argc = 0; + argv = NULL; + r = 0; + out: + if (argc != 0 && argv != NULL) { + for (i = 0; i < argc; i++) + free(argv[i]); + free(argv); + } + return r; +} + +/* + * Reassemble an argument vector into a string, quoting and escaping as + * necessary. Caller must free returned string. + */ +char * +argv_assemble(int argc, char **argv) +{ + int i, j, ws, r; + char c, *ret; + struct sshbuf *buf, *arg; + + if ((buf = sshbuf_new()) == NULL || (arg = sshbuf_new()) == NULL) + fatal("%s: sshbuf_new failed", __func__); + + for (i = 0; i < argc; i++) { + ws = 0; + sshbuf_reset(arg); + for (j = 0; argv[i][j] != '\0'; j++) { + r = 0; + c = argv[i][j]; + switch (c) { + case ' ': + case '\t': + ws = 1; + r = sshbuf_put_u8(arg, c); + break; + case '\\': + case '\'': + case '"': + if ((r = sshbuf_put_u8(arg, '\\')) != 0) + break; + /* FALLTHROUGH */ + default: + r = sshbuf_put_u8(arg, c); + break; + } + if (r != 0) + fatal("%s: sshbuf_put_u8: %s", + __func__, ssh_err(r)); + } + if ((i != 0 && (r = sshbuf_put_u8(buf, ' ')) != 0) || + (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0) || + (r = sshbuf_putb(buf, arg)) != 0 || + (ws != 0 && (r = sshbuf_put_u8(buf, '"')) != 0)) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + } + if ((ret = malloc(sshbuf_len(buf) + 1)) == NULL) + fatal("%s: malloc failed", __func__); + memcpy(ret, sshbuf_ptr(buf), sshbuf_len(buf)); + ret[sshbuf_len(buf)] = '\0'; + sshbuf_free(buf); + sshbuf_free(arg); + return ret; +} + +/* + * Runs command in a subprocess wuth a minimal environment. + * Returns pid on success, 0 on failure. + * The child stdout and stderr maybe captured, left attached or sent to + * /dev/null depending on the contents of flags. + * "tag" is prepended to log messages. + * NB. "command" is only used for logging; the actual command executed is + * av[0]. + */ +pid_t +subprocess(const char *tag, struct passwd *pw, const char *command, + int ac, char **av, FILE **child, u_int flags) +{ +#ifdef WINDOWS + error("subprocess is not implemented in Windows yet"); + return 0; +#else + FILE *f = NULL; + struct stat st; + int fd, devnull, p[2], i; + pid_t pid; + char *cp, errmsg[512]; + u_int envsize; + char **child_env; + + if (child != NULL) + *child = NULL; + + debug3("%s: %s command \"%s\" running as %s (flags 0x%x)", __func__, + tag, command, pw->pw_name, flags); + + /* Check consistency */ + if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 && + (flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) { + error("%s: inconsistent flags", __func__); + return 0; + } + if (((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) != (child == NULL)) { + error("%s: inconsistent flags/output", __func__); + return 0; + } + + /* + * If executing an explicit binary, then verify the it exists + * and appears safe-ish to execute + */ + if (*av[0] != '/') { + error("%s path is not absolute", tag); + return 0; + } + temporarily_use_uid(pw); + if (stat(av[0], &st) < 0) { + error("Could not stat %s \"%s\": %s", tag, + av[0], strerror(errno)); + restore_uid(); + return 0; + } + if (safe_path(av[0], &st, NULL, 0, errmsg, sizeof(errmsg)) != 0) { + error("Unsafe %s \"%s\": %s", tag, av[0], errmsg); + restore_uid(); + return 0; + } + /* Prepare to keep the child's stdout if requested */ + if (pipe(p) != 0) { + error("%s: pipe: %s", tag, strerror(errno)); + restore_uid(); + return 0; + } + restore_uid(); + + switch ((pid = fork())) { + case -1: /* error */ + error("%s: fork: %s", tag, strerror(errno)); + close(p[0]); + close(p[1]); + return 0; + case 0: /* child */ + /* Prepare a minimal environment for the child. */ + envsize = 5; + child_env = xcalloc(sizeof(*child_env), envsize); + child_set_env(&child_env, &envsize, "PATH", _PATH_STDPATH); + child_set_env(&child_env, &envsize, "USER", pw->pw_name); + child_set_env(&child_env, &envsize, "LOGNAME", pw->pw_name); + child_set_env(&child_env, &envsize, "HOME", pw->pw_dir); + if ((cp = getenv("LANG")) != NULL) + child_set_env(&child_env, &envsize, "LANG", cp); + + for (i = 0; i < NSIG; i++) + signal(i, SIG_DFL); + + if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { + error("%s: open %s: %s", tag, _PATH_DEVNULL, + strerror(errno)); + _exit(1); + } + if (dup2(devnull, STDIN_FILENO) == -1) { + error("%s: dup2: %s", tag, strerror(errno)); + _exit(1); + } + + /* Set up stdout as requested; leave stderr in place for now. */ + fd = -1; + if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) != 0) + fd = p[1]; + else if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0) + fd = devnull; + if (fd != -1 && dup2(fd, STDOUT_FILENO) == -1) { + error("%s: dup2: %s", tag, strerror(errno)); + _exit(1); + } + closefrom(STDERR_FILENO + 1); + + /* Don't use permanently_set_uid() here to avoid fatal() */ + if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) { + error("%s: setresgid %u: %s", tag, (u_int)pw->pw_gid, + strerror(errno)); + _exit(1); + } + if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) { + error("%s: setresuid %u: %s", tag, (u_int)pw->pw_uid, + strerror(errno)); + _exit(1); + } + /* stdin is pointed to /dev/null at this point */ + if ((flags & SSH_SUBPROCESS_STDOUT_DISCARD) != 0 && + dup2(STDIN_FILENO, STDERR_FILENO) == -1) { + error("%s: dup2: %s", tag, strerror(errno)); + _exit(1); + } + + execve(av[0], av, child_env); + error("%s exec \"%s\": %s", tag, command, strerror(errno)); + _exit(127); + default: /* parent */ + break; + } + + close(p[1]); + if ((flags & SSH_SUBPROCESS_STDOUT_CAPTURE) == 0) + close(p[0]); + else if ((f = fdopen(p[0], "r")) == NULL) { + error("%s: fdopen: %s", tag, strerror(errno)); + close(p[0]); + /* Don't leave zombie child */ + kill(pid, SIGTERM); + while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) + ; + return 0; + } + /* Success */ + debug3("%s: %s pid %ld", __func__, tag, (long)pid); + if (child != NULL) + *child = f; + return pid; +#endif +} + +/* Returns 0 if pid exited cleanly, non-zero otherwise */ +int +exited_cleanly(pid_t pid, const char *tag, const char *cmd, int quiet) +{ + int status; + + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + error("%s: waitpid: %s", tag, strerror(errno)); + return -1; + } + } + if (WIFSIGNALED(status)) { + error("%s %s exited on signal %d", tag, cmd, WTERMSIG(status)); + return -1; + } else if (WEXITSTATUS(status) != 0) { + do_log2(quiet ? SYSLOG_LEVEL_DEBUG1 : SYSLOG_LEVEL_INFO, + "%s %s failed, status %d", tag, cmd, WEXITSTATUS(status)); + return -1; + } + return 0; +} + +/* + * Check a given path for security. This is defined as all components + * of the path to the file must be owned by either the owner of + * of the file or root and no directories must be group or world writable. + * + * XXX Should any specific check be done for sym links ? + * + * Takes a file name, its stat information (preferably from fstat() to + * avoid races), the uid of the expected owner, their home directory and an + * error buffer plus max size as arguments. + * + * Returns 0 on success and -1 on failure + */ +int +safe_path(const char *name, struct stat *stp, const char *pw_dir, + uid_t uid, char *err, size_t errlen) +{ + char buf[PATH_MAX], homedir[PATH_MAX]; + char *cp; + int comparehome = 0; + struct stat st; + + if (realpath(name, buf) == NULL) { + snprintf(err, errlen, "realpath %s failed: %s", name, + strerror(errno)); + return -1; + } + if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL) + comparehome = 1; + + if (!S_ISREG(stp->st_mode)) { + snprintf(err, errlen, "%s is not a regular file", buf); + return -1; + } + if ((!platform_sys_dir_uid(stp->st_uid) && stp->st_uid != uid) || + (stp->st_mode & 022) != 0) { + snprintf(err, errlen, "bad ownership or modes for file %s", + buf); + return -1; + } + + /* for each component of the canonical path, walking upwards */ + for (;;) { + if ((cp = dirname(buf)) == NULL) { + snprintf(err, errlen, "dirname() failed"); + return -1; + } + strlcpy(buf, cp, sizeof(buf)); + + if (stat(buf, &st) < 0 || + (!platform_sys_dir_uid(st.st_uid) && st.st_uid != uid) || + (st.st_mode & 022) != 0) { + snprintf(err, errlen, + "bad ownership or modes for directory %s", buf); + return -1; + } + + /* If are past the homedir then we can stop */ + if (comparehome && strcmp(homedir, buf) == 0) + break; + + /* + * dirname should always complete with a "/" path, + * but we can be paranoid and check for "." too + */ + if ((strcmp("/", buf) == 0) || (strcmp(".", buf) == 0)) + break; + } + return 0; +} + +/* + * Version of safe_path() that accepts an open file descriptor to + * avoid races. + * + * Returns 0 on success and -1 on failure + */ +int +safe_path_fd(int fd, const char *file, struct passwd *pw, + char *err, size_t errlen) +{ + struct stat st; + + /* check the open file to avoid races */ + if (fstat(fd, &st) < 0) { + snprintf(err, errlen, "cannot stat file %s: %s", + file, strerror(errno)); + return -1; + } + return safe_path(file, &st, pw->pw_dir, pw->pw_uid, err, errlen); +} + +/* + * Sets the value of the given variable in the environment. If the variable + * already exists, its value is overridden. + */ +void +child_set_env(char ***envp, u_int *envsizep, const char *name, + const char *value) +{ + char **env; + u_int envsize; + u_int i, namelen; + + if (strchr(name, '=') != NULL) { + error("Invalid environment variable \"%.100s\"", name); + return; + } + + /* + * If we're passed an uninitialized list, allocate a single null + * entry before continuing. + */ + if (*envp == NULL && *envsizep == 0) { + *envp = xmalloc(sizeof(char *)); + *envp[0] = NULL; + *envsizep = 1; + } + + /* + * Find the slot where the value should be stored. If the variable + * already exists, we reuse the slot; otherwise we append a new slot + * at the end of the array, expanding if necessary. + */ + env = *envp; + namelen = strlen(name); + for (i = 0; env[i]; i++) + if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=') + break; + if (env[i]) { + /* Reuse the slot. */ + free(env[i]); + } else { + /* New variable. Expand if necessary. */ + envsize = *envsizep; + if (i >= envsize - 1) { + if (envsize >= 1000) + fatal("child_set_env: too many env vars"); + envsize += 50; + env = (*envp) = xreallocarray(env, envsize, sizeof(char *)); + *envsizep = envsize; + } + /* Need to set the NULL pointer at end of array beyond the new slot. */ + env[i + 1] = NULL; + } + + /* Allocate space and format the variable in the appropriate slot. */ + env[i] = xmalloc(strlen(name) + 1 + strlen(value) + 1); + snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value); +} diff --git a/misc.h b/misc.h index c242f90..153d113 100644 --- a/misc.h +++ b/misc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: misc.h,v 1.61 2016/11/30 00:28:31 dtucker Exp $ */ +/* $OpenBSD: misc.h,v 1.63 2017/08/18 05:48:04 djm Exp $ */ /* * Author: Tatu Ylonen @@ -16,6 +16,7 @@ #define _MISC_H #include +#include /* Data structure for representing a forwarding request. */ struct Forward { @@ -132,6 +133,25 @@ int parse_ipqos(const char *); const char *iptos2str(int); void mktemp_proto(char *, size_t); +void child_set_env(char ***envp, u_int *envsizep, const char *name, + const char *value); + +int argv_split(const char *, int *, char ***); +char *argv_assemble(int, char **argv); +int exited_cleanly(pid_t, const char *, const char *, int); + +#define SSH_SUBPROCESS_STDOUT_DISCARD (1) /* Discard stdout */ +#define SSH_SUBPROCESS_STDOUT_CAPTURE (1<<1) /* Redirect stdout */ +#define SSH_SUBPROCESS_STDERR_DISCARD (1<<2) /* Discard stderr */ +pid_t subprocess(const char *, struct passwd *, + const char *, int, char **, FILE **, u_int flags); + +struct stat; +int safe_path(const char *, struct stat *, const char *, uid_t, + char *, size_t); +int safe_path_fd(int, const char *, struct passwd *, + char *err, size_t errlen); + /* readpass.c */ #define RP_ECHO 0x0001 diff --git a/openbsd-compat/bsd-err.c b/openbsd-compat/bsd-err.c index ab10646..e4ed22b 100644 --- a/openbsd-compat/bsd-err.c +++ b/openbsd-compat/bsd-err.c @@ -27,6 +27,12 @@ #include "includes.h" +#include +#include +#include +#include +#include + #ifndef HAVE_ERR void err(int r, const char *fmt, ...) diff --git a/openbsd-compat/explicit_bzero.c b/openbsd-compat/explicit_bzero.c index 5078134..53a0034 100644 --- a/openbsd-compat/explicit_bzero.c +++ b/openbsd-compat/explicit_bzero.c @@ -20,6 +20,8 @@ void explicit_bzero(void *p, size_t n) { + if (n == 0) + return; (void)memset_s(p, n, 0, n); } @@ -34,6 +36,8 @@ static void (* volatile ssh_bzero)(void *, size_t) = bzero; void explicit_bzero(void *p, size_t n) { + if (n == 0) + return; /* * clang -fsanitize=memory needs to intercept memset-like functions * to correctly detect memory initialisation. Make sure one is called diff --git a/openbsd-compat/port-tun.c b/openbsd-compat/port-tun.c index a444adf..a7a5d94 100644 --- a/openbsd-compat/port-tun.c +++ b/openbsd-compat/port-tun.c @@ -199,49 +199,50 @@ sys_tun_open(int tun, int mode) */ #if defined(SSH_TUN_FILTER) +/* + * The tunnel forwarding protocol prepends the address family of forwarded + * IP packets using OpenBSD's numbers. + */ #define OPENBSD_AF_INET 2 #define OPENBSD_AF_INET6 24 int -sys_tun_infilter(struct Channel *c, char *buf, int len) +sys_tun_infilter(struct Channel *c, char *buf, int _len) { + int r; + size_t len; + char *ptr = buf; #if defined(SSH_TUN_PREPEND_AF) char rbuf[CHAN_RBUF]; - struct ip *iph; + struct ip iph; #endif - u_int32_t *af; - char *ptr = buf; - int r; +#if defined(SSH_TUN_PREPEND_AF) || defined(SSH_TUN_COMPAT_AF) + u_int32_t af; +#endif + + /* XXX update channel input filter API to use unsigned length */ + if (_len < 0) + return -1; + len = _len; #if defined(SSH_TUN_PREPEND_AF) - if (len <= 0 || len > (int)(sizeof(rbuf) - sizeof(*af))) - return (-1); - ptr = (char *)&rbuf[0]; - bcopy(buf, ptr + sizeof(u_int32_t), len); - len += sizeof(u_int32_t); - af = (u_int32_t *)ptr; - - iph = (struct ip *)(ptr + sizeof(u_int32_t)); - switch (iph->ip_v) { - case 6: - *af = AF_INET6; - break; - case 4: - default: - *af = AF_INET; - break; - } -#endif - -#if defined(SSH_TUN_COMPAT_AF) - if (len < (int)sizeof(u_int32_t)) - return (-1); - - af = (u_int32_t *)ptr; - if (*af == htonl(AF_INET6)) - *af = htonl(OPENBSD_AF_INET6); - else - *af = htonl(OPENBSD_AF_INET); + if (len <= sizeof(iph) || len > sizeof(rbuf) - 4) + return -1; + /* Determine address family from packet IP header. */ + memcpy(&iph, buf, sizeof(iph)); + af = iph.ip_v == 6 ? OPENBSD_AF_INET6 : OPENBSD_AF_INET; + /* Prepend address family to packet using OpenBSD constants */ + memcpy(rbuf + 4, buf, len); + len += 4; + POKE_U32(rbuf, af); + ptr = rbuf; +#elif defined(SSH_TUN_COMPAT_AF) + /* Convert existing address family header to OpenBSD value */ + if (len <= 4) + return -1; + af = PEEK_U32(buf); + /* Put it back */ + POKE_U32(buf, af == AF_INET6 ? OPENBSD_AF_INET6 : OPENBSD_AF_INET); #endif if ((r = sshbuf_put_string(&c->input, ptr, len)) != 0) @@ -253,7 +254,7 @@ u_char * sys_tun_outfilter(struct Channel *c, u_char **data, u_int *dlen) { u_char *buf; - u_int32_t *af; + u_int32_t af; int r; size_t xxx_dlen; @@ -262,21 +263,19 @@ sys_tun_outfilter(struct Channel *c, u_char **data, u_int *dlen) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (dlen != NULL) *dlen = xxx_dlen; - if (*dlen < sizeof(*af)) + if (*dlen < sizeof(af)) return (NULL); buf = *data; #if defined(SSH_TUN_PREPEND_AF) - *dlen -= sizeof(u_int32_t); - buf = *data + sizeof(u_int32_t); + /* skip address family */ + *dlen -= sizeof(af); + buf = *data + sizeof(af); #elif defined(SSH_TUN_COMPAT_AF) - af = ntohl(*(u_int32_t *)buf); - if (*af == OPENBSD_AF_INET6) - *af = htonl(AF_INET6); - else - *af = htonl(AF_INET); + /* translate address family */ + af = (PEEK_U32(buf) == OPENBSD_AF_INET6) ? AF_INET6 : AF_INET; + POKE_U32(buf, af); #endif - return (buf); } #endif /* SSH_TUN_FILTER */ diff --git a/packet.c b/packet.c index 9458ffd..ff69b66 100644 --- a/packet.c +++ b/packet.c @@ -1,4 +1,4 @@ -/* $OpenBSD: packet.c,v 1.262 2017/06/24 06:38:11 djm Exp $ */ +/* $OpenBSD: packet.c,v 1.263 2017/07/23 23:37:02 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -1997,7 +1997,7 @@ void ssh_packet_set_tos(struct ssh *ssh, int tos) { #ifndef IP_TOS_IS_BROKEN - if (!ssh_packet_connection_is_on_socket(ssh)) + if (!ssh_packet_connection_is_on_socket(ssh) || tos == INT_MAX) return; switch (ssh_packet_connection_af(ssh)) { # ifdef IP_TOS diff --git a/platform-misc.c b/platform-misc.c new file mode 100644 index 0000000..3f39670 --- /dev/null +++ b/platform-misc.c @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2006 Darren Tucker. All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "includes.h" + +#include "openbsd-compat/openbsd-compat.h" + +/* + * return 1 if the specified uid is a uid that may own a system directory + * otherwise 0. + */ +int +platform_sys_dir_uid(uid_t uid) +{ + if (uid == 0) + return 1; +#ifdef PLATFORM_SYS_DIR_UID + if (uid == PLATFORM_SYS_DIR_UID) + return 1; +#endif + return 0; +} diff --git a/platform.c b/platform.c index 973a63e..18c7751 100644 --- a/platform.c +++ b/platform.c @@ -197,19 +197,3 @@ platform_krb5_get_principal_name(const char *pw_name) return NULL; #endif } - -/* - * return 1 if the specified uid is a uid that may own a system directory - * otherwise 0. - */ -int -platform_sys_dir_uid(uid_t uid) -{ - if (uid == 0) - return 1; -#ifdef PLATFORM_SYS_DIR_UID - if (uid == PLATFORM_SYS_DIR_UID) - return 1; -#endif - return 0; -} diff --git a/regress/pesterTests/KeyUtils.Tests.ps1 b/regress/pesterTests/KeyUtils.Tests.ps1 index f51645f..50f0e5e 100644 --- a/regress/pesterTests/KeyUtils.Tests.ps1 +++ b/regress/pesterTests/KeyUtils.Tests.ps1 @@ -38,7 +38,10 @@ Describe "E2E scenarios for ssh key management" -Tags "CI" { #only validate owner and ACEs of the file function ValidateKeyFile { - param([string]$FilePath) + param( + [string]$FilePath, + [bool]$IsHostKey = $true + ) $myACL = Get-ACL $FilePath $currentOwnerSid = Get-UserSid -User $myACL.Owner @@ -53,8 +56,14 @@ Describe "E2E scenarios for ssh key management" -Tags "CI" { $FullControlPerm = [System.UInt32] [System.Security.AccessControl.FileSystemRights]::FullControl.value__ if($FilePath.EndsWith(".pub")) { - $myACL.Access.Count | Should Be 4 - $identities = @($systemSid, $adminsSid, $currentUserSid, $everyoneSid) + if ($IsHostKey) { + $myACL.Access.Count | Should Be 3 + $identities = @($systemSid, $adminsSid, $currentUserSid) + } + else { + $myACL.Access.Count | Should Be 4 + $identities = @($systemSid, $adminsSid, $currentUserSid, $everyoneSid) + } } else { $myACL.Access.Count | Should Be 3 @@ -135,7 +144,7 @@ Describe "E2E scenarios for ssh key management" -Tags "CI" { ssh-keygen -t $type -P $keypassphrase -f $keyPath } ValidateKeyFile -FilePath $keyPath - ValidateKeyFile -FilePath "$keyPath.pub" + ValidateKeyFile -FilePath "$keyPath.pub" -IsHostKey $false } } } diff --git a/regress/pesterTests/SSHDConfig.tests.ps1 b/regress/pesterTests/SSHDConfig.tests.ps1 index 722d8ae..858d796 100644 --- a/regress/pesterTests/SSHDConfig.tests.ps1 +++ b/regress/pesterTests/SSHDConfig.tests.ps1 @@ -1,17 +1,133 @@ -Describe "Tests of sshd_config" -Tags "Scenario" { +If ($PSVersiontable.PSVersion.Major -le 2) {$PSScriptRoot = Split-Path -Parent $MyInvocation.MyCommand.Path} +Import-Module $PSScriptRoot\CommonUtils.psm1 -Force +$tC = 1 +$tI = 0 +$suite = "sshdConfig" +Describe "Tests of sshd_config" -Tags "CI" { BeforeAll { - $fileName = "test.txt" - $filePath = Join-Path ${TestDrive} $fileName + if($OpenSSHTestInfo -eq $null) + { + Throw "`$OpenSSHTestInfo is null. Please run Set-OpenSSHTestEnvironment to set test environments." + } + + $testDir = "$($OpenSSHTestInfo["TestDataPath"])\$suite" + if( -not (Test-path $testDir -PathType Container)) + { + $null = New-Item $testDir -ItemType directory -Force -ErrorAction SilentlyContinue + } - [Machine] $client = [Machine]::new([MachineRole]::Client) - [Machine] $server = [Machine]::new([MachineRole]::Server) - $client.SetupClient($server) - $server.SetupServer($client) + $fileName = "test.txt" + $logName = "sshdlog.txt" + $server = $OpenSSHTestInfo["Target"] + $port = 47003 + Remove-Item -Path (Join-Path $testDir "*$fileName") -Force -ErrorAction SilentlyContinue + + Add-Type -AssemblyName System.DirectoryServices.AccountManagement + $ContextName = $env:COMPUTERNAME + $ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Machine + $PrincipalContext = [System.DirectoryServices.AccountManagement.PrincipalContext]::new($ContextType, $ContextName) + $IdentityType = [System.DirectoryServices.AccountManagement.IdentityType]::SamAccountName + + function Add-LocalUser + { + param([string] $UserName, [string] $Password) + $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($PrincipalContext, $IdentityType, $UserName) + if($user -eq $null) + { + try { + $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::new($PrincipalContext,$UserName,$Password, $true) + $user.Save() + } + finally { + $user.Dispose() + } + } + } + + function Add-LocalGroup + { + param([string] $groupName) + $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($PrincipalContext, $IdentityType, $GroupName) + if($group -eq $null) + { + try { + $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::new($PrincipalContext,$groupName) + $group.Save() + } + finally { + $group.Dispose() + } + } + } + + function Add-UserToLocalGroup + { + param([string]$UserName, [string]$Password, [string]$GroupName) + Add-LocalGroup -groupName $GroupName + Add-LocalUser -UserName $UserName -Password $Password + $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($PrincipalContext, $IdentityType, $GroupName) + $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($PrincipalContext, $IdentityType, $UserName) + + if(-not $group.Members.Contains($user)) + { + try { + $group.Members.Add($user) + $group.save() + } + finally { + $group.Dispose() + } + } + } + + function Remove-UserFromLocalGroup + { + param([string]$UserName, [string]$GroupName) + $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($PrincipalContext, $IdentityType, $GroupName) + $user = [System.DirectoryServices.AccountManagement.UserPrincipal]::FindByIdentity($PrincipalContext, $IdentityType, $UserName) + if($group.Members.Contains($user)) + { + try { + $group.Members.Remove($user) + $group.save() + } + finally { + $group.Dispose() + } + } + } + + function Clenaup-LocalGroup + { + param([string]$GroupName) + $group = [System.DirectoryServices.AccountManagement.GroupPrincipal]::FindByIdentity($PrincipalContext, $IdentityType, $GroupName) + if($group -ne $null) + { + try { + $group.Delete() + } + finally { + $group.Dispose() + } + } + } + $platform = Get-Platform + $skip = ($platform -eq [PlatformType]::Windows) -and ($PSVersionTable.PSVersion.Major -le 2) + if(($platform -eq [PlatformType]::Windows) -and ($psversiontable.BuildVersion.Major -le 6)) + { + #suppress the firewall blocking dialogue on win7 + netsh advfirewall firewall add rule name="sshd" program="$($OpenSSHTestInfo['OpenSSHBinPath'])\sshd.exe" protocol=any action=allow dir=in + } } + AfterEach { $tI++ } + AfterAll { - $client.CleanupClient() - $server.CleanupServer() + $PrincipalContext.Dispose() + if(($platform -eq [PlatformType]::Windows) -and ($psversiontable.BuildVersion.Major -le 6)) + { + netsh advfirewall firewall delete rule name="sshd" program="$($OpenSSHTestInfo['OpenSSHBinPath'])\sshd.exe" protocol=any dir=in + } } <# @@ -24,7 +140,6 @@ #> Context "Tests of AllowGroups, AllowUsers, DenyUsers, DenyGroups" { BeforeAll { - Remove-Item -Path $filePath -Force -ea silentlycontinue $password = "Bull_dog1" $allowUser1 = "allowuser1" @@ -47,113 +162,183 @@ $denyGroup1 = "denygroup1" $denyGroup2 = "denygroup2" $denyGroup3 = "denygroup3" - $client.AddPasswordSetting($password) - } - AfterEach { - Remove-Item -Path $filePath -Force -ea SilentlyContinue + $sshdConfigPath = Join-Path $PSScriptRoot testdata\SSHD_Config + $testknownhosts = Join-path $PSScriptRoot testdata\test_known_hosts + Get-Process -Name sshd -ErrorAction SilentlyContinue | Where-Object {$_.SessionID -ne 0} | Stop-process -force -ErrorAction SilentlyContinue + #add wrong password so ssh does not prompt password if failed with authorized keys + Add-PasswordSetting -Pass $password + $tI=1 + } + + BeforeEach { + $filePath = Join-Path $testDir "$tC.$tI.$fileName" + $logPath = Join-Path $testDir "$tC.$tI.$logName" + Get-Process -Name sshd -ErrorAction SilentlyContinue | Where-Object {$_.SessionID -ne 0} | Stop-process -force -ErrorAction SilentlyContinue } - AfterAll { - $client.CleanupPasswordSetting() + AfterAll { + Remove-PasswordSetting + $tC++ } - It 'User with full name in the list of AllowUsers' { - $server.AddUserToLocalGroup($allowUser1, $password, $allowGroup1) + It "$tC.$tI-User with full name in the list of AllowUsers" { + #Run + Start-Process -FilePath sshd.exe -WorkingDirectory $($OpenSSHTestInfo['OpenSSHBinPath']) -ArgumentList @("-d", "-f $sshdConfigPath", "-E $logPath") -NoNewWindow + + Add-UserToLocalGroup -UserName $allowUser1 -Password $password -GroupName $allowGroup1 + + $o = ssh -p $port $allowUser1@$server -o "UserKnownHostsFile $testknownhosts" echo 1234 + $o | Should Be "1234" + Remove-UserFromLocalGroup -UserName $allowUser1 -GroupName $allowGroup1 + + #Cleanup + Get-Process -Name sshd -ErrorAction SilentlyContinue | Where-Object {$_.SessionID -ne 0} | Stop-process -force -ErrorAction SilentlyContinue + } + + It "$tC.$tI-User with * wildcard" { + #Run + Start-Process -FilePath sshd.exe -WorkingDirectory $($OpenSSHTestInfo['OpenSSHBinPath']) -ArgumentList @("-d", "-f $sshdConfigPath", "-E $logPath") -NoNewWindow + + Add-UserToLocalGroup -UserName $allowUser2 -Password $password -GroupName $allowGroup1 - $client.RunCmd(".\ssh $($allowUser1)@$($server.MachineName) hostname > $filePath") - Get-Content $filePath | Should be $server.MachineName - $server.RemoveUserFromLocalGroup($allowUser1, $allowGroup1) + $o = ssh -p $port $allowUser2@$server -o "UserKnownHostsFile $testknownhosts" echo 1234 + $o | Should Be "1234" + Remove-UserFromLocalGroup -UserName $allowUser2 -GroupName $allowGroup1 + + #Cleanup + Get-Process -Name sshd -ErrorAction SilentlyContinue | Where-Object {$_.SessionID -ne 0} | Stop-process -force -ErrorAction SilentlyContinue } - It 'User with * wildcard' { - $server.AddUserToLocalGroup($allowUser2, $password, $allowGroup1) + It "$tC.$tI-User with ? wildcard" { + #Run + Start-Process -FilePath sshd.exe -WorkingDirectory $($OpenSSHTestInfo['OpenSSHBinPath']) -ArgumentList @("-d", "-f $sshdConfigPath", "-E $logPath") -NoNewWindow + Add-UserToLocalGroup -UserName $allowUser3 -Password $password -GroupName $allowGroup1 - $client.RunCmd(".\ssh $($allowUser2)@$($server.MachineName) hostname > $filePath") - $LASTEXITCODE | Should Be 0 - Get-Content $filePath | Should be $server.MachineName - $server.RemoveUserFromLocalGroup($allowUser2, $allowGroup1) + $o = ssh -p $port $allowUser3@$server -o "UserKnownHostsFile $testknownhosts" echo 1234 + $o | Should Be "1234" + Remove-UserFromLocalGroup -UserName $allowUser3 -GroupName $allowGroup1 + + #Cleanup + Get-Process -Name sshd -ErrorAction SilentlyContinue | Where-Object {$_.SessionID -ne 0} | Stop-process -force -ErrorAction SilentlyContinue } - It 'User with ? wildcard' { - $server.AddUserToLocalGroup($allowUser3, $password, $allowGroup1) + It "$tC.$tI-User with full name in the list of AllowUsers but not in any AllowGroups" { + #Run + Start-Process -FilePath sshd.exe -WorkingDirectory $($OpenSSHTestInfo['OpenSSHBinPath']) -ArgumentList @("-d", "-f $sshdConfigPath", "-E $logPath") -NoNewWindow + + Add-LocalUser -UserName $allowUser4 -Password $password + + ssh -p $port -E $filePath -o "UserKnownHostsFile $testknownhosts" $allowUser4@$server echo 1234 + $LASTEXITCODE | Should Not Be 0 + $matches = Get-Content $filePath | Select-String -pattern "Permission denied" + $matches.Count | Should BeGreaterThan 2 + + #Cleanup + Get-Process -Name sshd -ErrorAction SilentlyContinue | Where-Object {$_.SessionID -ne 0} | Stop-process -force -ErrorAction SilentlyContinue + } + + It "$tC.$tI-User with full name in the list of DenyUsers" { + #Run + Start-Process -FilePath sshd.exe -WorkingDirectory $($OpenSSHTestInfo['OpenSSHBinPath']) -ArgumentList @("-d", "-f $sshdConfigPath", "-E $logPath") -NoNewWindow + + Add-UserToLocalGroup -UserName $denyUser1 -Password $password -GroupName $allowGroup1 + + ssh -p $port -E $filePath -o "UserKnownHostsFile $testknownhosts" $denyUser1@$server echo 1234 + $LASTEXITCODE | Should Not Be 0 + $matches = Get-Content $filePath | Select-String -pattern "Permission denied" + $matches.Count | Should BeGreaterThan 2 + + Remove-UserFromLocalGroup -UserName $denyUser1 -GroupName $allowGroup1 + + #Cleanup + Get-Process -Name sshd -ErrorAction SilentlyContinue | Where-Object {$_.SessionID -ne 0} | Stop-process -force -ErrorAction SilentlyContinue + } + + It "$tC.$tI-User with * wildcard in the list of DenyUsers" { + #Run + Start-Process -FilePath sshd.exe -WorkingDirectory $($OpenSSHTestInfo['OpenSSHBinPath']) -ArgumentList @("-d", "-f $sshdConfigPath", "-E $logPath") -NoNewWindow + + Add-UserToLocalGroup -UserName $denyUser2 -Password $password -GroupName $allowGroup1 + + ssh -p $port -E $filePath -o "UserKnownHostsFile $testknownhosts" $denyUser2@$server echo 1234 + $LASTEXITCODE | Should Not Be 0 + $matches = Get-Content $filePath | Select-String -pattern "Permission denied" + $matches.Count | Should BeGreaterThan 2 + + Remove-UserFromLocalGroup -UserName $denyUser2 -GroupName $allowGroup1 + + #Cleanup + Get-Process -Name sshd -ErrorAction SilentlyContinue | Where-Object {$_.SessionID -ne 0} | Stop-process -force -ErrorAction SilentlyContinue + } + + It "$tC.$tI-User with ? wildcard in the list of DenyUsers" { + #Run + Start-Process -FilePath sshd.exe -WorkingDirectory $($OpenSSHTestInfo['OpenSSHBinPath']) -ArgumentList @("-d", "-f $sshdConfigPath", "-E $logPath") -NoNewWindow + + Add-UserToLocalGroup -UserName $denyUser3 -Password $password -GroupName $allowGroup1 + + ssh -p $port -E $filePath -o "UserKnownHostsFile $testknownhosts" $denyUser3@$server echo 1234 + $LASTEXITCODE | Should Not Be 0 + $matches = Get-Content $filePath | Select-String -pattern "Permission denied" + $matches.Count | Should BeGreaterThan 2 - $client.RunCmd(".\ssh $($allowUser3)@$($server.MachineName) hostname > $filePath") - $LASTEXITCODE | Should Be 0 - Get-Content $filePath | Should be $server.MachineName - $server.RemoveUserFromLocalGroup($allowUser3, $allowGroup1) + Remove-UserFromLocalGroup -UserName $denyUser3 -GroupName $allowGroup1 + + #Cleanup + Get-Process -Name sshd -ErrorAction SilentlyContinue | Where-Object {$_.SessionID -ne 0} | Stop-process -force -ErrorAction SilentlyContinue } - It 'User with full name in the list of AllowUsers but not in any AllowGroups' { - $server.AddLocalUser($allowUser4, $password) + It "$tC.$tI-User is listed in the list of AllowUsers but also in a full name DenyGroups and AllowGroups" { + #Run + Start-Process -FilePath sshd.exe -WorkingDirectory $($OpenSSHTestInfo['OpenSSHBinPath']) -ArgumentList @("-d", "-f $sshdConfigPath", "-E $logPath") -NoNewWindow + + Add-UserToLocalGroup -UserName $localuser1 -Password $password -GroupName $allowGroup1 + Add-UserToLocalGroup -UserName $localuser1 -Password $password -GroupName $denyGroup1 - $client.RunCmd(".\ssh $($allowUser4)@$($server.MachineName) hostname > $filePath") + ssh -p $port -E $filePath -o "UserKnownHostsFile $testknownhosts" $localuser1@$server echo 1234 $LASTEXITCODE | Should Not Be 0 - Get-Content $filePath | Should BeNullOrEmpty + $matches = Get-Content $filePath | Select-String -pattern "Permission denied" + $matches.Count | Should BeGreaterThan 2 + + Remove-UserFromLocalGroup -UserName $localuser1 -GroupName $allowGroup1 + Remove-UserFromLocalGroup -UserName $localuser1 -GroupName $denyGroup1 + + #Cleanup + Get-Process -Name sshd -ErrorAction SilentlyContinue | Where-Object {$_.SessionID -ne 0} | Stop-process -force -ErrorAction SilentlyContinue } - It 'User with full name in the list of DenyUsers' { - $server.AddUserToLocalGroup($denyUser1, $password, $allowGroup1) + It "$tC.$tI-User is listed in the list of AllowUsers but also in a wildcard * DenyGroups" { + #Run + Start-Process -FilePath sshd.exe -WorkingDirectory $($OpenSSHTestInfo['OpenSSHBinPath']) -ArgumentList @("-d", "-f $sshdConfigPath", "-E $logPath") -NoNewWindow - $client.RunCmd(".\ssh $($denyUser1)@$($server.MachineName) hostname > $filePath") - $LASTEXITCODE | Should Not Be 0 - Get-Content $filePath | Should BeNullOrEmpty - - $server.RemoveUserFromLocalGroup($denyUser1, $allowGroup1) - } - - It 'User with * wildcard in the list of DenyUsers' { - $server.AddUserToLocalGroup($denyUser2, $password, $allowGroup1) - - $str = ".\ssh $($denyUser2)@$($server.MachineName) hostname > $filePath" - $client.RunCmd(".\ssh $($denyUser2)@$($server.MachineName) hostname > $filePath") - $LASTEXITCODE | Should Not Be 0 - Get-Content $filePath | Should BeNullOrEmpty - - $server.RemoveUserFromLocalGroup($denyUser2, $allowGroup1) - } - - It 'User with ? wildcard in the list of DenyUsers' { - $server.AddUserToLocalGroup($denyUser3, $password, $allowGroup1) + Add-UserToLocalGroup -UserName $localuser2 -Password $password -GroupName $denyGroup2 - $client.RunCmd(".\ssh $($denyUser3)@$($server.MachineName) hostname > $filePath") + ssh -p $port -E $filePath -o "UserKnownHostsFile $testknownhosts" $localuser2@$server echo 1234 $LASTEXITCODE | Should Not Be 0 - Get-Content $filePath | Should BeNullOrEmpty - - $server.RemoveUserFromLocalGroup($denyUser3, $allowGroup1) - } - - It 'User is listed in the list of AllowUsers but also in a full name DenyGroups and AllowGroups' { - $server.AddUserToLocalGroup($localuser1, $password, $allowGroup1) - $server.AddUserToLocalGroup($localuser1, $password, $denyGroup1) + $matches = Get-Content $filePath | Select-String -pattern "Permission denied" + $matches.Count | Should BeGreaterThan 2 - $client.RunCmd(".\ssh $($localuser1)@$($server.MachineName) hostname > $filePath") + Remove-UserFromLocalGroup -UserName $localuser2 -GroupName $denyGroup2 - $LASTEXITCODE | Should Not Be 0 - Get-Content $filePath | Should BeNullOrEmpty - - - $server.RemoveUserFromLocalGroup($localuser1, $allowGroup1) - $server.RemoveUserFromLocalGroup($localuser1, $denyGroup1) + #Cleanup + Get-Process -Name sshd -ErrorAction SilentlyContinue | Where-Object {$_.SessionID -ne 0} | Stop-process -force -ErrorAction SilentlyContinue } - It 'User is listed in the list of AllowUsers but also in a wildcard * DenyGroups' { - $server.AddUserToLocalGroup($localuser2, $password, $denyGroup2) - - $client.RunCmd(".\ssh $($localuser2)@$($server.MachineName) hostname > $filePath") + It "$tC.$tI-User is listed in the list of AllowUsers but also in a wildcard ? DenyGroups" { + #Run + Start-Process -FilePath sshd.exe -WorkingDirectory $($OpenSSHTestInfo['OpenSSHBinPath']) -ArgumentList @("-d", "-f $sshdConfigPath", "-E $logPath") -NoNewWindow + + Add-UserToLocalGroup -UserName $localuser3 -Password $password -GroupName $denyGroup3 + + ssh -p $port -E $filePath -o "UserKnownHostsFile $testknownhosts" $localuser3@$server echo 1234 $LASTEXITCODE | Should Not Be 0 - Get-Content $filePath | Should BeNullOrEmpty + $matches = Get-Content $filePath | Select-String -pattern "Permission denied" + $matches.Count | Should BeGreaterThan 2 + + Remove-UserFromLocalGroup -UserName $localuser3 -GroupName $denyGroup3 - $server.RemoveUserFromLocalGroup($localuser2, $denyGroup2) - } - - It 'User is listed in the list of AllowUsers but also in a wildcard ? DenyGroups' { - $server.AddUserToLocalGroup($localuser3, $password, $denyGroup3) - - $client.RunCmd(".\ssh $($localuser3)@$($server.MachineName) hostname > $filePath") - $LASTEXITCODE | Should Not Be 0 - Get-Content $filePath | Should BeNullOrEmpty - - $server.RemoveUserFromLocalGroup($localuser3, $denyGroup3) + #Cleanup + Get-Process -Name sshd -ErrorAction SilentlyContinue | Where-Object {$_.SessionID -ne 0} | Stop-process -force -ErrorAction SilentlyContinue } } } diff --git a/regress/pesterTests/testdata/SSHD_Config b/regress/pesterTests/testdata/SSHD_Config new file mode 100644 index 0000000..d947d9f --- /dev/null +++ b/regress/pesterTests/testdata/SSHD_Config @@ -0,0 +1,119 @@ +# test usage of sshd_config + +Port 47003 +#AddressFamily any +#ListenAddress 0.0.0.0 +#ListenAddress :: + +# The default requires explicit activation of protocol 1 +#Protocol 2 + +# HostKey for protocol version 1 +#HostKey /etc/ssh/ssh_host_key +# HostKeys for protocol version 2 +HostKey sshtest_hostkey_rsa +HostKey sshtest_hostkey_dsa +HostKey sshtest_hostkey_ecdsa +HostKey sshtest_hostkey_ed25519 + +# Lifetime and size of ephemeral version 1 server key +#KeyRegenerationInterval 1h +#ServerKeyBits 1024 + +# Logging +# obsoletes QuietMode and FascistLogging +#SyslogFacility AUTH +LogLevel DEBUG3 + +# Authentication: + +#LoginGraceTime 2m +#PermitRootLogin yes +#StrictModes yes +#MaxAuthTries 6 +#MaxSessions 10 + +#RSAAuthentication yes +#PubkeyAuthentication yes + +# The default is to check both .ssh/authorized_keys and .ssh/authorized_keys2 +# but this is overridden so installations will only check .ssh/authorized_keys +AuthorizedKeysFile .ssh/authorized_keys + +# For this to work you will also need host keys in /etc/ssh/ssh_known_hosts +#RhostsRSAAuthentication no +# similar for protocol version 2 +#HostbasedAuthentication no +# Change to yes if you don't trust ~/.ssh/known_hosts for +# RhostsRSAAuthentication and HostbasedAuthentication +#IgnoreUserKnownHosts no +# Don't read the user's ~/.rhosts and ~/.shosts files +#IgnoreRhosts yes + +# To disable tunneled clear text passwords, change to no here! +#PasswordAuthentication yes +#PermitEmptyPasswords no + +# Change to no to disable s/key passwords +#ChallengeResponseAuthentication yes + +# Kerberos options +#KerberosAuthentication no +#KerberosOrLocalPasswd yes +#KerberosTicketCleanup yes +#KerberosGetAFSToken no + +# GSSAPI options +#GSSAPIAuthentication no +#GSSAPICleanupCredentials yes + +# Set this to 'yes' to enable PAM authentication, account processing, +# and session processing. If this is enabled, PAM authentication will +# be allowed through the ChallengeResponseAuthentication and +# PasswordAuthentication. Depending on your PAM configuration, +# PAM authentication via ChallengeResponseAuthentication may bypass +# the setting of "PermitRootLogin without-password". +# If you just want the PAM account and session checks to run without +# PAM authentication, then enable this but set PasswordAuthentication +# and ChallengeResponseAuthentication to 'no'. +#UsePAM no + +#AllowAgentForwarding yes +#AllowTcpForwarding yes +#GatewayPorts no +#X11Forwarding no +#X11DisplayOffset 10 +#X11UseLocalhost yes +#PrintMotd yes +#PrintLastLog yes +#TCPKeepAlive yes +#UseLogin no +#UsePrivilegeSeparation yes +#PermitUserEnvironment no +#Compression delayed +#ClientAliveInterval 0 +#ClientAliveCountMax 3 +#UseDNS yes +#PidFile /var/run/sshd.pid +#MaxStartups 10 +#PermitTunnel no +#ChrootDirectory none + +# no default banner path +#Banner none + +# override default of no subsystems +Subsystem sftp sftp-server.exe -l DEBUG3 + +# Example of overriding settings on a per-user basis +#Match User anoncvs +# X11Forwarding no +# AllowTcpForwarding no +# ForceCommand cvs server +PubkeyAcceptedKeyTypes ssh-ed25519* + +DenyUsers denyuser1 deny*2 denyuse?3, +AllowUsers allowuser1 allowu*r2 allow?se?3 allowuser4 localuser1 localu*r2 loc?lu?er3 localadmin +DenyGroups denygroup1 denygr*p2 deny?rou?3 +AllowGroups allowgroup1 allowg*2 allowg?ou?3 Adm* +hostkeyagent \\.\pipe\openssh-ssh-agent diff --git a/servconf.c b/servconf.c index 6907bf5..e6229eb 100644 --- a/servconf.c +++ b/servconf.c @@ -2084,6 +2084,32 @@ parse_server_config(ServerOptions *options, const char *filename, Buffer *conf, fatal("%s: terminating, %d bad configuration options", filename, bad_options); process_queued_listen_addrs(options); + +#ifdef WINDOWS + /* TODO - Refactor this into a platform specific post-read config processing routine. + * TODO - support all forms of username, groupname. + * a) domain\groupname + * b) domain\groupname@hostip + * c) full_domain_name\groupname + * d) full_domain_name\groupname@hostip + * e) user@domain + * f) domain\user + * g) fulldomain\user + * h) user@domain@hostip + */ + /* convert the users, user groups to lower case */ + for(int i = 0; i < options->num_allow_users; i++) + lowercase(options->allow_users[i]); + + for (int i = 0; i < options->num_deny_users; i++) + lowercase(options->deny_users[i]); + + for (int i = 0; i < options->num_allow_groups; i++) + lowercase(options->allow_groups[i]); + + for (int i = 0; i < options->num_deny_groups; i++) + lowercase(options->deny_groups[i]); +#endif // WINDOWS } static const char * diff --git a/serverloop.c b/serverloop.c index b5eb344..5cc3fc0 100644 --- a/serverloop.c +++ b/serverloop.c @@ -1,4 +1,4 @@ -/* $OpenBSD: serverloop.c,v 1.193 2017/05/31 07:00:13 markus Exp $ */ +/* $OpenBSD: serverloop.c,v 1.195 2017/08/11 04:16:35 dtucker Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -204,6 +204,7 @@ wait_until_can_do_something(int connection_in, int connection_out, int ret; time_t minwait_secs = 0; int client_alive_scheduled = 0; + static time_t last_client_time; /* Allocate and update select() masks for channel descriptors. */ channel_prepare_select(readsetp, writesetp, maxfdp, nallocp, @@ -268,8 +269,19 @@ wait_until_can_do_something(int connection_in, int connection_out, memset(*writesetp, 0, *nallocp); if (errno != EINTR) error("select: %.100s", strerror(errno)); - } else if (ret == 0 && client_alive_scheduled) - client_alive_check(); + } else if (client_alive_scheduled) { + time_t now = monotime(); + + if (ret == 0) { /* timeout */ + client_alive_check(); + } else if (FD_ISSET(connection_in, *readsetp)) { + last_client_time = now; + } else if (last_client_time != 0 && last_client_time + + options.client_alive_interval <= now) { + client_alive_check(); + last_client_time = now; + } + } notify_done(*readsetp); } diff --git a/session.c b/session.c index eed1706..1d354ec 100644 --- a/session.c +++ b/session.c @@ -1,4 +1,4 @@ -/* $OpenBSD: session.c,v 1.290 2017/06/24 06:34:38 djm Exp $ */ +/* $OpenBSD: session.c,v 1.291 2017/08/18 05:36:45 djm Exp $ */ /* * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland * All rights reserved @@ -1102,65 +1102,6 @@ check_quietlogin(Session *s, const char *command) return 0; } -/* - * Sets the value of the given variable in the environment. If the variable - * already exists, its value is overridden. - */ -void -child_set_env(char ***envp, u_int *envsizep, const char *name, - const char *value) -{ - char **env; - u_int envsize; - u_int i, namelen; - - if (strchr(name, '=') != NULL) { - error("Invalid environment variable \"%.100s\"", name); - return; - } - - /* - * If we're passed an uninitialized list, allocate a single null - * entry before continuing. - */ - if (*envp == NULL && *envsizep == 0) { - *envp = xmalloc(sizeof(char *)); - *envp[0] = NULL; - *envsizep = 1; - } - - /* - * Find the slot where the value should be stored. If the variable - * already exists, we reuse the slot; otherwise we append a new slot - * at the end of the array, expanding if necessary. - */ - env = *envp; - namelen = strlen(name); - for (i = 0; env[i]; i++) - if (strncmp(env[i], name, namelen) == 0 && env[i][namelen] == '=') - break; - if (env[i]) { - /* Reuse the slot. */ - free(env[i]); - } else { - /* New variable. Expand if necessary. */ - envsize = *envsizep; - if (i >= envsize - 1) { - if (envsize >= 1000) - fatal("child_set_env: too many env vars"); - envsize += 50; - env = (*envp) = xreallocarray(env, envsize, sizeof(char *)); - *envsizep = envsize; - } - /* Need to set the NULL pointer at end of array beyond the new slot. */ - env[i + 1] = NULL; - } - - /* Allocate space and format the variable in the appropriate slot. */ - env[i] = xmalloc(strlen(name) + 1 + strlen(value) + 1); - snprintf(env[i], strlen(name) + 1 + strlen(value) + 1, "%s=%s", name, value); -} - /* * Reads environment variables from the given file and adds/overrides them * into the environment. If the file does not exist, this does nothing. @@ -1262,8 +1203,9 @@ read_etc_default_login(char ***env, u_int *envsize, uid_t uid) } #endif /* HAVE_ETC_DEFAULT_LOGIN */ -void -copy_environment(char **source, char ***env, u_int *envsize) +static void +copy_environment_blacklist(char **source, char ***env, u_int *envsize, + const char *blacklist) { char *var_name, *var_val; int i; @@ -1279,13 +1221,22 @@ copy_environment(char **source, char ***env, u_int *envsize) } *var_val++ = '\0'; - debug3("Copy environment: %s=%s", var_name, var_val); - child_set_env(env, envsize, var_name, var_val); + if (blacklist == NULL || + match_pattern_list(var_name, blacklist, 0) != 1) { + debug3("Copy environment: %s=%s", var_name, var_val); + child_set_env(env, envsize, var_name, var_val); + } free(var_name); } } +void +copy_environment(char **source, char ***env, u_int *envsize) +{ + copy_environment_blacklist(source, env, envsize, NULL); +} + static char ** do_setup_env(Session *s, const char *shell) { @@ -1447,12 +1398,16 @@ do_setup_env(Session *s, const char *shell) if (options.use_pam) { char **p; + /* + * Don't allow SSH_AUTH_INFO variables posted to PAM to leak + * back into the environment. + */ p = fetch_pam_child_environment(); - copy_environment(p, &env, &envsize); + copy_environment_blacklist(p, &env, &envsize, "SSH_AUTH_INFO*"); free_pam_environment(p); p = fetch_pam_environment(); - copy_environment(p, &env, &envsize); + copy_environment_blacklist(p, &env, &envsize, "SSH_AUTH_INFO*"); free_pam_environment(p); } #endif /* USE_PAM */ diff --git a/session.h b/session.h index 98e1daf..74c557d 100644 --- a/session.h +++ b/session.h @@ -1,4 +1,4 @@ -/* $OpenBSD: session.h,v 1.33 2016/08/13 17:47:41 markus Exp $ */ +/* $OpenBSD: session.h,v 1.34 2017/08/18 05:36:45 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -77,8 +77,6 @@ Session *session_new(void); Session *session_by_tty(char *); void session_close(Session *); void do_setusercontext(struct passwd *); -void child_set_env(char ***envp, u_int *envsizep, const char *name, - const char *value); const char *session_get_remote_name_or_ip(struct ssh *, u_int, int); diff --git a/sftp-client.c b/sftp-client.c index a6e8322..6263302 100644 --- a/sftp-client.c +++ b/sftp-client.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sftp-client.c,v 1.126 2017/01/03 05:46:51 djm Exp $ */ +/* $OpenBSD: sftp-client.c,v 1.127 2017/08/11 04:41:08 djm Exp $ */ /* * Copyright (c) 2001-2004 Damien Miller * @@ -140,7 +140,7 @@ get_msg(struct sftp_conn *conn, struct sshbuf *m) fatal("%s: buffer error: %s", __func__, ssh_err(r)); if (atomicio6(read, conn->fd_in, p, 4, conn->limit_kbps > 0 ? sftpio : NULL, &conn->bwlimit_in) != 4) { - if (errno == EPIPE) + if (errno == EPIPE || errno == ECONNRESET) fatal("Connection closed"); else fatal("Couldn't read packet: %s", strerror(errno)); diff --git a/ssh-add.c b/ssh-add.c index 438c1c2..72d89db 100644 --- a/ssh-add.c +++ b/ssh-add.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-add.c,v 1.132 2017/05/30 14:16:41 markus Exp $ */ +/* $OpenBSD: ssh-add.c,v 1.133 2017/07/01 13:50:45 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -55,7 +55,6 @@ #include "xmalloc.h" #include "ssh.h" -#include "rsa.h" #include "log.h" #include "sshkey.h" #include "sshbuf.h" diff --git a/ssh-agent.c b/ssh-agent.c index 2ef8367..0c6c365 100644 --- a/ssh-agent.c +++ b/ssh-agent.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-agent.c,v 1.221 2017/04/30 23:29:10 djm Exp $ */ +/* $OpenBSD: ssh-agent.c,v 1.224 2017/07/24 04:34:28 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -60,6 +60,9 @@ #ifdef HAVE_PATHS_H # include #endif +#ifdef HAVE_POLL_H +# include +#endif #include #include #include @@ -73,7 +76,6 @@ #include "xmalloc.h" #include "ssh.h" -#include "rsa.h" #include "sshbuf.h" #include "sshkey.h" #include "authfd.h" @@ -92,6 +94,9 @@ # define DEFAULT_PKCS11_WHITELIST "/usr/lib*/*,/usr/local/lib*/*" #endif +/* Maximum accepted message length */ +#define AGENT_MAX_LEN (256*1024) + typedef enum { AUTH_UNUSED, AUTH_SOCKET, @@ -635,30 +640,46 @@ send: /* dispatch incoming messages */ -static void -process_message(SocketEntry *e) +static int +process_message(u_int socknum) { u_int msg_len; u_char type; const u_char *cp; int r; + SocketEntry *e; + + if (socknum >= sockets_alloc) { + fatal("%s: socket number %u >= allocated %u", + __func__, socknum, sockets_alloc); + } + e = &sockets[socknum]; if (sshbuf_len(e->input) < 5) - return; /* Incomplete message. */ + return 0; /* Incomplete message header. */ cp = sshbuf_ptr(e->input); msg_len = PEEK_U32(cp); - if (msg_len > 256 * 1024) { - close_socket(e); - return; + if (msg_len > AGENT_MAX_LEN) { + debug("%s: socket %u (fd=%d) message too long %u > %u", + __func__, socknum, e->fd, msg_len, AGENT_MAX_LEN); + return -1; } if (sshbuf_len(e->input) < msg_len + 4) - return; + return 0; /* Incomplete message body. */ /* move the current input to e->request */ sshbuf_reset(e->request); if ((r = sshbuf_get_stringb(e->input, e->request)) != 0 || - (r = sshbuf_get_u8(e->request, &type)) != 0) + (r = sshbuf_get_u8(e->request, &type)) != 0) { + if (r == SSH_ERR_MESSAGE_INCOMPLETE || + r == SSH_ERR_STRING_TOO_LARGE) { + debug("%s: buffer error: %s", __func__, ssh_err(r)); + return -1; + } fatal("%s: buffer error: %s", __func__, ssh_err(r)); + } + + debug("%s: socket %u (fd=%d) type %d", __func__, socknum, e->fd, type); /* check wheter agent is locked */ if (locked && type != SSH_AGENTC_UNLOCK) { @@ -672,10 +693,9 @@ process_message(SocketEntry *e) /* send a fail message for all other request types */ send_status(e, 0); } - return; + return 0; } - debug("type %d", type); switch (type) { case SSH_AGENTC_LOCK: case SSH_AGENTC_UNLOCK: @@ -717,6 +737,7 @@ process_message(SocketEntry *e) send_status(e, 0); break; } + return 0; } static void @@ -758,19 +779,141 @@ new_socket(sock_type type, int fd) } static int -prepare_select(fd_set **fdrp, fd_set **fdwp, int *fdl, u_int *nallocp, - struct timeval **tvpp) +handle_socket_read(u_int socknum) { - u_int i, sz; - int n = 0; - static struct timeval tv; + struct sockaddr_un sunaddr; + socklen_t slen; + uid_t euid; + gid_t egid; + int fd; + + slen = sizeof(sunaddr); + fd = accept(sockets[socknum].fd, (struct sockaddr *)&sunaddr, &slen); + if (fd < 0) { + error("accept from AUTH_SOCKET: %s", strerror(errno)); + return -1; + } + if (getpeereid(fd, &euid, &egid) < 0) { + error("getpeereid %d failed: %s", fd, strerror(errno)); + close(fd); + return -1; + } + if ((euid != 0) && (getuid() != euid)) { + error("uid mismatch: peer euid %u != uid %u", + (u_int) euid, (u_int) getuid()); + close(fd); + return -1; + } + new_socket(AUTH_CONNECTION, fd); + return 0; +} + +static int +handle_conn_read(u_int socknum) +{ + char buf[1024]; + ssize_t len; + int r; + + if ((len = read(sockets[socknum].fd, buf, sizeof(buf))) <= 0) { + if (len == -1) { + if (errno == EAGAIN || errno == EINTR) + return 0; + error("%s: read error on socket %u (fd %d): %s", + __func__, socknum, sockets[socknum].fd, + strerror(errno)); + } + return -1; + } + if ((r = sshbuf_put(sockets[socknum].input, buf, len)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + explicit_bzero(buf, sizeof(buf)); + process_message(socknum); + return 0; +} + +static int +handle_conn_write(u_int socknum) +{ + ssize_t len; + int r; + + if (sshbuf_len(sockets[socknum].output) == 0) + return 0; /* shouldn't happen */ + if ((len = write(sockets[socknum].fd, + sshbuf_ptr(sockets[socknum].output), + sshbuf_len(sockets[socknum].output))) <= 0) { + if (len == -1) { + if (errno == EAGAIN || errno == EINTR) + return 0; + error("%s: read error on socket %u (fd %d): %s", + __func__, socknum, sockets[socknum].fd, + strerror(errno)); + } + return -1; + } + if ((r = sshbuf_consume(sockets[socknum].output, len)) != 0) + fatal("%s: buffer error: %s", __func__, ssh_err(r)); + return 0; +} + +static void +after_poll(struct pollfd *pfd, size_t npfd) +{ + size_t i; + u_int socknum; + + for (i = 0; i < npfd; i++) { + if (pfd[i].revents == 0) + continue; + /* Find sockets entry */ + for (socknum = 0; socknum < sockets_alloc; socknum++) { + if (sockets[socknum].type != AUTH_SOCKET && + sockets[socknum].type != AUTH_CONNECTION) + continue; + if (pfd[i].fd == sockets[socknum].fd) + break; + } + if (socknum >= sockets_alloc) { + error("%s: no socket for fd %d", __func__, pfd[i].fd); + continue; + } + /* Process events */ + switch (sockets[socknum].type) { + case AUTH_SOCKET: + if ((pfd[i].revents & (POLLIN|POLLERR)) != 0 && + handle_socket_read(socknum) != 0) + close_socket(&sockets[socknum]); + break; + case AUTH_CONNECTION: + if ((pfd[i].revents & (POLLIN|POLLERR)) != 0 && + handle_conn_read(socknum) != 0) { + close_socket(&sockets[socknum]); + break; + } + if ((pfd[i].revents & (POLLOUT|POLLHUP)) != 0 && + handle_conn_write(socknum) != 0) + close_socket(&sockets[socknum]); + break; + default: + break; + } + } +} + +static int +prepare_poll(struct pollfd **pfdp, size_t *npfdp, int *timeoutp) +{ + struct pollfd *pfd = *pfdp; + size_t i, j, npfd = 0; time_t deadline; + /* Count active sockets */ for (i = 0; i < sockets_alloc; i++) { switch (sockets[i].type) { case AUTH_SOCKET: case AUTH_CONNECTION: - n = MAXIMUM(n, sockets[i].fd); + npfd++; break; case AUTH_UNUSED: break; @@ -779,28 +922,23 @@ prepare_select(fd_set **fdrp, fd_set **fdwp, int *fdl, u_int *nallocp, break; } } + if (npfd != *npfdp && + (pfd = recallocarray(pfd, *npfdp, npfd, sizeof(*pfd))) == NULL) + fatal("%s: recallocarray failed", __func__); + *pfdp = pfd; + *npfdp = npfd; - sz = howmany(n+1, NFDBITS) * sizeof(fd_mask); - if (*fdrp == NULL || sz > *nallocp) { - free(*fdrp); - free(*fdwp); - *fdrp = xmalloc(sz); - *fdwp = xmalloc(sz); - *nallocp = sz; - } - if (n < *fdl) - debug("XXX shrink: %d < %d", n, *fdl); - *fdl = n; - memset(*fdrp, 0, sz); - memset(*fdwp, 0, sz); - - for (i = 0; i < sockets_alloc; i++) { + for (i = j = 0; i < sockets_alloc; i++) { switch (sockets[i].type) { case AUTH_SOCKET: case AUTH_CONNECTION: - FD_SET(sockets[i].fd, *fdrp); + pfd[j].fd = sockets[i].fd; + pfd[j].revents = 0; + /* XXX backoff when input buffer full */ + pfd[j].events = POLLIN; if (sshbuf_len(sockets[i].output) > 0) - FD_SET(sockets[i].fd, *fdwp); + pfd[j].events |= POLLOUT; + j++; break; default: break; @@ -811,98 +949,16 @@ prepare_select(fd_set **fdrp, fd_set **fdwp, int *fdl, u_int *nallocp, deadline = (deadline == 0) ? parent_alive_interval : MINIMUM(deadline, parent_alive_interval); if (deadline == 0) { - *tvpp = NULL; + *timeoutp = -1; /* INFTIM */ } else { - tv.tv_sec = deadline; - tv.tv_usec = 0; - *tvpp = &tv; + if (deadline > INT_MAX / 1000) + *timeoutp = INT_MAX / 1000; + else + *timeoutp = deadline * 1000; } return (1); } -static void -after_select(fd_set *readset, fd_set *writeset) -{ - struct sockaddr_un sunaddr; - socklen_t slen; - char buf[1024]; - int len, sock, r; - u_int i, orig_alloc; - uid_t euid; - gid_t egid; - - for (i = 0, orig_alloc = sockets_alloc; i < orig_alloc; i++) - switch (sockets[i].type) { - case AUTH_UNUSED: - break; - case AUTH_SOCKET: - if (FD_ISSET(sockets[i].fd, readset)) { - slen = sizeof(sunaddr); - sock = accept(sockets[i].fd, - (struct sockaddr *)&sunaddr, &slen); - if (sock < 0) { - error("accept from AUTH_SOCKET: %s", - strerror(errno)); - break; - } - if (getpeereid(sock, &euid, &egid) < 0) { - error("getpeereid %d failed: %s", - sock, strerror(errno)); - close(sock); - break; - } - if ((euid != 0) && (getuid() != euid)) { - error("uid mismatch: " - "peer euid %u != uid %u", - (u_int) euid, (u_int) getuid()); - close(sock); - break; - } - new_socket(AUTH_CONNECTION, sock); - } - break; - case AUTH_CONNECTION: - if (sshbuf_len(sockets[i].output) > 0 && - FD_ISSET(sockets[i].fd, writeset)) { - len = write(sockets[i].fd, - sshbuf_ptr(sockets[i].output), - sshbuf_len(sockets[i].output)); - if (len == -1 && (errno == EAGAIN || - errno == EWOULDBLOCK || - errno == EINTR)) - continue; - if (len <= 0) { - close_socket(&sockets[i]); - break; - } - if ((r = sshbuf_consume(sockets[i].output, - len)) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); - } - if (FD_ISSET(sockets[i].fd, readset)) { - len = read(sockets[i].fd, buf, sizeof(buf)); - if (len == -1 && (errno == EAGAIN || - errno == EWOULDBLOCK || - errno == EINTR)) - continue; - if (len <= 0) { - close_socket(&sockets[i]); - break; - } - if ((r = sshbuf_put(sockets[i].input, - buf, len)) != 0) - fatal("%s: buffer error: %s", - __func__, ssh_err(r)); - explicit_bzero(buf, sizeof(buf)); - process_message(&sockets[i]); - } - break; - default: - fatal("Unknown type %d", sockets[i].type); - } -} - static void cleanup_socket(void) { @@ -962,9 +1018,7 @@ main(int ac, char **av) { int c_flag = 0, d_flag = 0, D_flag = 0, k_flag = 0, s_flag = 0; int sock, fd, ch, result, saved_errno; - u_int nalloc; char *shell, *format, *pidstr, *agentsocket = NULL; - fd_set *readsetp = NULL, *writesetp = NULL; #ifdef HAVE_SETRLIMIT struct rlimit rlim; #endif @@ -972,9 +1026,11 @@ main(int ac, char **av) extern char *optarg; pid_t pid; char pidstrbuf[1 + 3 * sizeof pid]; - struct timeval *tvp = NULL; size_t len; mode_t prev_mask; + int timeout = -1; /* INFTIM */ + struct pollfd *pfd = NULL; + size_t npfd = 0; ssh_malloc_init(); /* must be called before any mallocs */ /* Ensure that fds 0, 1 and 2 are open or directed to /dev/null */ @@ -1195,15 +1251,14 @@ skip: signal(SIGINT, (d_flag | D_flag) ? cleanup_handler : SIG_IGN); signal(SIGHUP, cleanup_handler); signal(SIGTERM, cleanup_handler); - nalloc = 0; if (pledge("stdio rpath cpath unix id proc exec", NULL) == -1) fatal("%s: pledge: %s", __progname, strerror(errno)); platform_pledge_agent(); while (1) { - prepare_select(&readsetp, &writesetp, &max_fd, &nalloc, &tvp); - result = select(max_fd + 1, readsetp, writesetp, NULL, tvp); + prepare_poll(&pfd, &npfd, &timeout); + result = poll(pfd, npfd, timeout); saved_errno = errno; if (parent_alive_interval != 0) check_parent_exists(); @@ -1211,9 +1266,9 @@ skip: if (result < 0) { if (saved_errno == EINTR) continue; - fatal("select: %s", strerror(saved_errno)); + fatal("poll: %s", strerror(saved_errno)); } else if (result > 0) - after_select(readsetp, writesetp); + after_poll(pfd, npfd); } /* NOTREACHED */ } diff --git a/ssh-keygen.1 b/ssh-keygen.1 index 66f8321..5f1ec09 100644 --- a/ssh-keygen.1 +++ b/ssh-keygen.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: ssh-keygen.1,v 1.142 2017/06/28 01:09:22 djm Exp $ +.\" $OpenBSD: ssh-keygen.1,v 1.144 2017/07/08 18:32:54 jmc Exp $ .\" .\" Author: Tatu Ylonen .\" Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -35,7 +35,7 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: June 28 2017 $ +.Dd $Mdocdate: July 8 2017 $ .Dt SSH-KEYGEN 1 .Os .Sh NAME @@ -126,6 +126,7 @@ .Op Fl f Ar input_keyfile .Nm ssh-keygen .Fl A +.Op Fl f Ar prefix_path .Nm ssh-keygen .Fl k .Fl f Ar krl_file @@ -224,6 +225,10 @@ For each of the key types (rsa, dsa, ecdsa and ed25519) for which host keys do not exist, generate the host keys with the default key file path, an empty passphrase, default bits for the key type, and default comment. +If +.Fl f +has also been specified, its argument is used as a prefix to the +default path for the resulting host key files. This is used by .Pa /etc/rc to generate new host keys. diff --git a/ssh-keygen.c b/ssh-keygen.c index ea6765e..514cacb 100644 --- a/ssh-keygen.c +++ b/ssh-keygen.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-keygen.c,v 1.305 2017/06/28 01:09:22 djm Exp $ */ +/* $OpenBSD: ssh-keygen.c,v 1.307 2017/07/07 03:53:12 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1994 Tatu Ylonen , Espoo, Finland @@ -41,7 +41,6 @@ #include "xmalloc.h" #include "sshkey.h" -#include "rsa.h" #include "authfile.h" #include "uuencode.h" #include "sshbuf.h" @@ -533,7 +532,7 @@ do_convert_private_ssh2_from_blob(u_char *blob, u_int blen) buffer_get_bignum_bits(b, key->rsa->iqmp); buffer_get_bignum_bits(b, key->rsa->q); buffer_get_bignum_bits(b, key->rsa->p); - if ((r = rsa_generate_additional_parameters(key->rsa)) != 0) + if ((r = ssh_rsa_generate_additional_parameters(key)) != 0) fatal("generate RSA parameters failed: %s", ssh_err(r)); break; } @@ -1003,20 +1002,38 @@ do_gen_all_hostkeys(struct passwd *pw) int first = 0; struct stat st; struct sshkey *private, *public; - char comment[1024]; + char comment[1024], *prv_tmp, *pub_tmp, *prv_file, *pub_file; int i, type, fd, r; FILE *f; for (i = 0; key_types[i].key_type; i++) { - if (stat(key_types[i].path, &st) == 0) - continue; - if (errno != ENOENT) { + public = private = NULL; + prv_tmp = pub_tmp = prv_file = pub_file = NULL; + + xasprintf(&prv_file, "%s%s", + identity_file, key_types[i].path); + + /* Check whether private key exists and is not zero-length */ + if (stat(prv_file, &st) == 0) { + if (st.st_size != 0) + goto next; + } else if (errno != ENOENT) { error("Could not stat %s: %s", key_types[i].path, strerror(errno)); - first = 0; - continue; + goto failnext; } + /* + * Private key doesn't exist or is invalid; proceed with + * key generation. + */ + xasprintf(&prv_tmp, "%s%s.XXXXXXXXXX", + identity_file, key_types[i].path); + xasprintf(&pub_tmp, "%s%s.pub.XXXXXXXXXX", + identity_file, key_types[i].path); + xasprintf(&pub_file, "%s%s.pub", + identity_file, key_types[i].path); + if (first == 0) { first = 1; printf("%s: generating new host keys: ", __progname); @@ -1024,63 +1041,87 @@ do_gen_all_hostkeys(struct passwd *pw) printf("%s ", key_types[i].key_type_display); fflush(stdout); type = sshkey_type_from_name(key_types[i].key_type); - strlcpy(identity_file, key_types[i].path, sizeof(identity_file)); + if ((fd = mkstemp(prv_tmp)) == -1) { + error("Could not save your public key in %s: %s", + prv_tmp, strerror(errno)); + goto failnext; + } + close(fd); /* just using mkstemp() to generate/reserve a name */ bits = 0; type_bits_valid(type, NULL, &bits); if ((r = sshkey_generate(type, bits, &private)) != 0) { error("sshkey_generate failed: %s", ssh_err(r)); - first = 0; - continue; + goto failnext; } if ((r = sshkey_from_private(private, &public)) != 0) fatal("sshkey_from_private failed: %s", ssh_err(r)); snprintf(comment, sizeof comment, "%s@%s", pw->pw_name, hostname); - if ((r = sshkey_save_private(private, identity_file, "", + if ((r = sshkey_save_private(private, prv_tmp, "", comment, use_new_format, new_format_cipher, rounds)) != 0) { error("Saving key \"%s\" failed: %s", - identity_file, ssh_err(r)); - sshkey_free(private); - sshkey_free(public); - first = 0; - continue; + prv_tmp, ssh_err(r)); + goto failnext; } - sshkey_free(private); - strlcat(identity_file, ".pub", sizeof(identity_file)); - fd = open(identity_file, O_WRONLY | O_CREAT | O_TRUNC, 0644); - if (fd == -1) { - error("Could not save your public key in %s", - identity_file); - sshkey_free(public); - first = 0; - continue; + if ((fd = mkstemp(pub_tmp)) == -1) { + error("Could not save your public key in %s: %s", + pub_tmp, strerror(errno)); + goto failnext; } #ifdef WINDOWS /* Windows POSIX adpater does not support fdopen() on open(file)*/ close(fd); - if ((f = fopen(identity_file, "w")) == NULL) { - error("fopen %s failed: %s", identity_file, strerror(errno)); + chmod(pub_tmp, 0644); + if ((f = fopen(pub_tmp, "w")) == NULL) { + error("fopen %s failed: %s", pub_tmp, strerror(errno)); #else /* !WINDOWS */ + (void)fchmod(fd, 0644); f = fdopen(fd, "w"); if (f == NULL) { - error("fdopen %s failed", identity_file); + error("fdopen %s failed: %s", pub_tmp, strerror(errno)); close(fd); #endif /* !WINDOWS */ sshkey_free(public); first = 0; continue; + goto failnext; } if ((r = sshkey_write(public, f)) != 0) { error("write key failed: %s", ssh_err(r)); fclose(f); - sshkey_free(public); - first = 0; - continue; + goto failnext; } fprintf(f, " %s\n", comment); - fclose(f); - sshkey_free(public); + if (ferror(f) != 0) { + error("write key failed: %s", strerror(errno)); + fclose(f); + goto failnext; + } + if (fclose(f) != 0) { + error("key close failed: %s", strerror(errno)); + goto failnext; + } + /* Rename temporary files to their permanent locations. */ + if (rename(pub_tmp, pub_file) != 0) { + error("Unable to move %s into position: %s", + pub_file, strerror(errno)); + goto failnext; + } + if (rename(prv_tmp, prv_file) != 0) { + error("Unable to move %s into position: %s", + key_types[i].path, strerror(errno)); + failnext: + first = 0; + goto next; + } + next: + sshkey_free(private); + sshkey_free(public); + free(prv_tmp); + free(pub_tmp); + free(prv_file); + free(pub_file); } if (first != 0) printf("\n"); diff --git a/ssh-rsa.c b/ssh-rsa.c index e8acc01..f570ae6 100644 --- a/ssh-rsa.c +++ b/ssh-rsa.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh-rsa.c,v 1.61 2017/05/07 23:15:59 djm Exp $ */ +/* $OpenBSD: ssh-rsa.c,v 1.62 2017/07/01 13:50:45 djm Exp $ */ /* * Copyright (c) 2000, 2003 Markus Friedl * @@ -78,6 +78,41 @@ rsa_hash_alg_nid(int type) } } +/* calculate p-1 and q-1 */ +int +ssh_rsa_generate_additional_parameters(struct sshkey *key) +{ + RSA *rsa; + BIGNUM *aux = NULL; + BN_CTX *ctx = NULL; + int r; + + if (key == NULL || key->rsa == NULL || + sshkey_type_plain(key->type) != KEY_RSA) + return SSH_ERR_INVALID_ARGUMENT; + + if ((ctx = BN_CTX_new()) == NULL) + return SSH_ERR_ALLOC_FAIL; + if ((aux = BN_new()) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + rsa = key->rsa; + + if ((BN_sub(aux, rsa->q, BN_value_one()) == 0) || + (BN_mod(rsa->dmq1, rsa->d, aux, ctx) == 0) || + (BN_sub(aux, rsa->p, BN_value_one()) == 0) || + (BN_mod(rsa->dmp1, rsa->d, aux, ctx) == 0)) { + r = SSH_ERR_LIBCRYPTO_ERROR; + goto out; + } + r = 0; + out: + BN_clear_free(aux); + BN_CTX_free(ctx); + return r; +} + /* RSASSA-PKCS1-v1_5 (PKCS #1 v2.0 signature) with SHA1 */ int ssh_rsa_sign(const struct sshkey *key, u_char **sigp, size_t *lenp, diff --git a/ssh.c b/ssh.c index 8bb7e90..62e1278 100644 --- a/ssh.c +++ b/ssh.c @@ -1,4 +1,4 @@ -/* $OpenBSD: ssh.c,v 1.461 2017/05/30 18:58:37 bluhm Exp $ */ +/* $OpenBSD: ssh.c,v 1.462 2017/08/12 06:46:01 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -509,13 +509,13 @@ int main(int ac, char **av) { struct ssh *ssh = NULL; - int i, r, opt, exit_status, use_syslog, direct, config_test = 0; + int i, r, opt, exit_status, use_syslog, direct, timeout_ms; + int config_test = 0, opt_terminated = 0; char *p, *cp, *line, *argv0, buf[PATH_MAX], *host_arg, *logfile; char thishost[NI_MAXHOST], shorthost[NI_MAXHOST], portstr[NI_MAXSERV]; char cname[NI_MAXHOST], uidstr[32], *conn_hash_hex; struct stat st; struct passwd *pw; - int timeout_ms; extern int optind, optreset; extern char *optarg; struct Forward fwd; @@ -917,6 +917,9 @@ main(int ac, char **av) } } + if (optind > 1 && strcmp(av[optind - 1], "--") == 0) + opt_terminated = 1; + ac -= optind; av += optind; @@ -931,7 +934,7 @@ main(int ac, char **av) host = xstrdup(++cp); } else host = xstrdup(*av); - if (ac > 1) { + if (ac > 1 && !opt_terminated) { optind = optreset = 1; goto again; } diff --git a/ssh_config.5 b/ssh_config.5 index 1cbfe04..15ca0b4 100644 --- a/ssh_config.5 +++ b/ssh_config.5 @@ -33,16 +33,13 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: ssh_config.5,v 1.251 2017/06/24 05:35:05 djm Exp $ -.Dd $Mdocdate: June 24 2017 $ +.\" $OpenBSD: ssh_config.5,v 1.253 2017/07/23 23:37:02 djm Exp $ +.Dd $Mdocdate: July 23 2017 $ .Dt SSH_CONFIG 5 .Os .Sh NAME .Nm ssh_config .Nd OpenSSH SSH client configuration files -.Sh SYNOPSIS -.Nm ~/.ssh/config -.Nm /etc/ssh/ssh_config .Sh DESCRIPTION .Xr ssh 1 obtains configuration data from the following sources in @@ -972,7 +969,9 @@ Accepted values are .Cm lowdelay , .Cm throughput , .Cm reliability , -or a numeric value. +a numeric value, or +.Cm none +to use the operating system default. This option may take one or two arguments, separated by whitespace. If one argument is specified, it is used as the packet class unconditionally. If two values are specified, the first is automatically selected for diff --git a/sshconnect.c b/sshconnect.c index 796e89c..32e3983 100644 --- a/sshconnect.c +++ b/sshconnect.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect.c,v 1.282 2017/06/24 05:37:44 djm Exp $ */ +/* $OpenBSD: sshconnect.c,v 1.283 2017/07/01 13:50:45 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -48,7 +48,6 @@ #include "key.h" #include "hostfile.h" #include "ssh.h" -#include "rsa.h" #include "buffer.h" #include "packet.h" #include "uidswap.h" diff --git a/sshconnect2.c b/sshconnect2.c index b8dff51..560697f 100644 --- a/sshconnect2.c +++ b/sshconnect2.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshconnect2.c,v 1.264 2017/06/14 00:31:38 dtucker Exp $ */ +/* $OpenBSD: sshconnect2.c,v 1.265 2017/08/11 04:47:12 djm Exp $ */ /* * Copyright (c) 2000 Markus Friedl. All rights reserved. * Copyright (c) 2008 Damien Miller. All rights reserved. @@ -1042,6 +1042,11 @@ identity_sign(struct identity *id, u_char **sigp, size_t *lenp, /* load the private key from the file */ if ((prv = load_identity_file(id)) == NULL) return SSH_ERR_KEY_NOT_FOUND; + if (id->key != NULL && !sshkey_equal_public(prv, id->key)) { + error("%s: private key %s contents do not match public", + __func__, id->filename); + return SSH_ERR_KEY_NOT_FOUND; + } ret = sshkey_sign(prv, sigp, lenp, data, datalen, key_sign_encode(prv), compat); sshkey_free(prv); diff --git a/sshd.c b/sshd.c index f19275e..1142de1 100644 --- a/sshd.c +++ b/sshd.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshd.c,v 1.490 2017/05/31 08:09:45 markus Exp $ */ +/* $OpenBSD: sshd.c,v 1.491 2017/07/01 13:50:45 djm Exp $ */ /* * Author: Tatu Ylonen * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland @@ -88,7 +88,6 @@ #include "xmalloc.h" #include "ssh.h" #include "ssh2.h" -#include "rsa.h" #include "sshpty.h" #include "packet.h" #include "log.h" diff --git a/sshd_config.5 b/sshd_config.5 index d126298..76e157f 100644 --- a/sshd_config.5 +++ b/sshd_config.5 @@ -33,15 +33,13 @@ .\" (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF .\" THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. .\" -.\" $OpenBSD: sshd_config.5,v 1.248 2017/06/24 07:08:57 djm Exp $ -.Dd $Mdocdate: June 24 2017 $ +.\" $OpenBSD: sshd_config.5,v 1.250 2017/07/23 23:37:02 djm Exp $ +.Dd $Mdocdate: July 23 2017 $ .Dt SSHD_CONFIG 5 .Os .Sh NAME .Nm sshd_config .Nd OpenSSH SSH daemon configuration file -.Sh SYNOPSIS -.Nm /etc/ssh/sshd_config .Sh DESCRIPTION .Xr sshd 8 reads configuration data from @@ -794,7 +792,9 @@ Accepted values are .Cm lowdelay , .Cm throughput , .Cm reliability , -or a numeric value. +a numeric value, or +.Cm none +to use the operating system default. This option may take one or two arguments, separated by whitespace. If one argument is specified, it is used as the packet class unconditionally. If two values are specified, the first is automatically selected for diff --git a/sshkey.c b/sshkey.c index acc6e3f..e91c54f 100644 --- a/sshkey.c +++ b/sshkey.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sshkey.c,v 1.53 2017/06/28 01:09:22 djm Exp $ */ +/* $OpenBSD: sshkey.c,v 1.56 2017/08/12 06:42:52 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. * Copyright (c) 2008 Alexander von Gernler. All rights reserved. @@ -51,7 +51,6 @@ #include "ssherr.h" #include "misc.h" #include "sshbuf.h" -#include "rsa.h" #include "cipher.h" #include "digest.h" #define SSHKEY_INTERNAL @@ -66,7 +65,7 @@ #define KDFNAME "bcrypt" #define AUTH_MAGIC "openssh-key-v1" #define SALT_LEN 16 -#define DEFAULT_CIPHERNAME "aes256-cbc" +#define DEFAULT_CIPHERNAME "aes256-ctr" #define DEFAULT_ROUNDS 16 /* Version identification string for SSH v1 identity files. */ @@ -1987,11 +1986,6 @@ sshkey_from_blob_internal(struct sshbuf *b, struct sshkey **keyp, pk = NULL; break; case KEY_UNSPEC: - if ((key = sshkey_new(type)) == NULL) { - ret = SSH_ERR_ALLOC_FAIL; - goto out; - } - break; default: ret = SSH_ERR_KEY_TYPE_UNKNOWN; goto out; @@ -2667,7 +2661,7 @@ sshkey_private_deserialize(struct sshbuf *buf, struct sshkey **kp) (r = sshbuf_get_bignum2(buf, k->rsa->iqmp)) != 0 || (r = sshbuf_get_bignum2(buf, k->rsa->p)) != 0 || (r = sshbuf_get_bignum2(buf, k->rsa->q)) != 0 || - (r = rsa_generate_additional_parameters(k->rsa)) != 0) + (r = ssh_rsa_generate_additional_parameters(k)) != 0) goto out; if (BN_num_bits(k->rsa->n) < SSH_RSA_MINIMUM_MODULUS_SIZE) { r = SSH_ERR_KEY_LENGTH; @@ -2681,7 +2675,7 @@ sshkey_private_deserialize(struct sshbuf *buf, struct sshkey **kp) (r = sshbuf_get_bignum2(buf, k->rsa->iqmp)) != 0 || (r = sshbuf_get_bignum2(buf, k->rsa->p)) != 0 || (r = sshbuf_get_bignum2(buf, k->rsa->q)) != 0 || - (r = rsa_generate_additional_parameters(k->rsa)) != 0) + (r = ssh_rsa_generate_additional_parameters(k)) != 0) goto out; if (BN_num_bits(k->rsa->n) < SSH_RSA_MINIMUM_MODULUS_SIZE) { r = SSH_ERR_KEY_LENGTH; diff --git a/sshkey.h b/sshkey.h index d8346a5..9093eac 100644 --- a/sshkey.h +++ b/sshkey.h @@ -1,4 +1,4 @@ -/* $OpenBSD: sshkey.h,v 1.20 2017/06/28 01:09:22 djm Exp $ */ +/* $OpenBSD: sshkey.h,v 1.21 2017/07/01 13:50:45 djm Exp $ */ /* * Copyright (c) 2000, 2001 Markus Friedl. All rights reserved. @@ -196,6 +196,9 @@ int sshkey_parse_private_fileblob(struct sshbuf *buffer, int sshkey_parse_private_fileblob_type(struct sshbuf *blob, int type, const char *passphrase, struct sshkey **keyp, char **commentp); +/* XXX should be internal, but used by ssh-keygen */ +int ssh_rsa_generate_additional_parameters(struct sshkey *); + #ifdef SSHKEY_INTERNAL int ssh_rsa_sign(const struct sshkey *key, u_char **sigp, size_t *lenp, const u_char *data, size_t datalen,