diff -ruN openssh-5.5p1.orig/auth2-pubkey.c openssh-5.5p1/auth2-pubkey.c --- openssh-5.5p1.orig/auth2-pubkey.c 2010-03-21 14:51:21.000000000 -0400 +++ openssh-5.5p1/auth2-pubkey.c 2010-07-03 20:23:43.000000000 -0400 @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -178,27 +178,15 @@ /* return 1 if user allows given key */ static int -user_key_allowed2(struct passwd *pw, Key *key, char *file) +user_search_key_in_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); @@ -273,8 +261,6 @@ break; } } - restore_uid(); - fclose(f); key_free(found); if (!found_key) debug2("key not found"); @@ -321,13 +307,191 @@ return ret; } -/* check whether given key is in .ssh/authorized_keys* */ +/* return 1 if user allows given key */ +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); + f = auth_openkeyfile(file, pw, options.strict_modes); + + if (f) { + found_key = user_search_key_in_file (f, file, key, pw); + fclose(f); + } + + restore_uid(); + return found_key; +} + +#ifdef WITH_AUTHORIZED_KEYS_COMMAND + +#define WHITESPACE " \t\r\n" + +/* return 1 if user allows given key */ +static int +user_key_via_command_allowed2(struct passwd *pw, Key *key) +{ + FILE *f; + int found_key = 0; + char *progname = NULL; + char *cp; + struct passwd *runas_pw; + struct stat st; + int childdescriptors[2], i; + pid_t pstat, pid, child; + + if (options.authorized_keys_command == NULL || options.authorized_keys_command[0] != '/') + return -1; + + /* get the run as identity from config */ + runas_pw = (options.authorized_keys_command_runas == NULL)? pw + : getpwnam (options.authorized_keys_command_runas); + if (!runas_pw) { + error("%s: getpwnam(\"%s\"): %s", __func__, + options.authorized_keys_command_runas, strerror(errno)); + return 0; + } + + /* Temporarily use the specified uid. */ + if (runas_pw->pw_uid != 0) + temporarily_use_uid(runas_pw); + + progname = xstrdup(options.authorized_keys_command); + + debug3("%s: checking program '%s'", __func__, progname); + + if (stat (progname, &st) < 0) { + error("%s: stat(\"%s\"): %s", __func__, + progname, strerror(errno)); + goto go_away; + } + + if (st.st_uid != 0 || (st.st_mode & 022) != 0) { + error("bad ownership or modes for AuthorizedKeysCommand \"%s\"", + progname); + goto go_away; + } + + if (!S_ISREG(st.st_mode)) { + error("AuthorizedKeysCommand \"%s\" is not a regular file", + progname); + goto go_away; + } + + /* + * Descend the path, checking that each component is a + * root-owned directory with strict permissions. + */ + do { + if ((cp = strrchr(progname, '/')) == NULL) + break; + else + *cp = '\0'; + + debug3("%s: checking component '%s'", __func__, (*progname == '\0' ? "/" : progname)); + + if (stat((*progname == '\0' ? "/" : progname), &st) != 0) { + error("%s: stat(\"%s\"): %s", __func__, + progname, strerror(errno)); + goto go_away; + } + if (st.st_uid != 0 || (st.st_mode & 022) != 0) { + error("bad ownership or modes for AuthorizedKeysCommand path component \"%s\"", + progname); + goto go_away; + } + if (!S_ISDIR(st.st_mode)) { + error("AuthorizedKeysCommand path component \"%s\" is not a directory", + progname); + goto go_away; + } + } while (1); + + /* open the pipe and read the keys */ + if (pipe(childdescriptors)) { + error("failed to pipe(2) for AuthorizedKeysCommand: %s", + strerror(errno)); + goto go_away; + } + + child = fork(); + if (child == -1) { + error("failed to fork(2) for AuthorizedKeysCommand: %s", + strerror(errno)); + goto go_away; + } else if (child == 0) { + /* we're in the child process here -- we should never return from this block. */ + /* permanently drop privs in child process */ + if (runas_pw->pw_uid != 0) { + restore_uid(); + permanently_set_uid(runas_pw); + } + + close(childdescriptors[0]); + /* put the write end of the pipe on stdout (FD 1) */ + if (dup2(childdescriptors[1], 1) == -1) { + error("failed to dup2(2) from AuthorizedKeysCommand: %s", + strerror(errno)); + _exit(127); + } + + debug3("about to execl() AuthorizedKeysCommand: \"%s\" \"%s\"", options.authorized_keys_command, pw->pw_name); + /* see session.c:child_close_fds() */ + for (i = 3; i < 64; ++i) { + close(i); + } + + execl(options.authorized_keys_command, options.authorized_keys_command, pw->pw_name, NULL); + + /* if we got here, it didn't work */ + error("failed to execl AuthorizedKeysCommand: %s", strerror(errno)); /* this won't work because we closed the fds above */ + _exit(127); + } + + close(childdescriptors[1]); + f = fdopen(childdescriptors[0], "r"); + if (!f) { + error("%s: could not buffer FDs from AuthorizedKeysCommand (\"%s\", \"r\"): %s", __func__, + options.authorized_keys_command, strerror (errno)); + goto go_away; + } + + found_key = user_search_key_in_file (f, options.authorized_keys_command, key, pw); + fclose (f); + do { + pid = waitpid(child, &pstat, 0); + } while (pid == -1 && errno == EINTR); + + /* what about the return value from the child process? */ +go_away: + if (progname) + xfree (progname); + + if (runas_pw->pw_uid != 0) + restore_uid(); + return found_key; +} +#endif + +/* check whether given key is in 0) + return success; +#endif + if (auth_key_is_revoked(key)) return 0; if (key_is_cert(key) && auth_key_is_revoked(key->cert->signature_key)) diff -ruN openssh-5.5p1.orig/configure.ac openssh-5.5p1/configure.ac --- openssh-5.5p1.orig/configure.ac 2010-04-10 08:58:01.000000000 -0400 +++ openssh-5.5p1/configure.ac 2010-07-03 19:57:42.000000000 -0400 @@ -1346,6 +1346,18 @@ esac ] ) +# Check whether user wants AuthorizedKeysCommand support +AKC_MSG="no" +AC_ARG_WITH(authorized-keys-command, + [ --with-authorized-keys-command Enable AuthorizedKeysCommand support], + [ + if test "x$withval" != "xno" ; then + AC_DEFINE([WITH_AUTHORIZED_KEYS_COMMAND], 1, [Enable AuthorizedKeysCommand support]) + AKC_MSG="yes" + fi + ] +) + dnl Checks for library functions. Please keep in alphabetical order AC_CHECK_FUNCS( \ arc4random \ @@ -4181,6 +4193,7 @@ echo " Smartcard support: $SCARD_MSG" echo " S/KEY support: $SKEY_MSG" echo " TCP Wrappers support: $TCPW_MSG" +echo " AuthorizedKeysCommand support: $AKC_MSG" echo " MD5 password support: $MD5_MSG" echo " libedit support: $LIBEDIT_MSG" echo " Solaris process contract support: $SPC_MSG" diff -ruN openssh-5.5p1.orig/servconf.c openssh-5.5p1/servconf.c --- openssh-5.5p1.orig/servconf.c 2010-03-25 19:40:04.000000000 -0400 +++ openssh-5.5p1/servconf.c 2010-07-03 19:59:07.000000000 -0400 @@ -128,6 +128,8 @@ options->num_permitted_opens = -1; options->adm_forced_command = NULL; options->chroot_directory = NULL; + options->authorized_keys_command = NULL; + options->authorized_keys_command_runas = NULL; options->zero_knowledge_password_authentication = -1; options->revoked_keys_file = NULL; options->trusted_user_ca_keys = NULL; @@ -311,6 +313,7 @@ sUsePrivilegeSeparation, sAllowAgentForwarding, sZeroKnowledgePasswordAuthentication, sHostCertificate, sRevokedKeys, sTrustedUserCAKeys, + sAuthorizedKeysCommand, sAuthorizedKeysCommandRunAs, sDeprecated, sUnsupported } ServerOpCodes; @@ -432,6 +435,13 @@ { "hostcertificate", sHostCertificate, SSHCFG_GLOBAL }, { "revokedkeys", sRevokedKeys, SSHCFG_ALL }, { "trustedusercakeys", sTrustedUserCAKeys, SSHCFG_ALL }, +#ifdef WITH_AUTHORIZED_KEYS_COMMAND + { "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL }, + { "authorizedkeyscommandrunas", sAuthorizedKeysCommandRunAs, SSHCFG_ALL }, +#else + { "authorizedkeyscommand", sUnsupported, SSHCFG_ALL }, + { "authorizedkeyscommandrunas", sUnsupported, SSHCFG_ALL }, +#endif { NULL, sBadOption, 0 } }; @@ -1345,6 +1355,20 @@ charptr = &options->revoked_keys_file; goto parse_filename; + case sAuthorizedKeysCommand: + len = strspn(cp, WHITESPACE); + if (*activep && options->authorized_keys_command == NULL) + options->authorized_keys_command = xstrdup(cp + len); + return 0; + + case sAuthorizedKeysCommandRunAs: + charptr = &options->authorized_keys_command_runas; + + arg = strdelim(&cp); + if (*activep && *charptr == NULL) + *charptr = xstrdup(arg); + break; + case sDeprecated: logit("%s line %d: Deprecated option %s", filename, linenum, arg); @@ -1438,6 +1462,8 @@ M_CP_INTOPT(gss_authentication); M_CP_INTOPT(rsa_authentication); M_CP_INTOPT(pubkey_authentication); + M_CP_STROPT(authorized_keys_command); + M_CP_STROPT(authorized_keys_command_runas); M_CP_INTOPT(kerberos_authentication); M_CP_INTOPT(hostbased_authentication); M_CP_INTOPT(kbd_interactive_authentication); @@ -1682,6 +1708,8 @@ dump_cfg_string(sChrootDirectory, o->chroot_directory); dump_cfg_string(sTrustedUserCAKeys, o->trusted_user_ca_keys); dump_cfg_string(sRevokedKeys, o->revoked_keys_file); + dump_cfg_string(sAuthorizedKeysCommand, o->authorized_keys_command); + dump_cfg_string(sAuthorizedKeysCommandRunAs, o->authorized_keys_command_runas); /* string arguments requiring a lookup */ dump_cfg_string(sLogLevel, log_level_name(o->log_level)); diff -ruN openssh-5.5p1.orig/servconf.h openssh-5.5p1/servconf.h --- openssh-5.5p1.orig/servconf.h 2010-03-04 05:53:35.000000000 -0500 +++ openssh-5.5p1/servconf.h 2010-07-03 19:57:42.000000000 -0400 @@ -156,6 +156,8 @@ char *chroot_directory; char *revoked_keys_file; char *trusted_user_ca_keys; + char *authorized_keys_command; + char *authorized_keys_command_runas; } ServerOptions; void initialize_server_options(ServerOptions *); diff -ruN openssh-5.5p1.orig/sshd_config openssh-5.5p1/sshd_config --- openssh-5.5p1.orig/sshd_config 2009-10-11 06:51:09.000000000 -0400 +++ openssh-5.5p1/sshd_config 2010-07-03 19:57:42.000000000 -0400 @@ -44,6 +44,8 @@ #RSAAuthentication yes #PubkeyAuthentication yes #AuthorizedKeysFile .ssh/authorized_keys +#AuthorizedKeysCommand none +#AuthorizedKeysCommandRunAs nobody # For this to work you will also need host keys in /etc/ssh/ssh_known_hosts #RhostsRSAAuthentication no diff -ruN openssh-5.5p1.orig/sshd_config.0 openssh-5.5p1/sshd_config.0 --- openssh-5.5p1.orig/sshd_config.0 2010-04-15 20:17:12.000000000 -0400 +++ openssh-5.5p1/sshd_config.0 2010-07-03 19:57:42.000000000 -0400 @@ -352,7 +352,8 @@ KbdInteractiveAuthentication, KerberosAuthentication, MaxAuthTries, MaxSessions, PasswordAuthentication, PermitEmptyPasswords, PermitOpen, PermitRootLogin, - PubkeyAuthentication, RhostsRSAAuthentication, RSAAuthentication, + PubkeyAuthentication, AuthorizedKeysCommand, AuthorizedKeysCommandRunAs, + RhostsRSAAuthentication, RSAAuthentication, X11DisplayOffset, X11Forwarding and X11UseLocalHost. MaxAuthTries @@ -467,6 +468,23 @@ this file is not readable, then public key authentication will be refused for all users. + 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. + RhostsRSAAuthentication Specifies whether rhosts or /etc/hosts.equiv authentication to- gether with successful RSA host authentication is allowed. The diff -ruN openssh-5.5p1.orig/sshd_config.5 openssh-5.5p1/sshd_config.5 --- openssh-5.5p1.orig/sshd_config.5 2010-03-04 18:41:45.000000000 -0500 +++ openssh-5.5p1/sshd_config.5 2010-07-03 19:57:42.000000000 -0400 @@ -618,6 +618,9 @@ .Cm KerberosAuthentication , .Cm MaxAuthTries , .Cm MaxSessions , +.Cm PubkeyAuthentication , +.Cm AuthorizedKeysCommand , +.Cm AuthorizedKeysCommandRunAs , .Cm PasswordAuthentication , .Cm PermitEmptyPasswords , .Cm PermitOpen , @@ -819,6 +822,20 @@ Keys listed in this file will be refused for public key authentication. Note that if this file is not readable, then public key authentication will be refused for all users. +.It Cm 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. +.It Cm AuthorizedKeysCommandRunAs +Specifies the user under whose account the AuthorizedKeysCommand is run. Empty +string (the default value) means the user being authorized is used. +.Dq .It Cm RhostsRSAAuthentication Specifies whether rhosts or /etc/hosts.equiv authentication together with successful RSA host authentication is allowed.