diff -up openssh-6.1p1/auth.c.akc openssh-6.1p1/auth.c --- openssh-6.1p1/auth.c.akc 2012-11-02 14:00:49.181077248 +0100 +++ openssh-6.1p1/auth.c 2012-11-02 14:00:49.253077860 +0100 @@ -413,39 +413,41 @@ check_key_in_hostfiles(struct passwd *pw /* - * Check a given file for security. This is defined as all components + * 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 an open file descriptor, the file name, a uid and and + * Takes an the 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 */ -static int -secure_filename(FILE *f, const char *file, struct passwd *pw, - char *err, size_t errlen) +int +auth_secure_path(const char *name, struct stat *stp, const char *pw_dir, + uid_t uid, char *err, size_t errlen) { - uid_t uid = pw->pw_uid; char buf[MAXPATHLEN], homedir[MAXPATHLEN]; char *cp; int comparehome = 0; struct stat st; - if (realpath(file, buf) == NULL) { - snprintf(err, errlen, "realpath %s failed: %s", file, + if (realpath(name, buf) == NULL) { + snprintf(err, errlen, "realpath %s failed: %s", name, strerror(errno)); return -1; } - if (realpath(pw->pw_dir, homedir) != NULL) + if (pw_dir != NULL && realpath(pw_dir, homedir) != NULL) comparehome = 1; - /* check the open file to avoid races */ - if (fstat(fileno(f), &st) < 0 || - (st.st_uid != 0 && st.st_uid != uid) || - (st.st_mode & 022) != 0) { + if (!S_ISREG(stp->st_mode)) { + snprintf(err, errlen, "%s is not a regular file", buf); + return -1; + } + if ((stp->st_uid != 0 && stp->st_uid != uid) || + (stp->st_mode & 022) != 0) { snprintf(err, errlen, "bad ownership or modes for file %s", buf); return -1; @@ -481,6 +483,31 @@ secure_filename(FILE *f, const char *fil 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) +{ + uid_t uid = pw->pw_uid; + char buf[MAXPATHLEN], homedir[MAXPATHLEN]; + char *cp; + int comparehome = 0; + struct stat st; + + /* check the open file to avoid races */ + if (fstat(fileno(f), &st) < 0) { + snprintf(err, errlen, "cannot stat file %s: %s", + buf, 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) diff -up openssh-6.1p1/auth.h.akc openssh-6.1p1/auth.h --- openssh-6.1p1/auth.h.akc 2012-11-02 14:00:49.239077742 +0100 +++ openssh-6.1p1/auth.h 2012-11-02 14:00:49.253077860 +0100 @@ -123,6 +123,10 @@ int auth_rhosts_rsa_key_allowed(struct int hostbased_key_allowed(struct passwd *, const char *, char *, Key *); int user_key_allowed(struct passwd *, Key *); +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 -up openssh-6.1p1/auth2-pubkey.c.akc openssh-6.1p1/auth2-pubkey.c --- openssh-6.1p1/auth2-pubkey.c.akc 2012-11-02 14:00:49.241077758 +0100 +++ openssh-6.1p1/auth2-pubkey.c 2012-11-02 14:00:49.252077852 +0100 @@ -27,9 +27,13 @@ #include #include +#include +#include #include +#include #include +#include #include #include #include @@ -260,7 +264,7 @@ match_principals_file(char *file, struct if (strcmp(cp, cert->principals[i]) == 0) { debug3("matched principal \"%.100s\" " "from file \"%s\" on line %lu", - cert->principals[i], file, linenum); + cert->principals[i], file, linenum); if (auth_parse_options(pw, line_opts, file, linenum) != 1) continue; @@ -273,31 +277,22 @@ match_principals_file(char *file, struct fclose(f); restore_uid(); return 0; -} +} -/* return 1 if user allows given key */ +/* + * Checks whether key is allowed in authorized_keys-format file, + * returns 1 if the key is allowed or 0 otherwise. + */ static int -user_key_allowed2(struct passwd *pw, Key *key, char *file) +check_authkeys_file(FILE *f, char *file, Key* key, struct passwd *pw) { char line[SSH_MAX_PUBKEY_BYTES]; const char *reason; int found_key = 0; - FILE *f; u_long linenum = 0; Key *found; char *fp; - /* Temporarily use the user's uid. */ - temporarily_use_uid(pw); - - debug("trying public key file %s", file); - f = auth_openkeyfile(file, pw, options.strict_modes); - - if (!f) { - restore_uid(); - return 0; - } - found_key = 0; found = key_new(key_is_cert(key) ? KEY_UNSPEC : key->type); @@ -390,8 +385,6 @@ user_key_allowed2(struct passwd *pw, Key break; } } - restore_uid(); - fclose(f); key_free(found); if (!found_key) debug2("key not found"); @@ -453,7 +446,173 @@ user_cert_trusted_ca(struct passwd *pw, return ret; } -/* check whether given key is in .ssh/authorized_keys* */ +/* + * Checks whether key is allowed in file. + * returns 1 if the key is allowed or 0 otherwise. + */ +static int +user_key_allowed2(struct passwd *pw, Key *key, char *file) +{ + FILE *f; + int found_key = 0; + + /* Temporarily use the user's uid. */ + temporarily_use_uid(pw); + + debug("trying public key file %s", file); + if ((f = auth_openkeyfile(file, pw, options.strict_modes)) != NULL) { + found_key = check_authkeys_file(f, file, key, pw); + fclose(f); + } + + restore_uid(); + return found_key; +} + +/* + * Checks whether key is allowed in output of command. + * returns 1 if the key is allowed or 0 otherwise. + */ +static int +user_key_command_allowed2(struct passwd *user_pw, Key *key) +{ + FILE *f; + int ok, found_key = 0; + struct passwd *pw; + struct stat st; + int status, devnull, p[2], i; + pid_t pid; + char errmsg[512]; + + if (options.authorized_keys_command == NULL || + options.authorized_keys_command[0] != '/') + return 0; + + /* If no user specified to run commands the default to target user */ + if (options.authorized_keys_command_user == NULL) + pw = user_pw; + else { + pw = getpwnam(options.authorized_keys_command_user); + if (pw == NULL) { + error("AuthorizedKeyCommandUser \"%s\" not found: %s", + options.authorized_keys_command, strerror(errno)); + return 0; + } + } + + temporarily_use_uid(pw); + if (stat(options.authorized_keys_command, &st) < 0) { + error("Could not stat AuthorizedKeysCommand \"%s\": %s", + options.authorized_keys_command, strerror(errno)); + goto out; + } + + if (auth_secure_path(options.authorized_keys_command, &st, NULL, 0, + errmsg, sizeof(errmsg)) != 0) { + error("Unsafe AuthorizedKeysCommand: %s", errmsg); + goto out; + } + + /* open the pipe and read the keys */ + if (pipe(p) != 0) { + error("%s: pipe: %s", __func__, strerror(errno)); + goto out; + } + + debug3("Running AuthorizedKeysCommand: \"%s\" as \"%s\"", + options.authorized_keys_command, pw->pw_name); + + /* + * 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", __func__, strerror(errno)); + close(p[0]); + close(p[1]); + return 0; + case 0: /* child */ + for (i = 0; i < NSIG; i++) + signal(i, SIG_DFL); + + /* Don't use permanently_set_uid() here to avoid fatal() */ + if (setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid) != 0) { + error("setresgid %u: %s", (u_int)pw->pw_gid, + strerror(errno)); + _exit(1); + } + if (setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) != 0) { + error("setresuid %u: %s", (u_int)pw->pw_uid, + strerror(errno)); + _exit(1); + } + + close(p[0]); + if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) { + error("%s: open %s: %s", __func__, _PATH_DEVNULL, + strerror(errno)); + _exit(1); + } + if (dup2(devnull, STDIN_FILENO) == -1 || + dup2(p[1], STDOUT_FILENO) == -1 || + dup2(devnull, STDERR_FILENO) == -1) { + error("%s: dup2: %s", __func__, strerror(errno)); + _exit(1); + } + closefrom(STDERR_FILENO + 1); + + execl(options.authorized_keys_command, + options.authorized_keys_command, pw->pw_name, NULL); + + error("AuthorizedKeysCommand %s exec failed: %s", + options.authorized_keys_command, strerror(errno)); + _exit(127); + default: /* parent */ + break; + } + + temporarily_use_uid(pw); + + close(p[1]); + if ((f = fdopen(p[0], "r")) == NULL) { + error("%s: fdopen: %s", __func__, strerror(errno)); + close(p[0]); + /* Don't leave zombie child */ + while (waitpid(pid, NULL, 0) == -1 && errno == EINTR) + ; + goto out; + } + ok = check_authkeys_file(f, options.authorized_keys_command, key, pw); + fclose(f); + + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + error("%s: waitpid: %s", __func__, strerror(errno)); + goto out; + } + } + if (WIFSIGNALED(status)) { + error("AuthorizedKeysCommand %s exited on signal %d", + options.authorized_keys_command, WTERMSIG(status)); + goto out; + } else if (WEXITSTATUS(status) != 0) { + error("AuthorizedKeysCommand %s returned status %d", + options.authorized_keys_command, WEXITSTATUS(status)); + goto out; + } + found_key = ok; + out: + restore_uid(); + + return found_key; +} + +/* + * Check whether key authenticates and authorises the user. + */ int user_key_allowed(struct passwd *pw, Key *key) { @@ -469,6 +628,10 @@ user_key_allowed(struct passwd *pw, Key if (success) return success; + success = user_key_command_allowed2(pw, key); + if (success > 0) + return success; + for (i = 0; !success && i < options.num_authkeys_files; i++) { file = expand_authorized_keys( options.authorized_keys_files[i], pw); diff -up openssh-6.1p1/servconf.c.akc openssh-6.1p1/servconf.c --- openssh-6.1p1/servconf.c.akc 2012-11-02 14:00:49.186077290 +0100 +++ openssh-6.1p1/servconf.c 2012-11-02 14:26:32.086138017 +0100 @@ -139,6 +139,8 @@ initialize_server_options(ServerOptions options->num_permitted_opens = -1; options->adm_forced_command = NULL; options->chroot_directory = NULL; + options->authorized_keys_command = NULL; + options->authorized_keys_command_user = NULL; options->zero_knowledge_password_authentication = -1; options->revoked_keys_file = NULL; options->trusted_user_ca_keys = NULL; @@ -334,6 +336,7 @@ typedef enum { sZeroKnowledgePasswordAuthentication, sHostCertificate, sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile, sKexAlgorithms, sIPQoS, sVersionAddendum, + sAuthorizedKeysCommand, sAuthorizedKeysCommandUser, sDeprecated, sUnsupported } ServerOpCodes; @@ -460,6 +463,9 @@ static struct { { "requiredauthentications1", sRequiredAuthentications1, SSHCFG_ALL }, { "requiredauthentications2", sRequiredAuthentications2, SSHCFG_ALL }, { "ipqos", sIPQoS, SSHCFG_ALL }, + { "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL }, + { "authorizedkeyscommandrunas", sAuthorizedKeysCommandUser, SSHCFG_ALL }, + { "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL }, { "versionaddendum", sVersionAddendum, SSHCFG_GLOBAL }, { NULL, sBadOption, 0 } }; @@ -1532,6 +1538,26 @@ process_server_config_line(ServerOptions } return 0; + case sAuthorizedKeysCommand: + len = strspn(cp, WHITESPACE); + if (*activep && options->authorized_keys_command == NULL) { + options->authorized_keys_command = xstrdup(cp + len); + if (*options->authorized_keys_command != '/') { + fatal("%.200s line %d: AuthorizedKeysCommand " + "must be an absolute path", + filename, linenum); + } + } + return 0; + + case sAuthorizedKeysCommandUser: + charptr = &options->authorized_keys_command_user; + + arg = strdelim(&cp); + if (*activep && *charptr == NULL) + *charptr = xstrdup(arg); + break; + case sDeprecated: logit("%s line %d: Deprecated option %s", filename, linenum, arg); @@ -1682,6 +1708,8 @@ copy_set_server_options(ServerOptions *d M_CP_INTOPT(hostbased_uses_name_from_packet_only); M_CP_INTOPT(kbd_interactive_authentication); M_CP_INTOPT(zero_knowledge_password_authentication); + M_CP_STROPT(authorized_keys_command); + M_CP_STROPT(authorized_keys_command_user); M_CP_INTOPT(permit_root_login); M_CP_INTOPT(permit_empty_passwd); @@ -1942,6 +1970,8 @@ dump_config(ServerOptions *o) dump_cfg_string(sAuthorizedPrincipalsFile, o->authorized_principals_file); dump_cfg_string(sVersionAddendum, o->version_addendum); + dump_cfg_string(sAuthorizedKeysCommand, o->authorized_keys_command); + dump_cfg_string(sAuthorizedKeysCommandUser, o->authorized_keys_command_user); /* string arguments requiring a lookup */ dump_cfg_string(sLogLevel, log_level_name(o->log_level)); diff -up openssh-6.1p1/servconf.h.akc openssh-6.1p1/servconf.h --- openssh-6.1p1/servconf.h.akc 2012-11-02 14:00:49.186077290 +0100 +++ openssh-6.1p1/servconf.h 2012-11-02 14:00:49.254077869 +0100 @@ -169,6 +169,8 @@ typedef struct { char *revoked_keys_file; char *trusted_user_ca_keys; char *authorized_principals_file; + char *authorized_keys_command; + char *authorized_keys_command_user; char *version_addendum; /* Appended to SSH banner */ } ServerOptions; diff -up openssh-6.1p1/sshd.c.akc openssh-6.1p1/sshd.c --- openssh-6.1p1/sshd.c.akc 2012-11-02 14:00:49.249077826 +0100 +++ openssh-6.1p1/sshd.c 2012-11-02 14:00:49.254077869 +0100 @@ -366,9 +366,20 @@ main_sigchld_handler(int sig) static void grace_alarm_handler(int sig) { + pid_t pgid; + if (use_privsep && pmonitor != NULL && pmonitor->m_pid > 0) kill(pmonitor->m_pid, SIGALRM); + /* + * Try to kill any processes that we have spawned, E.g. authorized + * keys command helpers. + */ + if ((pgid = getpgid(0)) == getpid()) { + signal(SIGTERM, SIG_IGN); + killpg(pgid, SIGTERM); + } + /* Log error and exit. */ sigdie("Timeout before authentication for %s", get_remote_ipaddr()); } diff -up openssh-6.1p1/sshd_config.akc openssh-6.1p1/sshd_config --- openssh-6.1p1/sshd_config.akc 2012-07-31 04:21:34.000000000 +0200 +++ openssh-6.1p1/sshd_config 2012-11-02 14:00:49.255077878 +0100 @@ -49,6 +49,9 @@ # but this is overridden so installations will only check .ssh/authorized_keys AuthorizedKeysFile .ssh/authorized_keys +#AuthorizedKeysCommand none +#AuthorizedKeysCommandUser nobody + #AuthorizedPrincipalsFile none # For this to work you will also need host keys in /etc/ssh/ssh_known_hosts diff -up openssh-6.1p1/sshd_config.0.akc openssh-6.1p1/sshd_config.0 --- openssh-6.1p1/sshd_config.0.akc 2012-08-29 02:53:04.000000000 +0200 +++ openssh-6.1p1/sshd_config.0 2012-11-02 14:00:49.255077878 +0100 @@ -71,6 +71,23 @@ DESCRIPTION See PATTERNS in ssh_config(5) for more information on patterns. + AuthorizedKeysCommand + + Specifies a program to be used for lookup of the user's + public keys. The program will be invoked with its first + argument the name of the user being authorized, and should produce + on standard output AuthorizedKeys lines (see AUTHORIZED_KEYS + in sshd(8)). By default (or when set to the empty string) there is no + AuthorizedKeysCommand run. If the AuthorizedKeysCommand does not successfully + authorize the user, authorization falls through to the + AuthorizedKeysFile. Note that this option has an effect + only with PubkeyAuthentication turned on. + + AuthorizedKeysCommandRunAs + Specifies the user under whose account the AuthorizedKeysCommand is run. + Empty string (the default value) means the user being authorized + is used. + AuthorizedKeysFile Specifies the file that contains the public keys that can be used for user authentication. The format is described in the @@ -402,7 +419,8 @@ DESCRIPTION Only a subset of keywords may be used on the lines following a Match keyword. Available keywords are AcceptEnv, AllowAgentForwarding, AllowGroups, AllowTcpForwarding, - AllowUsers, AuthorizedKeysFile, AuthorizedPrincipalsFile, Banner, + AllowUsers, AuthorizedKeysFile, AuthorizedKeysCommand, + AuthorizedKeysCommandRunAs, AuthorizedPrincipalsFile, Banner, ChrootDirectory, DenyGroups, DenyUsers, ForceCommand, GatewayPorts, GSSAPIAuthentication, HostbasedAuthentication, HostbasedUsesNameFromPacketOnly, KbdInteractiveAuthentication, diff -up openssh-6.1p1/sshd_config.5.akc openssh-6.1p1/sshd_config.5 --- openssh-6.1p1/sshd_config.5.akc 2012-11-02 14:00:49.187077299 +0100 +++ openssh-6.1p1/sshd_config.5 2012-11-02 14:00:49.255077878 +0100 @@ -151,6 +151,20 @@ See in .Xr ssh_config 5 for more information on patterns. +.It Cm AuthorizedKeysCommand +Specifies a program to be used for lookup of the user's public keys. +The program will be invoked with a single argument of the username +being authenticated, and should produce on standard output zero or +more lines of authorized_keys output (see AUTHORIZED_KEYS in +.Xr sshd 8 ) +If a key supplied by AuthorizedKeysCommand does not successfully authenticate +and authorize the user then public key authentication continues using the usual +.Cm AuthorizedKeysFile +files. +By default, no AuthorizedKeysCommand is run. +.It Cm AuthorizedKeysCommandUser +Specifies the user under whose account the AuthorizedKeysCommand is run. +The default is the user being authenticated. .It Cm AuthorizedKeysFile Specifies the file that contains the public keys that can be used for user authentication. @@ -712,6 +726,8 @@ Available keywords are .Cm AllowTcpForwarding , .Cm AllowUsers , .Cm AuthorizedKeysFile , +.Cm AuthorizedKeysCommand , +.Cm AuthorizedKeysCommandUser , .Cm AuthorizedPrincipalsFile , .Cm Banner , .Cm ChrootDirectory , @@ -726,6 +742,7 @@ Available keywords are .Cm KerberosAuthentication , .Cm MaxAuthTries , .Cm MaxSessions , +.Cm PubkeyAuthentication , .Cm PasswordAuthentication , .Cm PermitEmptyPasswords , .Cm PermitOpen ,