Bugzilla – Attachment 2556 Details for
Bug 2081
extend the parameters to the AuthorizedKeysCommand
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Add AuthorizedPrincipalsCommand
keys_principals_command_args.diff (text/plain), 30.65 KB, created by
Damien Miller
on 2015-03-03 06:33:57 AEDT
(
hide
)
Description:
Add AuthorizedPrincipalsCommand
Filename:
MIME Type:
Creator:
Damien Miller
Created:
2015-03-03 06:33:57 AEDT
Size:
30.65 KB
patch
obsolete
>diff --git a/auth2-pubkey.c b/auth2-pubkey.c >index 0e98254..ab719b3 100644 >--- a/auth2-pubkey.c >+++ b/auth2-pubkey.c >@@ -62,6 +62,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? */ > > /* import */ > extern ServerOptions options; >@@ -245,6 +248,233 @@ pubkey_auth_info(Authctxt *authctxt, const Key *key, const char *fmt, ...) > free(extra); > } > >+/* >+ * 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 = 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; >+ } >+ } >+ /* 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; >+ } >+ 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; >+} >+ >+/* 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) > { >@@ -266,19 +496,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++) >@@ -306,24 +530,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. >+ */ >+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; >+} >+/* > * Checks whether key is allowed in authorized_keys-format file, > * returns 1 if the key is allowed or 0 otherwise. > */ >@@ -445,7 +764,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; >@@ -467,14 +786,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) >@@ -523,144 +848,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 (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; > } >+ 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); > >- if (pipe(p) != 0) { >- error("%s: pipe: %s", __func__, strerror(errno)); >+ /* 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]); > } > >- 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) >- ; >+ if ((pid = subprocess("AuthorizedKeysCommand", pw, command, >+ ac, av, &f)) == 0) > goto out; >- } >+ >+ uid_swapped = 1; >+ temporarily_use_uid(pw); >+ > 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 6434c66..7391707 100644 >--- a/servconf.c >+++ b/servconf.c >@@ -149,6 +149,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; >@@ -366,6 +368,7 @@ typedef enum { > sUsePrivilegeSeparation, sAllowAgentForwarding, > sHostCertificate, > sRevokedKeys, sTrustedUserCAKeys, sAuthorizedPrincipalsFile, >+ sAuthorizedPrincipalsCommand, sAuthorizedPrincipalsCommandUser, > sKexAlgorithms, sIPQoS, sVersionAddendum, > sAuthorizedKeysCommand, sAuthorizedKeysCommandUser, > sAuthenticationMethods, sHostKeyAgent, sPermitUserRC, >@@ -486,6 +489,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 }, >@@ -1649,6 +1654,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') { >@@ -2113,6 +2146,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 447a2ce..50622e0 100644 >--- a/servconf.h >+++ b/servconf.h >@@ -174,9 +174,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; >@@ -212,9 +214,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/sshd.c b/sshd.c >index 7362116..11213cc 100644 >--- a/sshd.c >+++ b/sshd.c >@@ -1596,6 +1596,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 d116df6..0a32b67 100644 >--- a/sshd_config.5 >+++ b/sshd_config.5 >@@ -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 ) . > If a key supplied by AuthorizedKeysCommand does not successfully authenticate >@@ -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 c311834..9cf0dad 100644 >--- a/sshkey.c >+++ b/sshkey.c >@@ -737,6 +737,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); > >@@ -1381,98 +1387,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; >-#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; >- goto out; >- } >- if ((ret = sshbuf_putf(b, "%s ", sshkey_ssh_name(key))) != 0) >- goto out; >- if ((ret = sshbuf_put(b, uu, strlen(uu))) != 0) >- goto out; >- break; >- default: >- ret = SSH_ERR_KEY_TYPE_UNKNOWN; >+ if ((dec_e = BN_bn2dec(key->rsa->e)) == NULL || >+ (dec_n = BN_bn2dec(key->rsa->n)) == NULL) { >+ r = SSH_ERR_ALLOC_FAIL; > goto out; > } >- if (fwrite(sshbuf_ptr(b), sshbuf_len(b), 1, f) != 1) { >- if (feof(f)) >- errno = EPIPE; >- ret = SSH_ERR_SYSTEM_ERROR; >+ /* size of modulus 'n' */ >+ if ((bits = BN_num_bits(key->rsa->n)) <= 0) { >+ r = SSH_ERR_INVALID_ARGUMENT; > goto out; > } >- ret = 0; >+ if ((r = sshbuf_putf(b, "%u %s %s", bits, dec_e, dec_n)) != 0) >+ goto out; >+ >+ /* 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; >+ >+ 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; >+ } else { >+ /* Unsupported key types handled in sshkey_to_base64() */ >+ if ((r = sshkey_to_base64(key, &uu)) != 0) >+ goto out; >+ if ((r = sshbuf_putf(b, "%s %s", >+ sshkey_ssh_name(key), uu)) != 0) >+ 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; >+ r = SSH_ERR_SYSTEM_ERROR; >+ goto out; >+ } >+ /* Success */ >+ r = 0; >+ out: >+ sshbuf_free(b); >+ return r; > } > > const char * >diff --git a/sshkey.h b/sshkey.h >index e05b407..9cbc1fb 100644 >--- a/sshkey.h >+++ b/sshkey.h >@@ -157,6 +157,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 *);
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
Attachments on
bug 2081
:
2412
|
2416
|
2417
|
2438
|
2477
|
2478
|
2479
|
2522
|
2544
|
2545
|
2546
|
2549
| 2556 |
2557