Blob Blame History Raw
diff -up openssh-6.1p1/auth2-pubkey.c.akc openssh-6.1p1/auth2-pubkey.c
--- openssh-6.1p1/auth2-pubkey.c.akc	2013-02-14 17:46:45.259546968 +0100
+++ openssh-6.1p1/auth2-pubkey.c	2013-02-14 17:48:19.072137541 +0100
@@ -27,9 +27,13 @@
 
 #include <sys/types.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 
+#include <errno.h>
 #include <fcntl.h>
+#include <paths.h>
 #include <pwd.h>
+#include <signal.h>
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
@@ -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,180 @@ 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 *username, errmsg[512];
+
+	if (options.authorized_keys_command == NULL ||
+	    options.authorized_keys_command[0] != '/')
+		return 0;
+
+	if (options.authorized_keys_command_user == NULL) {
+		error("No user for AuthorizedKeysCommand specified, skipping");
+		return 0;
+	}
+
+	username = percent_expand(options.authorized_keys_command_user,
+	    "u", user_pw->pw_name, (char *)NULL);
+	pw = getpwnam(username);
+	if (pw == NULL) {
+		error("AuthorizedKeyCommandUser \"%s\" not found: %s",
+		    username, strerror(errno));
+		free(username);
+		return 0;
+	}
+	free(username);
+
+	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;
+	}
+
+	if (pipe(p) != 0) {
+		error("%s: pipe: %s", __func__, strerror(errno));
+		goto out;
+	}
+
+	debug3("Running AuthorizedKeysCommand: \"%s %s\" as \"%s\"",
+	    options.authorized_keys_command, user_pw->pw_name, 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);
+
+		if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
+			error("%s: open %s: %s", __func__, _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", __func__, 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("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);
+		}
+		/* stdin is pointed to /dev/null at this point */
+		if (dup2(STDIN_FILENO, STDERR_FILENO) == -1) {
+			error("%s: dup2: %s", __func__, strerror(errno));
+			_exit(1);
+		}
+
+		execl(options.authorized_keys_command,
+		    options.authorized_keys_command, user_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 */
+		kill(pid, SIGTERM);
+		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,9 +635,17 @@ 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++) {
+
+		if (strcasecmp(options.authorized_keys_files[i], "none") == 0)
+			continue;
 		file = expand_authorized_keys(
 		    options.authorized_keys_files[i], pw);
+
 		success = user_key_allowed2(pw, key, file);
 		xfree(file);
 	}
diff -up openssh-6.1p1/auth.c.akc openssh-6.1p1/auth.c
--- openssh-6.1p1/auth.c.akc	2013-02-14 17:46:45.189547274 +0100
+++ openssh-6.1p1/auth.c	2013-02-14 17:46:45.273546907 +0100
@@ -415,39 +415,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;
@@ -483,6 +485,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	2013-02-14 17:46:45.259546968 +0100
+++ openssh-6.1p1/auth.h	2013-02-14 17:46:45.274546903 +0100
@@ -125,6 +125,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/servconf.c.akc openssh-6.1p1/servconf.c
--- openssh-6.1p1/servconf.c.akc	2013-02-14 17:46:45.193547257 +0100
+++ openssh-6.1p1/servconf.c	2013-02-14 17:46:45.274546903 +0100
@@ -137,6 +137,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;
@@ -331,6 +333,7 @@ typedef enum {
 	sZeroKnowledgePasswordAuthentication, sHostCertificate,
 	sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile,
 	sKexAlgorithms, sIPQoS, sVersionAddendum,
+	sAuthorizedKeysCommand, sAuthorizedKeysCommandUser,
 	sAuthenticationMethods,
 	sDeprecated, sUnsupported
 } ServerOpCodes;
@@ -457,6 +460,9 @@ static struct {
 	{ "kexalgorithms", sKexAlgorithms, SSHCFG_GLOBAL },
 	{ "ipqos", sIPQoS, SSHCFG_ALL },
 	{ "versionaddendum", sVersionAddendum, SSHCFG_GLOBAL },
+	{ "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL },
+	{ "authorizedkeyscommandrunas", sAuthorizedKeysCommandUser, SSHCFG_ALL },
+	{ "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL },
 	{ "authenticationmethods", sAuthenticationMethods, SSHCFG_ALL },
 	{ NULL, sBadOption, 0 }
 };
@@ -1520,6 +1526,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);
@@ -1670,6 +1696,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);
 
@@ -1930,6 +1958,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	2013-02-14 17:46:45.194547252 +0100
+++ openssh-6.1p1/servconf.h	2013-02-14 17:46:45.275546898 +0100
@@ -167,6 +167,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 */
 
diff -up openssh-6.1p1/sshd.c.akc openssh-6.1p1/sshd.c
--- openssh-6.1p1/sshd.c.akc	2013-02-14 17:46:45.270546920 +0100
+++ openssh-6.1p1/sshd.c	2013-02-14 17:46:45.276546894 +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.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	2013-02-14 17:46:45.276546894 +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	2013-02-14 17:46:45.195547248 +0100
+++ openssh-6.1p1/sshd_config.5	2013-02-14 17:46:45.277546890 +0100
@@ -173,6 +173,20 @@ Note that each authentication method lis
 in the configuration.
 The default is not to require multiple authentication; successful completion
 of a single authentication method is sufficient.
+.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.
@@ -734,6 +748,8 @@ Available keywords are
 .Cm AllowTcpForwarding ,
 .Cm AllowUsers ,
 .Cm AuthenticationMethods ,
+.Cm AuthorizedKeysCommand ,
+.Cm AuthorizedKeysCommandUser ,
 .Cm AuthorizedKeysFile ,
 .Cm AuthorizedPrincipalsFile ,
 .Cm Banner ,
@@ -749,6 +765,7 @@ Available keywords are
 .Cm KerberosAuthentication ,
 .Cm MaxAuthTries ,
 .Cm MaxSessions ,
+.Cm PubkeyAuthentication ,
 .Cm PasswordAuthentication ,
 .Cm PermitEmptyPasswords ,
 .Cm PermitOpen ,
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	2013-02-14 17:46:45.277546890 +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