summaryrefslogtreecommitdiffstats
path: root/scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch
diff options
context:
space:
mode:
Diffstat (limited to 'scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch')
-rw-r--r--scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch1146
1 files changed, 1044 insertions, 102 deletions
diff --git a/scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch b/scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch
index 6b72712..688b115 100644
--- a/scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch
+++ b/scripts/git-integration/0001-Patch-sshd-for-the-AUR.patch
@@ -1,11 +1,10 @@
-From e23745b61a46f034bca3cab9936c24c249afdc7f Mon Sep 17 00:00:00 2001
-From: Lukas Fleischer <archlinux@cryptocrack.de>
-Date: Sun, 21 Dec 2014 22:17:48 +0100
+From 6423ae83d38535687d52097b7854b3c81151fe34 Mon Sep 17 00:00:00 2001
+From: Lukas Fleischer <lfleischer@archlinux.org>
+Date: Sat, 11 Apr 2015 12:57:46 +0200
Subject: [PATCH] Patch sshd for the AUR
-* Add SSH_KEY_FINGERPRINT and SSH_KEY variables to the environment of
- the AuthorizedKeysCommand which allows for efficiently looking up SSH
- keys in the AUR database.
+* Apply the latest version of Damien Miller's patch to extend the
+ parameters to the AuthorizedKeysCommand.
* Remove the secure path check for the AuthorizedKeysCommand. We are
running the sshd under a non-privileged user who has as little
@@ -14,139 +13,1082 @@ Subject: [PATCH] Patch sshd for the AUR
* Prevent from running the sshd as root.
-Signed-off-by: Lukas Fleischer <archlinux@cryptocrack.de>
+Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
---
- auth2-pubkey.c | 48 +++++++++++++++++++++++++++++++++++++++++++-----
- ssh.h | 12 ++++++++++++
- sshd.c | 5 +++++
- sshd_config.5 | 5 +++++
- 4 files changed, 65 insertions(+), 5 deletions(-)
+ auth2-pubkey.c | 530 +++++++++++++++++++++++++++++++++++++++++++--------------
+ servconf.c | 35 ++++
+ servconf.h | 8 +-
+ ssh.c | 5 +
+ sshd.c | 5 +
+ sshd_config.5 | 54 +++++-
+ sshkey.c | 172 +++++++++++--------
+ sshkey.h | 1 +
+ 8 files changed, 606 insertions(+), 204 deletions(-)
diff --git a/auth2-pubkey.c b/auth2-pubkey.c
-index 0a3c1de..baf4922 100644
+index d943efa..2ce0a4b 100644
--- a/auth2-pubkey.c
+++ b/auth2-pubkey.c
-@@ -510,6 +510,8 @@ user_key_command_allowed2(struct passwd *user_pw, Key *key)
- int status, devnull, p[2], i;
- pid_t pid;
- char *username, errmsg[512];
-+ struct sshbuf *b = NULL, *bb = NULL;
-+ char *keytext, *uu = NULL;
-
- if (options.authorized_keys_command == NULL ||
- options.authorized_keys_command[0] != '/')
-@@ -538,11 +540,6 @@ user_key_command_allowed2(struct passwd *user_pw, Key *key)
- 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;
-- }
+@@ -65,6 +65,9 @@
+ #include "monitor_wrap.h"
+ #include "authfile.h"
+ #include "match.h"
++#include "ssherr.h"
++#include "channels.h" /* XXX for session.h */
++#include "session.h" /* XXX for child_set_env(); refactor? */
- if (pipe(p) != 0) {
- error("%s: pipe: %s", __func__, strerror(errno));
-@@ -568,6 +565,47 @@ user_key_command_allowed2(struct passwd *user_pw, Key *key)
- for (i = 0; i < NSIG; i++)
- signal(i, SIG_DFL);
+ /* import */
+ extern ServerOptions options;
+@@ -248,6 +251,227 @@ pubkey_auth_info(Authctxt *authctxt, const Key *key, const char *fmt, ...)
+ free(extra);
+ }
-+ keytext = key_fingerprint(key, SSH_FP_MD5, SSH_FP_HEX);
-+ if (setenv(SSH_KEY_FINGERPRINT_ENV_NAME, keytext, 1) == -1) {
-+ error("%s: setenv: %s", __func__, strerror(errno));
-+ _exit(1);
-+ }
++/*
++ * 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));
+
-+ if (!(b = sshbuf_new()) || !(bb = sshbuf_new())) {
-+ error("%s: sshbuf_new: %s", __func__, strerror(errno));
-+ _exit(1);
++ *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 = xrealloc(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;
+ }
-+ if (sshkey_to_blob_buf(key, bb) != 0) {
-+ error("%s: sshkey_to_blob_buf: %s", __func__,
++ }
++ /* 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;
++}
++
++/*
++ * 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)
++{
++ 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;
++ }
++
++ /*
++ * 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);
+ }
-+ if (!(uu = sshbuf_dtob64(bb))) {
-+ error("%s: sshbuf_dtob64: %s", __func__,
-+ strerror(errno));
++ /* 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);
+ }
-+ if (sshbuf_putf(b, "%s ", sshkey_ssh_name(key))) {
-+ error("%s: sshbuf_putf: %s", __func__,
++ 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 (sshbuf_put(b, uu, strlen(uu) + 1)) {
-+ error("%s: sshbuf_put: %s", __func__,
++ 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);
+ }
-+ if (setenv(SSH_KEY_ENV_NAME, sshbuf_ptr(b), 1) == -1) {
-+ error("%s: setenv: %s", __func__, strerror(errno));
++ /* 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);
+ }
-+ if (uu)
-+ free(uu);
-+ if (b)
-+ sshbuf_free(b);
-+ if (bb)
-+ sshbuf_free(bb);
-+
- if ((devnull = open(_PATH_DEVNULL, O_RDWR)) == -1) {
- error("%s: open %s: %s", __func__, _PATH_DEVNULL,
- strerror(errno));
-diff --git a/ssh.h b/ssh.h
-index c94633b..411ea86 100644
---- a/ssh.h
-+++ b/ssh.h
-@@ -97,3 +97,15 @@
-
- /* Listen backlog for sshd, ssh-agent and forwarding sockets */
- #define SSH_LISTEN_BACKLOG 128
+
-+/*
-+ * Name of the environment variable containing the incoming key passed
-+ * to AuthorizedKeysCommand.
++ 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;
++}
++
++/* 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)
+ {
+@@ -269,19 +493,13 @@ match_principals_option(const char *principal_list, struct sshkey_cert *cert)
+ }
+
+ static int
+-match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert)
++process_principals(FILE *f, char *file, struct passwd *pw,
++ struct sshkey_cert *cert)
+ {
+- FILE *f;
+ char line[SSH_MAX_PUBKEY_BYTES], *cp, *ep, *line_opts;
+ u_long linenum = 0;
+ u_int i;
+
+- temporarily_use_uid(pw);
+- debug("trying authorized principals file %s", file);
+- if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) {
+- restore_uid();
+- return 0;
+- }
+ while (read_keyfile_line(f, file, line, sizeof(line), &linenum) != -1) {
+ /* Skip leading whitespace. */
+ for (cp = line; *cp == ' ' || *cp == '\t'; cp++)
+@@ -309,24 +527,119 @@ match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert)
+ }
+ for (i = 0; i < cert->nprincipals; i++) {
+ if (strcmp(cp, cert->principals[i]) == 0) {
+- debug3("matched principal \"%.100s\" "
+- "from file \"%s\" on line %lu",
+- cert->principals[i], file, linenum);
++ debug3("%s:%lu: matched principal \"%.100s\"",
++ file == NULL ? "(command)" : file,
++ linenum, cert->principals[i]);
+ if (auth_parse_options(pw, line_opts,
+ file, linenum) != 1)
+ continue;
+- fclose(f);
+- restore_uid();
+ return 1;
+ }
+ }
+ }
++ return 0;
++}
++
++static int
++match_principals_file(char *file, struct passwd *pw, struct sshkey_cert *cert)
++{
++ FILE *f;
++ int success;
++
++ temporarily_use_uid(pw);
++ debug("trying authorized principals file %s", file);
++ if ((f = auth_openprincipals(file, pw, options.strict_modes)) == NULL) {
++ restore_uid();
++ return 0;
++ }
++ success = process_principals(f, file, pw, cert);
+ fclose(f);
+ restore_uid();
+- return 0;
++ return success;
+ }
+
+ /*
++ * Checks whether principal is allowed in output of command.
++ * returns 1 if the principal is allowed or 0 otherwise.
+ */
-+#define SSH_KEY_ENV_NAME "SSH_KEY"
++static int
++match_principals_command(struct passwd *user_pw, struct sshkey *key)
++{
++ FILE *f = NULL;
++ int ok, found_principal = 0;
++ struct passwd *pw;
++ int i, ac = 0, uid_swapped = 0;
++ pid_t pid;
++ char *username = NULL, *command = NULL, **av = NULL;
++ void (*osigchld)(int);
++
++ if (options.authorized_principals_command == NULL)
++ return 0;
++ if (options.authorized_principals_command_user == NULL) {
++ error("No user for AuthorizedPrincipalsCommand specified, "
++ "skipping");
++ return 0;
++ }
++
++ /*
++ * NB. all returns later this function should go via "out" to
++ * ensure the original SIGCHLD handler is restored properly.
++ */
++ osigchld = signal(SIGCHLD, SIG_DFL);
+
++ /* Prepare and verify the user for the command */
++ username = percent_expand(options.authorized_principals_command_user,
++ "u", user_pw->pw_name, (char *)NULL);
++ pw = getpwnam(username);
++ if (pw == NULL) {
++ error("AuthorizedPrincipalsCommandUser \"%s\" not found: %s",
++ username, strerror(errno));
++ goto out;
++ }
++
++ command = percent_expand(options.authorized_principals_command,
++ "u", user_pw->pw_name, "h", user_pw->pw_dir, (char *)NULL);
++
++ /* Turn the command into an argument vector */
++ if (split_argv(command, &ac, &av) != 0) {
++ error("AuthorizedPrincipalsCommand \"%s\" contains "
++ "invalid quotes", command);
++ goto out;
++ }
++ if (ac == 0) {
++ error("AuthorizedPrincipalsCommand \"%s\" yielded no arguments",
++ command);
++ goto out;
++ }
++
++ if ((pid = subprocess("AuthorizedPrincipalsCommand", pw, command,
++ ac, av, &f)) == 0)
++ goto out;
++
++ uid_swapped = 1;
++ temporarily_use_uid(pw);
++
++ ok = process_principals(f, NULL, pw, key->cert);
++
++ if (exited_cleanly(pid, "AuthorizedPrincipalsCommand", command))
++ goto out;
++
++ /* Read completed successfully */
++ found_principal = ok;
++ out:
++ if (f != NULL)
++ fclose(f);
++ signal(SIGCHLD, osigchld);
++ for (i = 0; i < ac; i++)
++ free(av[i]);
++ free(av);
++ if (uid_swapped)
++ restore_uid();
++ free(command);
++ free(username);
++ return found_principal;
++}
+/*
-+ * Name of the environment variable containing the incoming key fingerprint
-+ * passed to AuthorizedKeysCommand.
-+ */
-+#define SSH_KEY_FINGERPRINT_ENV_NAME "SSH_KEY_FINGERPRINT"
-diff --git a/sshd.c b/sshd.c
-index 4e01855..60c676f 100644
---- a/sshd.c
-+++ b/sshd.c
-@@ -1424,6 +1424,11 @@ main(int ac, char **av)
- av = saved_argv;
- #endif
+ * Checks whether key is allowed in authorized_keys-format file,
+ * returns 1 if the key is allowed or 0 otherwise.
+ */
+@@ -448,7 +761,7 @@ user_cert_trusted_ca(struct passwd *pw, Key *key)
+ {
+ char *ca_fp, *principals_file = NULL;
+ const char *reason;
+- int ret = 0;
++ int ret = 0, found_principal = 0;
+
+ if (!key_is_cert(key) || options.trusted_user_ca_keys == NULL)
+ return 0;
+@@ -470,14 +783,20 @@ user_cert_trusted_ca(struct passwd *pw, Key *key)
+ * against the username.
+ */
+ if ((principals_file = authorized_principals_file(pw)) != NULL) {
+- if (!match_principals_file(principals_file, pw, key->cert)) {
+- reason = "Certificate does not contain an "
+- "authorized principal";
++ if (match_principals_file(principals_file, pw, key->cert))
++ found_principal = 1;
++ }
++ /* Try querying command if specified */
++ if (!found_principal && match_principals_command(pw, key))
++ found_principal = 1;
++ /* If principals file or command specify, then require a match here */
++ if (!found_principal && (principals_file != NULL ||
++ options.authorized_principals_command != NULL)) {
++ reason = "Certificate does not contain an authorized principal";
+ fail_reason:
+- error("%s", reason);
+- auth_debug_add("%s", reason);
+- goto out;
+- }
++ error("%s", reason);
++ auth_debug_add("%s", reason);
++ goto out;
+ }
+ if (key_cert_check_authority(key, 0, 1,
+ principals_file == NULL ? pw->pw_name : NULL, &reason) != 0)
+@@ -526,144 +845,105 @@ user_key_allowed2(struct passwd *pw, Key *key, char *file)
+ static int
+ user_key_command_allowed2(struct passwd *user_pw, Key *key)
+ {
+- FILE *f;
+- int ok, found_key = 0;
++ FILE *f = NULL;
++ int r, ok, found_key = 0;
+ struct passwd *pw;
+- struct stat st;
+- int status, devnull, p[2], i;
++ int i, uid_swapped = 0, ac = 0;
+ pid_t pid;
+- char *username, errmsg[512];
++ char *username = NULL, *key_fp = NULL, *keytext = NULL;
++ char *command = NULL, **av = NULL;
++ void (*osigchld)(int);
+
+- if (options.authorized_keys_command == NULL ||
+- options.authorized_keys_command[0] != '/')
++ if (options.authorized_keys_command == NULL)
+ return 0;
+-
+ if (options.authorized_keys_command_user == NULL) {
+ error("No user for AuthorizedKeysCommand specified, skipping");
+ return 0;
+ }
+
++ /*
++ * NB. all returns later this function should go via "out" to
++ * ensure the original SIGCHLD handler is restored properly.
++ */
++ osigchld = signal(SIGCHLD, SIG_DFL);
++
++ /* Prepare and verify the user for the command */
+ username = percent_expand(options.authorized_keys_command_user,
+ "u", user_pw->pw_name, (char *)NULL);
+ pw = getpwnam(username);
+ if (pw == NULL) {
+ error("AuthorizedKeysCommandUser \"%s\" not found: %s",
+ username, strerror(errno));
+- free(username);
+- return 0;
++ goto out;
+ }
+- free(username);
+-
+- temporarily_use_uid(pw);
-+ if (geteuid() == 0) {
+- if (stat(options.authorized_keys_command, &st) < 0) {
+- error("Could not stat AuthorizedKeysCommand \"%s\": %s",
+- options.authorized_keys_command, strerror(errno));
++ /* Prepare AuthorizedKeysCommand */
++ if ((key_fp = sshkey_fingerprint(key, options.fingerprint_hash,
++ SSH_FP_DEFAULT)) == NULL) {
++ error("%s: sshkey_fingerprint failed", __func__);
+ goto out;
+ }
+- if (auth_secure_path(options.authorized_keys_command, &st, NULL, 0,
+- errmsg, sizeof(errmsg)) != 0) {
+- error("Unsafe AuthorizedKeysCommand: %s", errmsg);
++ if ((r = sshkey_to_base64(key, &keytext)) != 0) {
++ error("%s: sshkey_to_base64 failed: %s", __func__, ssh_err(r));
+ goto out;
+ }
+-
+- if (pipe(p) != 0) {
+- error("%s: pipe: %s", __func__, strerror(errno));
++ command = percent_expand(options.authorized_keys_command,
++ "u", user_pw->pw_name, "h", user_pw->pw_dir,
++ "t", sshkey_ssh_name(key), "f", key_fp, "k", keytext, (char *)NULL);
++
++ /* Turn the command into an argument vector */
++ if (split_argv(command, &ac, &av) != 0) {
++ error("AuthorizedKeysCommand \"%s\" contains invalid quotes",
++ command);
++ goto out;
++ }
++ if (ac == 0) {
++ error("AuthorizedKeysCommand \"%s\" yielded no arguments",
++ command);
+ 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.
++ * If AuthorizedKeysCommand was run without arguments
++ * then fall back to the old behaviour of passing the
++ * target username as a single argument.
+ */
+- 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;
++ if (ac == 1) {
++ av = xrealloc(av, ac + 2, sizeof(*av));
++ av[1] = xstrdup(user_pw->pw_name);
++ av[2] = NULL;
++ /* Fix up command too, since it is used in log messages */
++ free(command);
++ xasprintf(&command, "%s %s", av[0], av[1]);
+ }
+
++ if ((pid = subprocess("AuthorizedKeysCommand", pw, command,
++ ac, av, &f)) == 0)
++ goto out;
++
++ uid_swapped = 1;
+ 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));
++ if (exited_cleanly(pid, "AuthorizedKeysCommand", command))
+ goto out;
+- } else if (WEXITSTATUS(status) != 0) {
+- error("AuthorizedKeysCommand %s returned status %d",
+- options.authorized_keys_command, WEXITSTATUS(status));
+- goto out;
+- }
++
++ /* Read completed successfully */
+ found_key = ok;
+ out:
+- restore_uid();
++ if (f != NULL)
++ fclose(f);
++ signal(SIGCHLD, osigchld);
++ for (i = 0; i < ac; i++)
++ free(av[i]);
++ free(av);
++ if (uid_swapped)
++ restore_uid();
++ free(command);
++ free(username);
++ free(key_fp);
++ free(keytext);
+ return found_key;
+ }
+
+diff --git a/servconf.c b/servconf.c
+index 3185462..510cdde 100644
+--- a/servconf.c
++++ b/servconf.c
+@@ -159,6 +159,8 @@ initialize_server_options(ServerOptions *options)
+ options->revoked_keys_file = NULL;
+ options->trusted_user_ca_keys = NULL;
+ options->authorized_principals_file = NULL;
++ options->authorized_principals_command = NULL;
++ options->authorized_principals_command_user = NULL;
+ options->ip_qos_interactive = -1;
+ options->ip_qos_bulk = -1;
+ options->version_addendum = NULL;
+@@ -396,6 +398,7 @@ typedef enum {
+ sUsePrivilegeSeparation, sAllowAgentForwarding,
+ sHostCertificate,
+ sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile,
++ sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser,
+ sKexAlgorithms, sIPQoS, sVersionAddendum,
+ sAuthorizedKeysCommand, sAuthorizedKeysCommandUser,
+ sAuthenticationMethods, sHostKeyAgent, sPermitUserRC,
+@@ -528,6 +531,8 @@ static struct {
+ { "ipqos", sIPQoS, SSHCFG_ALL },
+ { "authorizedkeyscommand", sAuthorizedKeysCommand, SSHCFG_ALL },
+ { "authorizedkeyscommanduser", sAuthorizedKeysCommandUser, SSHCFG_ALL },
++ { "authorizedprincipalscommand", sAuthorizedPrincipalsCommand, SSHCFG_ALL },
++ { "authorizedprincipalscommanduser", sAuthorizedPrincipalsCommandUser, SSHCFG_ALL },
+ { "versionaddendum", sVersionAddendum, SSHCFG_GLOBAL },
+ { "authenticationmethods", sAuthenticationMethods, SSHCFG_ALL },
+ { "streamlocalbindmask", sStreamLocalBindMask, SSHCFG_ALL },
+@@ -1697,6 +1702,34 @@ process_server_config_line(ServerOptions *options, char *line,
+ *charptr = xstrdup(arg);
+ break;
+
++ case sAuthorizedPrincipalsCommand:
++ if (cp == NULL)
++ fatal("%.200s line %d: Missing argument.", filename,
++ linenum);
++ len = strspn(cp, WHITESPACE);
++ if (*activep &&
++ options->authorized_principals_command == NULL) {
++ if (cp[len] != '/' && strcasecmp(cp + len, "none") != 0)
++ fatal("%.200s line %d: "
++ "AuthorizedPrincipalsCommand must be "
++ "an absolute path", filename, linenum);
++ options->authorized_principals_command =
++ xstrdup(cp + len);
++ }
++ return 0;
++
++ case sAuthorizedPrincipalsCommandUser:
++ charptr = &options->authorized_principals_command_user;
++
++ arg = strdelim(&cp);
++ if (!arg || *arg == '\0')
++ fatal("%s line %d: missing "
++ "AuthorizedPrincipalsCommandUser argument.",
++ filename, linenum);
++ if (*activep && *charptr == NULL)
++ *charptr = xstrdup(arg);
++ break;
++
+ case sAuthenticationMethods:
+ if (*activep && options->num_auth_methods == 0) {
+ while ((arg = strdelim(&cp)) && *arg != '\0') {
+@@ -2166,6 +2199,8 @@ dump_config(ServerOptions *o)
+ dump_cfg_string(sVersionAddendum, o->version_addendum);
+ dump_cfg_string(sAuthorizedKeysCommand, o->authorized_keys_command);
+ dump_cfg_string(sAuthorizedKeysCommandUser, o->authorized_keys_command_user);
++ dump_cfg_string(sAuthorizedPrincipalsCommand, o->authorized_principals_command);
++ dump_cfg_string(sAuthorizedPrincipalsCommandUser, o->authorized_principals_command_user);
+ dump_cfg_string(sHostKeyAgent, o->host_key_agent);
+ dump_cfg_string(sKexAlgorithms,
+ o->kex_algorithms ? o->kex_algorithms : KEX_SERVER_KEX);
+diff --git a/servconf.h b/servconf.h
+index 9922f0c..35d6673 100644
+--- a/servconf.h
++++ b/servconf.h
+@@ -176,9 +176,11 @@ typedef struct {
+ char *chroot_directory;
+ char *revoked_keys_file;
+ char *trusted_user_ca_keys;
+- char *authorized_principals_file;
+ char *authorized_keys_command;
+ char *authorized_keys_command_user;
++ char *authorized_principals_file;
++ char *authorized_principals_command;
++ char *authorized_principals_command_user;
+
+ int64_t rekey_limit;
+ int rekey_interval;
+@@ -214,9 +216,11 @@ struct connection_info {
+ M_CP_STROPT(banner); \
+ M_CP_STROPT(trusted_user_ca_keys); \
+ M_CP_STROPT(revoked_keys_file); \
+- M_CP_STROPT(authorized_principals_file); \
+ M_CP_STROPT(authorized_keys_command); \
+ M_CP_STROPT(authorized_keys_command_user); \
++ M_CP_STROPT(authorized_principals_file); \
++ M_CP_STROPT(authorized_principals_command); \
++ M_CP_STROPT(authorized_principals_command_user); \
+ M_CP_STROPT(hostbased_key_types); \
+ M_CP_STROPT(pubkey_key_types); \
+ M_CP_STRARRAYOPT(authorized_keys_files, num_authkeys_files); \
+diff --git a/ssh.c b/ssh.c
+index 0ad82f0..abf4e54 100644
+--- a/ssh.c
++++ b/ssh.c
+@@ -548,6 +548,11 @@ main(int ac, char **av)
+ original_real_uid = getuid();
+ original_effective_uid = geteuid();
+
++ if (original_effective_uid == 0) {
+ fprintf(stderr, "this is a patched version of the sshd that must not be run as root.\n");
+ exit(1);
+ }
+
- if (geteuid() == 0 && setgroups(0, NULL) == -1)
- debug("setgroups(): %.200s", strerror(errno));
+ /*
+ * Use uid-swapping to give up root privileges for the duration of
+ * option processing. We will re-instantiate the rights when we are
+diff --git a/sshd.c b/sshd.c
+index 6aa17fa..672c486 100644
+--- a/sshd.c
++++ b/sshd.c
+@@ -1694,6 +1694,11 @@ main(int ac, char **av)
+ strcasecmp(options.authorized_keys_command, "none") != 0))
+ fatal("AuthorizedKeysCommand set without "
+ "AuthorizedKeysCommandUser");
++ if (options.authorized_principals_command_user == NULL &&
++ (options.authorized_principals_command != NULL &&
++ strcasecmp(options.authorized_principals_command, "none") != 0))
++ fatal("AuthorizedPrincipalsCommand set without "
++ "AuthorizedPrincipalsCommandUser");
+ /*
+ * Check whether there is any path through configured auth methods.
diff --git a/sshd_config.5 b/sshd_config.5
-index ef36d33..1d7bade 100644
+index 6dce0c7..a267af9 100644
--- a/sshd_config.5
+++ b/sshd_config.5
-@@ -223,6 +223,11 @@ It will be invoked with a single argument of the username
- being authenticated, and should produce on standard output zero or
+@@ -230,9 +230,21 @@ 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 to look up the user's public keys.
+-The program must be owned by root and not writable by group or others.
+-It will be invoked with a single argument of the username
+-being authenticated, and should produce on standard output zero or
++The program must be owned by root, not writable by group or others and
++specified by an absolute path.
++.Pp
++Arguments to
++.Cm AuthorizedKeysCommand
++may be provided using the following tokens, which will be expanded
++at runtime: %% is replaced by a literal '%', %u is replaced by the
++username being authenticated, %h is replaced by the home directory
++of the user being authenticated, %t is replaced with the key type
++offered for authentication, %f is replaced with the fingerprint of
++the key, and %k is replaced with the key being offered for authentication.
++If no arguments are specified then the username of the target user
++will be supplied.
++.Pp
++The program should produce on standard output zero or
more lines of authorized_keys output (see AUTHORIZED_KEYS in
.Xr sshd 8 ) .
-+The key being used for authentication (the key's type and the key text itself,
-+separated by a space) will be available in the
-+.Ev SSH_KEY
-+environment variable, and the fingerprint of the key will be available in the
-+.Ev SSH_KEY_FINGERPRINT environment variable.
If a key supplied by AuthorizedKeysCommand does not successfully authenticate
- and authorize the user then public key authentication continues using the usual
- .Cm AuthorizedKeysFile
+@@ -271,6 +283,42 @@ directory.
+ Multiple files may be listed, separated by whitespace.
+ The default is
+ .Dq .ssh/authorized_keys .ssh/authorized_keys2 .
++.It Cm AuthorizedPrincipalsCommand
++Specifies a program to be used to generate the list of allowed
++certificate principals as per
++.Cm AuthorizedPrincipalsFile .
++The program must be owned by root, not writable by group or others and
++specified by an absolute path.
++.Pp
++Arguments to
++.Cm AuthorizedPrincipalsCommand
++may be provided using the following tokens, which will be expanded
++at runtime: %% is replaced by a literal '%', %u is replaced by the
++username being authenticated and %h is replaced by the home directory
++of the user being authenticated.
++.Pp
++The program should produce on standard output zero or
++more lines of
++.Cm AuthorizedPrincipalsFile
++output.
++If either
++.Cm AuthorizedPrincipalsCommand
++or
++.Cm AuthorizedPrincipalsFile
++is specified, then certificates offered by the client for authentication
++must contain a principal that is listed.
++By default, no AuthorizedPrincipalsCommand is run.
++.It Cm AuthorizedPrincipalsCommandUser
++Specifies the user under whose account the AuthorizedPrincipalsCommand is run.
++It is recommended to use a dedicated user that has no other role on the host
++than running authorized principals commands.
++If
++.Cm AuthorizedPrincipalsCommand
++is specified but
++.Cm AuthorizedPrincipalsCommandUser
++is not, then
++.Xr sshd 8
++will refuse to start.
+ .It Cm AuthorizedPrincipalsFile
+ Specifies a file that lists principal names that are accepted for
+ certificate authentication.
+diff --git a/sshkey.c b/sshkey.c
+index 3cc3f44..ecb61fd 100644
+--- a/sshkey.c
++++ b/sshkey.c
+@@ -761,6 +761,12 @@ to_blob_buf(const struct sshkey *key, struct sshbuf *b, int force_plain)
+ if (key == NULL)
+ return SSH_ERR_INVALID_ARGUMENT;
+
++ if (sshkey_is_cert(key)) {
++ if (key->cert == NULL)
++ return SSH_ERR_EXPECTED_CERT;
++ if (sshbuf_len(key->cert->certblob) == 0)
++ return SSH_ERR_KEY_LACKS_CERTBLOB;
++ }
+ type = force_plain ? sshkey_type_plain(key->type) : key->type;
+ typename = sshkey_ssh_name_from_type_nid(type, key->ecdsa_nid);
+
+@@ -1409,98 +1415,116 @@ sshkey_read(struct sshkey *ret, char **cpp)
+ }
+
+ int
+-sshkey_write(const struct sshkey *key, FILE *f)
++sshkey_to_base64(const struct sshkey *key, char **b64p)
+ {
+- int ret = SSH_ERR_INTERNAL_ERROR;
+- struct sshbuf *b = NULL, *bb = NULL;
++ int r = SSH_ERR_INTERNAL_ERROR;
++ struct sshbuf *b = NULL;
+ char *uu = NULL;
++
++ if (b64p != NULL)
++ *b64p = NULL;
++ if ((b = sshbuf_new()) == NULL)
++ return SSH_ERR_ALLOC_FAIL;
++ if ((r = sshkey_putb(key, b)) != 0)
++ goto out;
++ if ((uu = sshbuf_dtob64(b)) == NULL) {
++ r = SSH_ERR_ALLOC_FAIL;
++ goto out;
++ }
++ /* Success */
++ if (b64p != NULL) {
++ *b64p = uu;
++ uu = NULL;
++ }
++ r = 0;
++ out:
++ sshbuf_free(b);
++ free(uu);
++ return r;
++}
++
++static int
++sshkey_format_rsa1(const struct sshkey *key, struct sshbuf *b)
++{
++ int r = SSH_ERR_INTERNAL_ERROR;
+ #ifdef WITH_SSH1
+ u_int bits = 0;
+ char *dec_e = NULL, *dec_n = NULL;
+-#endif /* WITH_SSH1 */
+
+- if (sshkey_is_cert(key)) {
+- if (key->cert == NULL)
+- return SSH_ERR_EXPECTED_CERT;
+- if (sshbuf_len(key->cert->certblob) == 0)
+- return SSH_ERR_KEY_LACKS_CERTBLOB;
++ if (key->rsa == NULL || key->rsa->e == NULL ||
++ key->rsa->n == NULL) {
++ r = SSH_ERR_INVALID_ARGUMENT;
++ goto out;
+ }
+- if ((b = sshbuf_new()) == NULL)
+- return SSH_ERR_ALLOC_FAIL;
+- switch (key->type) {
+-#ifdef WITH_SSH1
+- case KEY_RSA1:
+- if (key->rsa == NULL || key->rsa->e == NULL ||
+- key->rsa->n == NULL) {
+- ret = SSH_ERR_INVALID_ARGUMENT;
+- goto out;
+- }
+- if ((dec_e = BN_bn2dec(key->rsa->e)) == NULL ||
+- (dec_n = BN_bn2dec(key->rsa->n)) == NULL) {
+- ret = SSH_ERR_ALLOC_FAIL;
+- goto out;
+- }
+- /* size of modulus 'n' */
+- if ((bits = BN_num_bits(key->rsa->n)) <= 0) {
+- ret = SSH_ERR_INVALID_ARGUMENT;
+- goto out;
+- }
+- if ((ret = sshbuf_putf(b, "%u %s %s", bits, dec_e, dec_n)) != 0)
+- goto out;
++ if ((dec_e = BN_bn2dec(key->rsa->e)) == NULL ||
++ (dec_n = BN_bn2dec(key->rsa->n)) == NULL) {
++ r = SSH_ERR_ALLOC_FAIL;
++ goto out;
++ }
++ /* size of modulus 'n' */
++ if ((bits = BN_num_bits(key->rsa->n)) <= 0) {
++ r = SSH_ERR_INVALID_ARGUMENT;
++ goto out;
++ }
++ if ((r = sshbuf_putf(b, "%u %s %s", bits, dec_e, dec_n)) != 0)
++ goto out;
++
++ /* Success */
++ r = 0;
++ out:
++ if (dec_e != NULL)
++ OPENSSL_free(dec_e);
++ if (dec_n != NULL)
++ OPENSSL_free(dec_n);
+ #endif /* WITH_SSH1 */
+- break;
+-#ifdef WITH_OPENSSL
+- case KEY_DSA:
+- case KEY_DSA_CERT_V00:
+- case KEY_DSA_CERT:
+- case KEY_ECDSA:
+- case KEY_ECDSA_CERT:
+- case KEY_RSA:
+- case KEY_RSA_CERT_V00:
+- case KEY_RSA_CERT:
+-#endif /* WITH_OPENSSL */
+- case KEY_ED25519:
+- case KEY_ED25519_CERT:
+- if ((bb = sshbuf_new()) == NULL) {
+- ret = SSH_ERR_ALLOC_FAIL;
+- goto out;
+- }
+- if ((ret = sshkey_putb(key, bb)) != 0)
+- goto out;
+- if ((uu = sshbuf_dtob64(bb)) == NULL) {
+- ret = SSH_ERR_ALLOC_FAIL;
++
++ return r;
++}
++
++static int
++sshkey_format_text(const struct sshkey *key, struct sshbuf *b)
++{
++ int r = SSH_ERR_INTERNAL_ERROR;
++ char *uu = NULL;
++
++ if (key->type == KEY_RSA1) {
++ if ((r = sshkey_format_rsa1(key, b)) != 0)
+ goto out;
+- }
+- if ((ret = sshbuf_putf(b, "%s ", sshkey_ssh_name(key))) != 0)
++ } else {
++ /* Unsupported key types handled in sshkey_to_base64() */
++ if ((r = sshkey_to_base64(key, &uu)) != 0)
+ goto out;
+- if ((ret = sshbuf_put(b, uu, strlen(uu))) != 0)
++ if ((r = sshbuf_putf(b, "%s %s",
++ sshkey_ssh_name(key), uu)) != 0)
+ goto out;
+- break;
+- default:
+- ret = SSH_ERR_KEY_TYPE_UNKNOWN;
+- goto out;
+ }
++ r = 0;
++ out:
++ free(uu);
++ return r;
++}
++
++int
++sshkey_write(const struct sshkey *key, FILE *f)
++{
++ struct sshbuf *b = NULL;
++ int r = SSH_ERR_INTERNAL_ERROR;
++
++ if ((b = sshbuf_new()) == NULL)
++ return SSH_ERR_ALLOC_FAIL;
++ if ((r = sshkey_format_text(key, b)) != 0)
++ goto out;
+ if (fwrite(sshbuf_ptr(b), sshbuf_len(b), 1, f) != 1) {
+ if (feof(f))
+ errno = EPIPE;
+- ret = SSH_ERR_SYSTEM_ERROR;
++ r = SSH_ERR_SYSTEM_ERROR;
+ goto out;
+ }
+- ret = 0;
++ /* Success */
++ r = 0;
+ out:
+- if (b != NULL)
+- sshbuf_free(b);
+- if (bb != NULL)
+- sshbuf_free(bb);
+- if (uu != NULL)
+- free(uu);
+-#ifdef WITH_SSH1
+- if (dec_e != NULL)
+- OPENSSL_free(dec_e);
+- if (dec_n != NULL)
+- OPENSSL_free(dec_n);
+-#endif /* WITH_SSH1 */
+- return ret;
++ sshbuf_free(b);
++ return r;
+ }
+
+ const char *
+diff --git a/sshkey.h b/sshkey.h
+index 62c1c3e..98f1ca9 100644
+--- a/sshkey.h
++++ b/sshkey.h
+@@ -163,6 +163,7 @@ int sshkey_from_blob(const u_char *, size_t, struct sshkey **);
+ int sshkey_fromb(struct sshbuf *, struct sshkey **);
+ int sshkey_froms(struct sshbuf *, struct sshkey **);
+ int sshkey_to_blob(const struct sshkey *, u_char **, size_t *);
++int sshkey_to_base64(const struct sshkey *, char **);
+ int sshkey_putb(const struct sshkey *, struct sshbuf *);
+ int sshkey_puts(const struct sshkey *, struct sshbuf *);
+ int sshkey_plain_to_blob(const struct sshkey *, u_char **, size_t *);
--
-2.2.1
+2.3.5