mirror of
https://gitlab.archlinux.org/archlinux/aurweb.git
synced 2025-02-03 10:43:03 +01:00
Use the latest version of Damien Miller's patch to extend the parameters to the AuthorizedKeysCommand. Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
1094 lines
32 KiB
Diff
1094 lines
32 KiB
Diff
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
|
|
|
|
* 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
|
|
permissions as possible. In particular, he does not own the directory
|
|
that contains the scripts for the Git backend.
|
|
|
|
* Prevent from running the sshd as root.
|
|
|
|
Signed-off-by: Lukas Fleischer <lfleischer@archlinux.org>
|
|
---
|
|
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 d943efa..2ce0a4b 100644
|
|
--- a/auth2-pubkey.c
|
|
+++ b/auth2-pubkey.c
|
|
@@ -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? */
|
|
|
|
/* import */
|
|
extern ServerOptions options;
|
|
@@ -248,6 +251,227 @@ 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;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * 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)
|
|
{
|
|
@@ -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.
|
|
+ */
|
|
+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.
|
|
*/
|
|
@@ -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 (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);
|
|
+ }
|
|
+
|
|
/*
|
|
* 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 6dce0c7..a267af9 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 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.3.5
|
|
|