diff options
author | Alistair Delva <adelva@google.com> | 2020-08-21 00:00:13 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2020-08-21 00:00:13 +0000 |
commit | ed358b3546c776c1c677fd88eb8f716cf6187510 (patch) | |
tree | 3c6134bcb2cda4b9dccc57b4a8b997a945aab62d /auth-options.c | |
parent | 22246b08952d746a7cc5a292570636cf4277598f (diff) | |
parent | 44a1065de8a58c51a021243a28bfa01e87822e4f (diff) |
Merge changes I934c73d4,I28cdc9a0,I9e734da9,I3c079d86
* changes:
UPSTREAM: depend
UPSTREAM: upstream: avoid possible NULL deref; from Pedro Martelletto
Revert "upstream: fix compilation with DEBUG_KEXDH; bz#3160 ok dtucker@"
Merge upstream-master into master
Diffstat (limited to 'auth-options.c')
-rw-r--r-- | auth-options.c | 1230 |
1 files changed, 742 insertions, 488 deletions
diff --git a/auth-options.c b/auth-options.c index 4f0da9c0..696ba6ac 100644 --- a/auth-options.c +++ b/auth-options.c @@ -1,441 +1,82 @@ -/* $OpenBSD: auth-options.c,v 1.65 2015/01/14 10:30:34 markus Exp $ */ +/* $OpenBSD: auth-options.c,v 1.92 2020/03/06 18:15:38 markus Exp $ */ /* - * Author: Tatu Ylonen <ylo@cs.hut.fi> - * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland - * All rights reserved - * As far as I am concerned, the code I have written for this software - * can be used freely for any purpose. Any derived versions of this - * software must be clearly marked as such, and if the derived work is - * incompatible with the protocol description in the RFC file, it must be - * called by a name other than "ssh" or "Secure Shell". + * Copyright (c) 2018 Damien Miller <djm@mindrot.org> + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "includes.h" #include <sys/types.h> +#include <stdlib.h> #include <netdb.h> #include <pwd.h> #include <string.h> #include <stdio.h> #include <stdarg.h> +#include <ctype.h> +#include <limits.h> #include "openbsd-compat/sys-queue.h" -#include "key.h" /* XXX for typedef */ -#include "buffer.h" /* XXX for typedef */ #include "xmalloc.h" -#include "match.h" #include "ssherr.h" #include "log.h" -#include "canohost.h" #include "sshbuf.h" #include "misc.h" -#include "channels.h" -#include "servconf.h" #include "sshkey.h" +#include "match.h" +#include "ssh2.h" #include "auth-options.h" -#include "hostfile.h" -#include "auth.h" - -/* Flags set authorized_keys flags */ -int no_port_forwarding_flag = 0; -int no_agent_forwarding_flag = 0; -int no_x11_forwarding_flag = 0; -int no_pty_flag = 0; -int no_user_rc = 0; -int key_is_cert_authority = 0; - -/* "command=" option. */ -char *forced_command = NULL; - -/* "environment=" options. */ -struct envstring *custom_environment = NULL; - -/* "tunnel=" option. */ -int forced_tun_device = -1; - -/* "principals=" option. */ -char *authorized_principals = NULL; - -extern ServerOptions options; - -void -auth_clear_options(void) -{ - no_agent_forwarding_flag = 0; - no_port_forwarding_flag = 0; - no_pty_flag = 0; - no_x11_forwarding_flag = 0; - no_user_rc = 0; - key_is_cert_authority = 0; - while (custom_environment) { - struct envstring *ce = custom_environment; - custom_environment = ce->next; - free(ce->s); - free(ce); - } - if (forced_command) { - free(forced_command); - forced_command = NULL; - } - if (authorized_principals) { - free(authorized_principals); - authorized_principals = NULL; - } - forced_tun_device = -1; - channel_clear_permitted_opens(); -} -/* - * return 1 if access is granted, 0 if not. - * side effect: sets key option flags - */ -int -auth_parse_options(struct passwd *pw, char *opts, char *file, u_long linenum) +static int +dup_strings(char ***dstp, size_t *ndstp, char **src, size_t nsrc) { - const char *cp; - int i; + char **dst; + size_t i, j; - /* reset options */ - auth_clear_options(); + *dstp = NULL; + *ndstp = 0; + if (nsrc == 0) + return 0; - if (!opts) - return 1; - - while (*opts && *opts != ' ' && *opts != '\t') { - cp = "cert-authority"; - if (strncasecmp(opts, cp, strlen(cp)) == 0) { - key_is_cert_authority = 1; - opts += strlen(cp); - goto next_option; - } - cp = "no-port-forwarding"; - if (strncasecmp(opts, cp, strlen(cp)) == 0) { - auth_debug_add("Port forwarding disabled."); - no_port_forwarding_flag = 1; - opts += strlen(cp); - goto next_option; - } - cp = "no-agent-forwarding"; - if (strncasecmp(opts, cp, strlen(cp)) == 0) { - auth_debug_add("Agent forwarding disabled."); - no_agent_forwarding_flag = 1; - opts += strlen(cp); - goto next_option; - } - cp = "no-X11-forwarding"; - if (strncasecmp(opts, cp, strlen(cp)) == 0) { - auth_debug_add("X11 forwarding disabled."); - no_x11_forwarding_flag = 1; - opts += strlen(cp); - goto next_option; - } - cp = "no-pty"; - if (strncasecmp(opts, cp, strlen(cp)) == 0) { - auth_debug_add("Pty allocation disabled."); - no_pty_flag = 1; - opts += strlen(cp); - goto next_option; - } - cp = "no-user-rc"; - if (strncasecmp(opts, cp, strlen(cp)) == 0) { - auth_debug_add("User rc file execution disabled."); - no_user_rc = 1; - opts += strlen(cp); - goto next_option; - } - cp = "command=\""; - if (strncasecmp(opts, cp, strlen(cp)) == 0) { - opts += strlen(cp); - if (forced_command != NULL) - free(forced_command); - forced_command = xmalloc(strlen(opts) + 1); - i = 0; - while (*opts) { - if (*opts == '"') - break; - if (*opts == '\\' && opts[1] == '"') { - opts += 2; - forced_command[i++] = '"'; - continue; - } - forced_command[i++] = *opts++; - } - if (!*opts) { - debug("%.100s, line %lu: missing end quote", - file, linenum); - auth_debug_add("%.100s, line %lu: missing end quote", - file, linenum); - free(forced_command); - forced_command = NULL; - goto bad_option; - } - forced_command[i] = '\0'; - auth_debug_add("Forced command."); - opts++; - goto next_option; - } - cp = "principals=\""; - if (strncasecmp(opts, cp, strlen(cp)) == 0) { - opts += strlen(cp); - if (authorized_principals != NULL) - free(authorized_principals); - authorized_principals = xmalloc(strlen(opts) + 1); - i = 0; - while (*opts) { - if (*opts == '"') - break; - if (*opts == '\\' && opts[1] == '"') { - opts += 2; - authorized_principals[i++] = '"'; - continue; - } - authorized_principals[i++] = *opts++; - } - if (!*opts) { - debug("%.100s, line %lu: missing end quote", - file, linenum); - auth_debug_add("%.100s, line %lu: missing end quote", - file, linenum); - free(authorized_principals); - authorized_principals = NULL; - goto bad_option; - } - authorized_principals[i] = '\0'; - auth_debug_add("principals: %.900s", - authorized_principals); - opts++; - goto next_option; - } - cp = "environment=\""; - if (options.permit_user_env && - strncasecmp(opts, cp, strlen(cp)) == 0) { - char *s; - struct envstring *new_envstring; - - opts += strlen(cp); - s = xmalloc(strlen(opts) + 1); - i = 0; - while (*opts) { - if (*opts == '"') - break; - if (*opts == '\\' && opts[1] == '"') { - opts += 2; - s[i++] = '"'; - continue; - } - s[i++] = *opts++; - } - if (!*opts) { - debug("%.100s, line %lu: missing end quote", - file, linenum); - auth_debug_add("%.100s, line %lu: missing end quote", - file, linenum); - free(s); - goto bad_option; - } - s[i] = '\0'; - auth_debug_add("Adding to environment: %.900s", s); - debug("Adding to environment: %.900s", s); - opts++; - new_envstring = xcalloc(1, sizeof(struct envstring)); - new_envstring->s = s; - new_envstring->next = custom_environment; - custom_environment = new_envstring; - goto next_option; - } - cp = "from=\""; - if (strncasecmp(opts, cp, strlen(cp)) == 0) { - const char *remote_ip = get_remote_ipaddr(); - const char *remote_host = get_canonical_hostname( - options.use_dns); - char *patterns = xmalloc(strlen(opts) + 1); - - opts += strlen(cp); - i = 0; - while (*opts) { - if (*opts == '"') - break; - if (*opts == '\\' && opts[1] == '"') { - opts += 2; - patterns[i++] = '"'; - continue; - } - patterns[i++] = *opts++; - } - if (!*opts) { - debug("%.100s, line %lu: missing end quote", - file, linenum); - auth_debug_add("%.100s, line %lu: missing end quote", - file, linenum); - free(patterns); - goto bad_option; - } - patterns[i] = '\0'; - opts++; - switch (match_host_and_ip(remote_host, remote_ip, - patterns)) { - case 1: - free(patterns); - /* Host name matches. */ - goto next_option; - case -1: - debug("%.100s, line %lu: invalid criteria", - file, linenum); - auth_debug_add("%.100s, line %lu: " - "invalid criteria", file, linenum); - /* FALLTHROUGH */ - case 0: - free(patterns); - logit("Authentication tried for %.100s with " - "correct key but not from a permitted " - "host (host=%.200s, ip=%.200s).", - pw->pw_name, remote_host, remote_ip); - auth_debug_add("Your host '%.200s' is not " - "permitted to use this key for login.", - remote_host); - break; - } - /* deny access */ - return 0; - } - cp = "permitopen=\""; - if (strncasecmp(opts, cp, strlen(cp)) == 0) { - char *host, *p; - int port; - char *patterns = xmalloc(strlen(opts) + 1); - - opts += strlen(cp); - i = 0; - while (*opts) { - if (*opts == '"') - break; - if (*opts == '\\' && opts[1] == '"') { - opts += 2; - patterns[i++] = '"'; - continue; - } - patterns[i++] = *opts++; - } - if (!*opts) { - debug("%.100s, line %lu: missing end quote", - file, linenum); - auth_debug_add("%.100s, line %lu: missing " - "end quote", file, linenum); - free(patterns); - goto bad_option; - } - patterns[i] = '\0'; - opts++; - p = patterns; - /* XXX - add streamlocal support */ - host = hpdelim(&p); - if (host == NULL || strlen(host) >= NI_MAXHOST) { - debug("%.100s, line %lu: Bad permitopen " - "specification <%.100s>", file, linenum, - patterns); - auth_debug_add("%.100s, line %lu: " - "Bad permitopen specification", file, - linenum); - free(patterns); - goto bad_option; - } - host = cleanhostname(host); - if (p == NULL || (port = permitopen_port(p)) < 0) { - debug("%.100s, line %lu: Bad permitopen port " - "<%.100s>", file, linenum, p ? p : ""); - auth_debug_add("%.100s, line %lu: " - "Bad permitopen port", file, linenum); - free(patterns); - goto bad_option; - } - if ((options.allow_tcp_forwarding & FORWARD_LOCAL) != 0) - channel_add_permitted_opens(host, port); - free(patterns); - goto next_option; - } - cp = "tunnel=\""; - if (strncasecmp(opts, cp, strlen(cp)) == 0) { - char *tun = NULL; - opts += strlen(cp); - tun = xmalloc(strlen(opts) + 1); - i = 0; - while (*opts) { - if (*opts == '"') - break; - tun[i++] = *opts++; - } - if (!*opts) { - debug("%.100s, line %lu: missing end quote", - file, linenum); - auth_debug_add("%.100s, line %lu: missing end quote", - file, linenum); - free(tun); - forced_tun_device = -1; - goto bad_option; - } - tun[i] = '\0'; - forced_tun_device = a2tun(tun, NULL); - free(tun); - if (forced_tun_device == SSH_TUNID_ERR) { - debug("%.100s, line %lu: invalid tun device", - file, linenum); - auth_debug_add("%.100s, line %lu: invalid tun device", - file, linenum); - forced_tun_device = -1; - goto bad_option; - } - auth_debug_add("Forced tun device: %d", forced_tun_device); - opts++; - goto next_option; + if ((dst = calloc(nsrc, sizeof(*src))) == NULL) + return -1; + for (i = 0; i < nsrc; i++) { + if ((dst[i] = strdup(src[i])) == NULL) { + for (j = 0; j < i; j++) + free(dst[j]); + free(dst); + return -1; } -next_option: - /* - * Skip the comma, and move to the next option - * (or break out if there are no more). - */ - if (!*opts) - fatal("Bugs in auth-options.c option processing."); - if (*opts == ' ' || *opts == '\t') - break; /* End of options. */ - if (*opts != ',') - goto bad_option; - opts++; - /* Process the next option. */ } - - /* grant access */ - return 1; - -bad_option: - logit("Bad options in %.100s file, line %lu: %.50s", - file, linenum, opts); - auth_debug_add("Bad options in %.100s file, line %lu: %.50s", - file, linenum, opts); - - /* deny access */ + /* success */ + *dstp = dst; + *ndstp = nsrc; return 0; } #define OPTIONS_CRITICAL 1 #define OPTIONS_EXTENSIONS 2 static int -parse_option_list(struct sshbuf *oblob, struct passwd *pw, - u_int which, int crit, - int *cert_no_port_forwarding_flag, - int *cert_no_agent_forwarding_flag, - int *cert_no_x11_forwarding_flag, - int *cert_no_pty_flag, - int *cert_no_user_rc, - char **cert_forced_command, - int *cert_source_address_done) +cert_option_list(struct sshauthopt *opts, struct sshbuf *oblob, + u_int which, int crit) { char *command, *allowed; - const char *remote_ip; char *name = NULL; struct sshbuf *c = NULL, *data = NULL; - int r, ret = -1, result, found; + int r, ret = -1, found; if ((c = sshbuf_fromb(oblob)) == NULL) { error("%s: sshbuf_fromb failed", __func__); @@ -455,22 +96,25 @@ parse_option_list(struct sshbuf *oblob, struct passwd *pw, name, sshbuf_len(data)); found = 0; if ((which & OPTIONS_EXTENSIONS) != 0) { - if (strcmp(name, "permit-X11-forwarding") == 0) { - *cert_no_x11_forwarding_flag = 0; + if (strcmp(name, "no-touch-required") == 0) { + opts->no_require_user_presence = 1; + found = 1; + } else if (strcmp(name, "permit-X11-forwarding") == 0) { + opts->permit_x11_forwarding_flag = 1; found = 1; } else if (strcmp(name, "permit-agent-forwarding") == 0) { - *cert_no_agent_forwarding_flag = 0; + opts->permit_agent_forwarding_flag = 1; found = 1; } else if (strcmp(name, "permit-port-forwarding") == 0) { - *cert_no_port_forwarding_flag = 0; + opts->permit_port_forwarding_flag = 1; found = 1; } else if (strcmp(name, "permit-pty") == 0) { - *cert_no_pty_flag = 0; + opts->permit_pty_flag = 1; found = 1; } else if (strcmp(name, "permit-user-rc") == 0) { - *cert_no_user_rc = 0; + opts->permit_user_rc = 1; found = 1; } } @@ -482,13 +126,13 @@ parse_option_list(struct sshbuf *oblob, struct passwd *pw, "section: %s", name, ssh_err(r)); goto out; } - if (*cert_forced_command != NULL) { + if (opts->force_command != NULL) { error("Certificate has multiple " "force-command options"); free(command); goto out; } - *cert_forced_command = command; + opts->force_command = command; found = 1; } if (strcmp(name, "source-address") == 0) { @@ -498,38 +142,19 @@ parse_option_list(struct sshbuf *oblob, struct passwd *pw, "section: %s", name, ssh_err(r)); goto out; } - if ((*cert_source_address_done)++) { + if (opts->required_from_host_cert != NULL) { error("Certificate has multiple " "source-address options"); free(allowed); goto out; } - remote_ip = get_remote_ipaddr(); - result = addr_match_cidr_list(remote_ip, - allowed); - free(allowed); - switch (result) { - case 1: - /* accepted */ - break; - case 0: - /* no match */ - logit("Authentication tried for %.100s " - "with valid certificate but not " - "from a permitted host " - "(ip=%.200s).", pw->pw_name, - remote_ip); - auth_debug_add("Your address '%.200s' " - "is not permitted to use this " - "certificate for login.", - remote_ip); - goto out; - case -1: - default: + /* Check syntax */ + if (addr_match_cidr_list(NULL, allowed) == -1) { error("Certificate source-address " "contents invalid"); goto out; } + opts->required_from_host_cert = allowed; found = 1; } } @@ -555,75 +180,704 @@ parse_option_list(struct sshbuf *oblob, struct passwd *pw, ret = 0; out: - if (ret != 0 && - cert_forced_command != NULL && - *cert_forced_command != NULL) { - free(*cert_forced_command); - *cert_forced_command = NULL; - } - if (name != NULL) - free(name); + free(name); sshbuf_free(data); sshbuf_free(c); return ret; } +struct sshauthopt * +sshauthopt_new(void) +{ + struct sshauthopt *ret; + + if ((ret = calloc(1, sizeof(*ret))) == NULL) + return NULL; + ret->force_tun_device = -1; + return ret; +} + +void +sshauthopt_free(struct sshauthopt *opts) +{ + size_t i; + + if (opts == NULL) + return; + + free(opts->cert_principals); + free(opts->force_command); + free(opts->required_from_host_cert); + free(opts->required_from_host_keys); + + for (i = 0; i < opts->nenv; i++) + free(opts->env[i]); + free(opts->env); + + for (i = 0; i < opts->npermitopen; i++) + free(opts->permitopen[i]); + free(opts->permitopen); + + for (i = 0; i < opts->npermitlisten; i++) + free(opts->permitlisten[i]); + free(opts->permitlisten); + + freezero(opts, sizeof(*opts)); +} + +struct sshauthopt * +sshauthopt_new_with_keys_defaults(void) +{ + struct sshauthopt *ret = NULL; + + if ((ret = sshauthopt_new()) == NULL) + return NULL; + + /* Defaults for authorized_keys flags */ + ret->permit_port_forwarding_flag = 1; + ret->permit_agent_forwarding_flag = 1; + ret->permit_x11_forwarding_flag = 1; + ret->permit_pty_flag = 1; + ret->permit_user_rc = 1; + return ret; +} + /* - * Set options from critical certificate options. These supersede user key - * options so this must be called after auth_parse_options(). + * Parse and record a permitopen/permitlisten directive. + * Return 0 on success. Return -1 on failure and sets *errstrp to error reason. */ -int -auth_cert_options(struct sshkey *k, struct passwd *pw) +static int +handle_permit(const char **optsp, int allow_bare_port, + char ***permitsp, size_t *npermitsp, const char **errstrp) { - int cert_no_port_forwarding_flag = 1; - int cert_no_agent_forwarding_flag = 1; - int cert_no_x11_forwarding_flag = 1; - int cert_no_pty_flag = 1; - int cert_no_user_rc = 1; - char *cert_forced_command = NULL; - int cert_source_address_done = 0; - - if (sshkey_cert_is_legacy(k)) { - /* All options are in the one field for v00 certs */ - if (parse_option_list(k->cert->critical, pw, - OPTIONS_CRITICAL|OPTIONS_EXTENSIONS, 1, - &cert_no_port_forwarding_flag, - &cert_no_agent_forwarding_flag, - &cert_no_x11_forwarding_flag, - &cert_no_pty_flag, - &cert_no_user_rc, - &cert_forced_command, - &cert_source_address_done) == -1) - return -1; - } else { - /* Separate options and extensions for v01 certs */ - if (parse_option_list(k->cert->critical, pw, - OPTIONS_CRITICAL, 1, NULL, NULL, NULL, NULL, NULL, - &cert_forced_command, - &cert_source_address_done) == -1) - return -1; - if (parse_option_list(k->cert->extensions, pw, - OPTIONS_EXTENSIONS, 1, - &cert_no_port_forwarding_flag, - &cert_no_agent_forwarding_flag, - &cert_no_x11_forwarding_flag, - &cert_no_pty_flag, - &cert_no_user_rc, - NULL, NULL) == -1) + char *opt, *tmp, *cp, *host, **permits = *permitsp; + size_t npermits = *npermitsp; + const char *errstr = "unknown error"; + + if (npermits > SSH_AUTHOPT_PERMIT_MAX) { + *errstrp = "too many permission directives"; + return -1; + } + if ((opt = opt_dequote(optsp, &errstr)) == NULL) { + return -1; + } + if (allow_bare_port && strchr(opt, ':') == NULL) { + /* + * Allow a bare port number in permitlisten to indicate a + * listen_host wildcard. + */ + if (asprintf(&tmp, "*:%s", opt) == -1) { + free(opt); + *errstrp = "memory allocation failed"; return -1; + } + free(opt); + opt = tmp; + } + if ((tmp = strdup(opt)) == NULL) { + free(opt); + *errstrp = "memory allocation failed"; + return -1; + } + cp = tmp; + /* validate syntax before recording it. */ + host = hpdelim(&cp); + if (host == NULL || strlen(host) >= NI_MAXHOST) { + free(tmp); + free(opt); + *errstrp = "invalid permission hostname"; + return -1; + } + /* + * don't want to use permitopen_port to avoid + * dependency on channels.[ch] here. + */ + if (cp == NULL || + (strcmp(cp, "*") != 0 && a2port(cp) <= 0)) { + free(tmp); + free(opt); + *errstrp = "invalid permission port"; + return -1; + } + /* XXX - add streamlocal support */ + free(tmp); + /* Record it */ + if ((permits = recallocarray(permits, npermits, npermits + 1, + sizeof(*permits))) == NULL) { + free(opt); + /* NB. don't update *permitsp if alloc fails */ + *errstrp = "memory allocation failed"; + return -1; + } + permits[npermits++] = opt; + *permitsp = permits; + *npermitsp = npermits; + return 0; +} + +struct sshauthopt * +sshauthopt_parse(const char *opts, const char **errstrp) +{ + char **oarray, *opt, *cp, *tmp; + int r; + struct sshauthopt *ret = NULL; + const char *errstr = "unknown error"; + uint64_t valid_before; + + if (errstrp != NULL) + *errstrp = NULL; + if ((ret = sshauthopt_new_with_keys_defaults()) == NULL) + goto alloc_fail; + + if (opts == NULL) + return ret; + + while (*opts && *opts != ' ' && *opts != '\t') { + /* flag options */ + if ((r = opt_flag("restrict", 0, &opts)) != -1) { + ret->restricted = 1; + ret->permit_port_forwarding_flag = 0; + ret->permit_agent_forwarding_flag = 0; + ret->permit_x11_forwarding_flag = 0; + ret->permit_pty_flag = 0; + ret->permit_user_rc = 0; + } else if ((r = opt_flag("cert-authority", 0, &opts)) != -1) { + ret->cert_authority = r; + } else if ((r = opt_flag("port-forwarding", 1, &opts)) != -1) { + ret->permit_port_forwarding_flag = r == 1; + } else if ((r = opt_flag("agent-forwarding", 1, &opts)) != -1) { + ret->permit_agent_forwarding_flag = r == 1; + } else if ((r = opt_flag("x11-forwarding", 1, &opts)) != -1) { + ret->permit_x11_forwarding_flag = r == 1; + } else if ((r = opt_flag("touch-required", 1, &opts)) != -1) { + ret->no_require_user_presence = r != 1; /* NB. flip */ + } else if ((r = opt_flag("pty", 1, &opts)) != -1) { + ret->permit_pty_flag = r == 1; + } else if ((r = opt_flag("user-rc", 1, &opts)) != -1) { + ret->permit_user_rc = r == 1; + } else if (opt_match(&opts, "command")) { + if (ret->force_command != NULL) { + errstr = "multiple \"command\" clauses"; + goto fail; + } + ret->force_command = opt_dequote(&opts, &errstr); + if (ret->force_command == NULL) + goto fail; + } else if (opt_match(&opts, "principals")) { + if (ret->cert_principals != NULL) { + errstr = "multiple \"principals\" clauses"; + goto fail; + } + ret->cert_principals = opt_dequote(&opts, &errstr); + if (ret->cert_principals == NULL) + goto fail; + } else if (opt_match(&opts, "from")) { + if (ret->required_from_host_keys != NULL) { + errstr = "multiple \"from\" clauses"; + goto fail; + } + ret->required_from_host_keys = opt_dequote(&opts, + &errstr); + if (ret->required_from_host_keys == NULL) + goto fail; + } else if (opt_match(&opts, "expiry-time")) { + if ((opt = opt_dequote(&opts, &errstr)) == NULL) + goto fail; + if (parse_absolute_time(opt, &valid_before) != 0 || + valid_before == 0) { + free(opt); + errstr = "invalid expires time"; + goto fail; + } + free(opt); + if (ret->valid_before == 0 || + valid_before < ret->valid_before) + ret->valid_before = valid_before; + } else if (opt_match(&opts, "environment")) { + if (ret->nenv > INT_MAX) { + errstr = "too many environment strings"; + goto fail; + } + if ((opt = opt_dequote(&opts, &errstr)) == NULL) + goto fail; + /* env name must be alphanumeric and followed by '=' */ + if ((tmp = strchr(opt, '=')) == NULL) { + free(opt); + errstr = "invalid environment string"; + goto fail; + } + if ((cp = strdup(opt)) == NULL) + goto alloc_fail; + cp[tmp - opt] = '\0'; /* truncate at '=' */ + if (!valid_env_name(cp)) { + free(cp); + free(opt); + errstr = "invalid environment string"; + goto fail; + } + free(cp); + /* Append it. */ + oarray = ret->env; + if ((ret->env = recallocarray(ret->env, ret->nenv, + ret->nenv + 1, sizeof(*ret->env))) == NULL) { + free(opt); + ret->env = oarray; /* put it back for cleanup */ + goto alloc_fail; + } + ret->env[ret->nenv++] = opt; + } else if (opt_match(&opts, "permitopen")) { + if (handle_permit(&opts, 0, &ret->permitopen, + &ret->npermitopen, &errstr) != 0) + goto fail; + } else if (opt_match(&opts, "permitlisten")) { + if (handle_permit(&opts, 1, &ret->permitlisten, + &ret->npermitlisten, &errstr) != 0) + goto fail; + } else if (opt_match(&opts, "tunnel")) { + if ((opt = opt_dequote(&opts, &errstr)) == NULL) + goto fail; + ret->force_tun_device = a2tun(opt, NULL); + free(opt); + if (ret->force_tun_device == SSH_TUNID_ERR) { + errstr = "invalid tun device"; + goto fail; + } + } + /* + * Skip the comma, and move to the next option + * (or break out if there are no more). + */ + if (*opts == '\0' || *opts == ' ' || *opts == '\t') + break; /* End of options. */ + /* Anything other than a comma is an unknown option */ + if (*opts != ',') { + errstr = "unknown key option"; + goto fail; + } + opts++; + if (*opts == '\0') { + errstr = "unexpected end-of-options"; + goto fail; + } + } + + /* success */ + if (errstrp != NULL) + *errstrp = NULL; + return ret; + +alloc_fail: + errstr = "memory allocation failed"; +fail: + sshauthopt_free(ret); + if (errstrp != NULL) + *errstrp = errstr; + return NULL; +} + +struct sshauthopt * +sshauthopt_from_cert(struct sshkey *k) +{ + struct sshauthopt *ret; + + if (k == NULL || !sshkey_type_is_cert(k->type) || k->cert == NULL || + k->cert->type != SSH2_CERT_TYPE_USER) + return NULL; + + if ((ret = sshauthopt_new()) == NULL) + return NULL; + + /* Handle options and critical extensions separately */ + if (cert_option_list(ret, k->cert->critical, + OPTIONS_CRITICAL, 1) == -1) { + sshauthopt_free(ret); + return NULL; + } + if (cert_option_list(ret, k->cert->extensions, + OPTIONS_EXTENSIONS, 0) == -1) { + sshauthopt_free(ret); + return NULL; + } + /* success */ + return ret; +} + +/* + * Merges "additional" options to "primary" and returns the result. + * NB. Some options from primary have primacy. + */ +struct sshauthopt * +sshauthopt_merge(const struct sshauthopt *primary, + const struct sshauthopt *additional, const char **errstrp) +{ + struct sshauthopt *ret; + const char *errstr = "internal error"; + const char *tmp; + + if (errstrp != NULL) + *errstrp = NULL; + + if ((ret = sshauthopt_new()) == NULL) + goto alloc_fail; + + /* cert_authority and cert_principals are cleared in result */ + + /* Prefer access lists from primary. */ + /* XXX err is both set and mismatch? */ + tmp = primary->required_from_host_cert; + if (tmp == NULL) + tmp = additional->required_from_host_cert; + if (tmp != NULL && (ret->required_from_host_cert = strdup(tmp)) == NULL) + goto alloc_fail; + tmp = primary->required_from_host_keys; + if (tmp == NULL) + tmp = additional->required_from_host_keys; + if (tmp != NULL && (ret->required_from_host_keys = strdup(tmp)) == NULL) + goto alloc_fail; + + /* + * force_tun_device, permitopen/permitlisten and environment all + * prefer the primary. + */ + ret->force_tun_device = primary->force_tun_device; + if (ret->force_tun_device == -1) + ret->force_tun_device = additional->force_tun_device; + if (primary->nenv > 0) { + if (dup_strings(&ret->env, &ret->nenv, + primary->env, primary->nenv) != 0) + goto alloc_fail; + } else if (additional->nenv) { + if (dup_strings(&ret->env, &ret->nenv, + additional->env, additional->nenv) != 0) + goto alloc_fail; + } + if (primary->npermitopen > 0) { + if (dup_strings(&ret->permitopen, &ret->npermitopen, + primary->permitopen, primary->npermitopen) != 0) + goto alloc_fail; + } else if (additional->npermitopen > 0) { + if (dup_strings(&ret->permitopen, &ret->npermitopen, + additional->permitopen, additional->npermitopen) != 0) + goto alloc_fail; + } + + if (primary->npermitlisten > 0) { + if (dup_strings(&ret->permitlisten, &ret->npermitlisten, + primary->permitlisten, primary->npermitlisten) != 0) + goto alloc_fail; + } else if (additional->npermitlisten > 0) { + if (dup_strings(&ret->permitlisten, &ret->npermitlisten, + additional->permitlisten, additional->npermitlisten) != 0) + goto alloc_fail; + } + +#define OPTFLAG_AND(x) ret->x = (primary->x == 1) && (additional->x == 1) + /* Permissive flags are logical-AND (i.e. must be set in both) */ + OPTFLAG_AND(permit_port_forwarding_flag); + OPTFLAG_AND(permit_agent_forwarding_flag); + OPTFLAG_AND(permit_x11_forwarding_flag); + OPTFLAG_AND(permit_pty_flag); + OPTFLAG_AND(permit_user_rc); + OPTFLAG_AND(no_require_user_presence); +#undef OPTFLAG_AND + + /* Earliest expiry time should win */ + if (primary->valid_before != 0) + ret->valid_before = primary->valid_before; + if (additional->valid_before != 0 && + additional->valid_before < ret->valid_before) + ret->valid_before = additional->valid_before; + + /* + * When both multiple forced-command are specified, only + * proceed if they are identical, otherwise fail. + */ + if (primary->force_command != NULL && + additional->force_command != NULL) { + if (strcmp(primary->force_command, + additional->force_command) == 0) { + /* ok */ + ret->force_command = strdup(primary->force_command); + if (ret->force_command == NULL) + goto alloc_fail; + } else { + errstr = "forced command options do not match"; + goto fail; + } + } else if (primary->force_command != NULL) { + if ((ret->force_command = strdup( + primary->force_command)) == NULL) + goto alloc_fail; + } else if (additional->force_command != NULL) { + if ((ret->force_command = strdup( + additional->force_command)) == NULL) + goto alloc_fail; + } + /* success */ + if (errstrp != NULL) + *errstrp = NULL; + return ret; + + alloc_fail: + errstr = "memory allocation failed"; + fail: + if (errstrp != NULL) + *errstrp = errstr; + sshauthopt_free(ret); + return NULL; +} + +/* + * Copy options + */ +struct sshauthopt * +sshauthopt_copy(const struct sshauthopt *orig) +{ + struct sshauthopt *ret; + + if ((ret = sshauthopt_new()) == NULL) + return NULL; + +#define OPTSCALAR(x) ret->x = orig->x + OPTSCALAR(permit_port_forwarding_flag); + OPTSCALAR(permit_agent_forwarding_flag); + OPTSCALAR(permit_x11_forwarding_flag); + OPTSCALAR(permit_pty_flag); + OPTSCALAR(permit_user_rc); + OPTSCALAR(restricted); + OPTSCALAR(cert_authority); + OPTSCALAR(force_tun_device); + OPTSCALAR(valid_before); + OPTSCALAR(no_require_user_presence); +#undef OPTSCALAR +#define OPTSTRING(x) \ + do { \ + if (orig->x != NULL && (ret->x = strdup(orig->x)) == NULL) { \ + sshauthopt_free(ret); \ + return NULL; \ + } \ + } while (0) + OPTSTRING(cert_principals); + OPTSTRING(force_command); + OPTSTRING(required_from_host_cert); + OPTSTRING(required_from_host_keys); +#undef OPTSTRING + + if (dup_strings(&ret->env, &ret->nenv, orig->env, orig->nenv) != 0 || + dup_strings(&ret->permitopen, &ret->npermitopen, + orig->permitopen, orig->npermitopen) != 0 || + dup_strings(&ret->permitlisten, &ret->npermitlisten, + orig->permitlisten, orig->npermitlisten) != 0) { + sshauthopt_free(ret); + return NULL; + } + return ret; +} + +static int +serialise_array(struct sshbuf *m, char **a, size_t n) +{ + struct sshbuf *b; + size_t i; + int r; + + if (n > INT_MAX) + return SSH_ERR_INTERNAL_ERROR; + + if ((b = sshbuf_new()) == NULL) { + return SSH_ERR_ALLOC_FAIL; + } + for (i = 0; i < n; i++) { + if ((r = sshbuf_put_cstring(b, a[i])) != 0) { + sshbuf_free(b); + return r; + } + } + if ((r = sshbuf_put_u32(m, n)) != 0 || + (r = sshbuf_put_stringb(m, b)) != 0) { + sshbuf_free(b); + return r; } + /* success */ + return 0; +} - no_port_forwarding_flag |= cert_no_port_forwarding_flag; - no_agent_forwarding_flag |= cert_no_agent_forwarding_flag; - no_x11_forwarding_flag |= cert_no_x11_forwarding_flag; - no_pty_flag |= cert_no_pty_flag; - no_user_rc |= cert_no_user_rc; - /* CA-specified forced command supersedes key option */ - if (cert_forced_command != NULL) { - if (forced_command != NULL) - free(forced_command); - forced_command = cert_forced_command; +static int +deserialise_array(struct sshbuf *m, char ***ap, size_t *np) +{ + char **a = NULL; + size_t i, n = 0; + struct sshbuf *b = NULL; + u_int tmp; + int r = SSH_ERR_INTERNAL_ERROR; + + if ((r = sshbuf_get_u32(m, &tmp)) != 0 || + (r = sshbuf_froms(m, &b)) != 0) + goto out; + if (tmp > INT_MAX) { + r = SSH_ERR_INVALID_FORMAT; + goto out; } + n = tmp; + if (n > 0 && (a = calloc(n, sizeof(*a))) == NULL) { + r = SSH_ERR_ALLOC_FAIL; + goto out; + } + for (i = 0; i < n; i++) { + if ((r = sshbuf_get_cstring(b, &a[i], NULL)) != 0) + goto out; + } + /* success */ + r = 0; + *ap = a; + a = NULL; + *np = n; + n = 0; + out: + if (a != NULL) { + for (i = 0; i < n; i++) + free(a[i]); + free(a); + } + sshbuf_free(b); + return r; +} + +static int +serialise_nullable_string(struct sshbuf *m, const char *s) +{ + int r; + + if ((r = sshbuf_put_u8(m, s == NULL)) != 0 || + (r = sshbuf_put_cstring(m, s)) != 0) + return r; + return 0; +} + +static int +deserialise_nullable_string(struct sshbuf *m, char **sp) +{ + int r; + u_char flag; + + *sp = NULL; + if ((r = sshbuf_get_u8(m, &flag)) != 0 || + (r = sshbuf_get_cstring(m, flag ? NULL : sp, NULL)) != 0) + return r; return 0; } +int +sshauthopt_serialise(const struct sshauthopt *opts, struct sshbuf *m, + int untrusted) +{ + int r = SSH_ERR_INTERNAL_ERROR; + + /* Flag options */ + if ((r = sshbuf_put_u8(m, opts->permit_port_forwarding_flag)) != 0 || + (r = sshbuf_put_u8(m, opts->permit_agent_forwarding_flag)) != 0 || + (r = sshbuf_put_u8(m, opts->permit_x11_forwarding_flag)) != 0 || + (r = sshbuf_put_u8(m, opts->permit_pty_flag)) != 0 || + (r = sshbuf_put_u8(m, opts->permit_user_rc)) != 0 || + (r = sshbuf_put_u8(m, opts->restricted)) != 0 || + (r = sshbuf_put_u8(m, opts->cert_authority)) != 0 || + (r = sshbuf_put_u8(m, opts->no_require_user_presence)) != 0) + return r; + + /* Simple integer options */ + if ((r = sshbuf_put_u64(m, opts->valid_before)) != 0) + return r; + + /* tunnel number can be negative to indicate "unset" */ + if ((r = sshbuf_put_u8(m, opts->force_tun_device == -1)) != 0 || + (r = sshbuf_put_u32(m, (opts->force_tun_device < 0) ? + 0 : (u_int)opts->force_tun_device)) != 0) + return r; + + /* String options; these may be NULL */ + if ((r = serialise_nullable_string(m, + untrusted ? "yes" : opts->cert_principals)) != 0 || + (r = serialise_nullable_string(m, + untrusted ? "true" : opts->force_command)) != 0 || + (r = serialise_nullable_string(m, + untrusted ? NULL : opts->required_from_host_cert)) != 0 || + (r = serialise_nullable_string(m, + untrusted ? NULL : opts->required_from_host_keys)) != 0) + return r; + + /* Array options */ + if ((r = serialise_array(m, opts->env, + untrusted ? 0 : opts->nenv)) != 0 || + (r = serialise_array(m, opts->permitopen, + untrusted ? 0 : opts->npermitopen)) != 0 || + (r = serialise_array(m, opts->permitlisten, + untrusted ? 0 : opts->npermitlisten)) != 0) + return r; + + /* success */ + return 0; +} + +int +sshauthopt_deserialise(struct sshbuf *m, struct sshauthopt **optsp) +{ + struct sshauthopt *opts = NULL; + int r = SSH_ERR_INTERNAL_ERROR; + u_char f; + u_int tmp; + + if ((opts = calloc(1, sizeof(*opts))) == NULL) + return SSH_ERR_ALLOC_FAIL; + + /* Flag options */ +#define OPT_FLAG(x) \ + do { \ + if ((r = sshbuf_get_u8(m, &f)) != 0) \ + goto out; \ + opts->x = f; \ + } while (0) + OPT_FLAG(permit_port_forwarding_flag); + OPT_FLAG(permit_agent_forwarding_flag); + OPT_FLAG(permit_x11_forwarding_flag); + OPT_FLAG(permit_pty_flag); + OPT_FLAG(permit_user_rc); + OPT_FLAG(restricted); + OPT_FLAG(cert_authority); + OPT_FLAG(no_require_user_presence); +#undef OPT_FLAG + + /* Simple integer options */ + if ((r = sshbuf_get_u64(m, &opts->valid_before)) != 0) + goto out; + + /* tunnel number can be negative to indicate "unset" */ + if ((r = sshbuf_get_u8(m, &f)) != 0 || + (r = sshbuf_get_u32(m, &tmp)) != 0) + goto out; + opts->force_tun_device = f ? -1 : (int)tmp; + + /* String options may be NULL */ + if ((r = deserialise_nullable_string(m, &opts->cert_principals)) != 0 || + (r = deserialise_nullable_string(m, &opts->force_command)) != 0 || + (r = deserialise_nullable_string(m, + &opts->required_from_host_cert)) != 0 || + (r = deserialise_nullable_string(m, + &opts->required_from_host_keys)) != 0) + goto out; + + /* Array options */ + if ((r = deserialise_array(m, &opts->env, &opts->nenv)) != 0 || + (r = deserialise_array(m, + &opts->permitopen, &opts->npermitopen)) != 0 || + (r = deserialise_array(m, + &opts->permitlisten, &opts->npermitlisten)) != 0) + goto out; + + /* success */ + r = 0; + *optsp = opts; + opts = NULL; + out: + sshauthopt_free(opts); + return r; +} |