]> git.ipfire.org Git - thirdparty/openssh-portable.git/commitdiff
upstream: Introduce a new API for handling authorized_keys options.
authordjm@openbsd.org <djm@openbsd.org>
Sat, 3 Mar 2018 03:06:02 +0000 (03:06 +0000)
committerDamien Miller <djm@mindrot.org>
Sat, 3 Mar 2018 03:22:11 +0000 (14:22 +1100)
This API parses options to a dedicated structure rather than the old API's
approach of setting global state. It also includes support for merging
options, e.g. from authorized_keys, authorized_principals and/or
certificates.

feedback and ok markus@

OpenBSD-Commit-ID: 98badda102cd575210d7802943e93a34232c80a2

auth-options.c
auth-options.h

index bed00eef0fe3619b982a06dce82f3fe894f2eb00..8b93b51e50c35c1ad5eb6351f2c3261ef95a4b3e 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth-options.c,v 1.74 2017/09/12 06:32:07 djm Exp $ */
+/* $OpenBSD: auth-options.c,v 1.75 2018/03/03 03:06:02 djm Exp $ */
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
  * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
@@ -9,6 +9,21 @@
  * 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"
 
@@ -19,6 +34,8 @@
 #include <string.h>
 #include <stdio.h>
 #include <stdarg.h>
+#include <ctype.h>
+#include <limits.h>
 
 #include "openbsd-compat/sys-queue.h"
 
@@ -27,6 +44,7 @@
 #include "xmalloc.h"
 #include "match.h"
 #include "ssherr.h"
+#include "ssh2.h"
 #include "log.h"
 #include "canohost.h"
 #include "packet.h"
@@ -661,3 +679,812 @@ auth_cert_options(struct sshkey *k, struct passwd *pw, const char **reason)
        return 0;
 }
 
+/*
+ * authorized_keys options processing.
+ */
+
+/*
+ * Match flag 'opt' in *optsp, and if allow_negate is set then also match
+ * 'no-opt'. Returns -1 if option not matched, 1 if option matches or 0
+ * if negated option matches.
+ * If the option or negated option matches, then *optsp is updated to
+ * point to the first character after the option.
+ */
+static int
+opt_flag(const char *opt, int allow_negate, const char **optsp)
+{
+       size_t opt_len = strlen(opt);
+       const char *opts = *optsp;
+       int negate = 0;
+
+       if (allow_negate && strncasecmp(opts, "no-", 3) == 0) {
+               opts += 3;
+               negate = 1;
+       }
+       if (strncasecmp(opts, opt, opt_len) == 0) {
+               *optsp = opts + opt_len;
+               return negate ? 0 : 1;
+       }
+       return -1;
+}
+
+static char *
+opt_dequote(const char **sp, const char **errstrp)
+{
+       const char *s = *sp;
+       char *ret;
+       size_t i;
+
+       *errstrp = NULL;
+       if (*s != '"') {
+               *errstrp = "missing start quote";
+               return NULL;
+       }
+       s++;
+       if ((ret = malloc(strlen((s)) + 1)) == NULL) {
+               *errstrp = "memory allocation failed";
+               return NULL;
+       }
+       for (i = 0; *s != '\0' && *s != '"';) {
+               if (s[0] == '\\' && s[1] == '"')
+                       s++;
+               ret[i++] = *s++;
+       }
+       if (*s == '\0') {
+               *errstrp = "missing end quote";
+               free(ret);
+               return NULL;
+       }
+       ret[i] = '\0';
+       s++;
+       *sp = s;
+       return ret;
+}
+
+static int
+opt_match(const char **opts, const char *term)
+{
+       if (strncasecmp((*opts), term, strlen(term)) == 0 &&
+           (*opts)[strlen(term)] == '=') {
+               *opts += strlen(term) + 1;
+               return 1;
+       }
+       return 0;
+}
+
+static int
+dup_strings(char ***dstp, size_t *ndstp, char **src, size_t nsrc)
+{
+       char **dst;
+       size_t i, j;
+
+       *dstp = NULL;
+       *ndstp = 0;
+       if (nsrc == 0)
+               return 0;
+
+       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;
+               }
+       }
+       /* success */
+       *dstp = dst;
+       *ndstp = nsrc;
+       return 0;
+}
+
+#define OPTIONS_CRITICAL       1
+#define OPTIONS_EXTENSIONS     2
+static int
+cert_option_list(struct sshauthopt *opts, struct sshbuf *oblob,
+    u_int which, int crit)
+{
+       char *command, *allowed;
+       char *name = NULL;
+       struct sshbuf *c = NULL, *data = NULL;
+       int r, ret = -1, found;
+
+       if ((c = sshbuf_fromb(oblob)) == NULL) {
+               error("%s: sshbuf_fromb failed", __func__);
+               goto out;
+       }
+
+       while (sshbuf_len(c) > 0) {
+               sshbuf_free(data);
+               data = NULL;
+               if ((r = sshbuf_get_cstring(c, &name, NULL)) != 0 ||
+                   (r = sshbuf_froms(c, &data)) != 0) {
+                       error("Unable to parse certificate options: %s",
+                           ssh_err(r));
+                       goto out;
+               }
+               debug3("found certificate option \"%.100s\" len %zu",
+                   name, sshbuf_len(data));
+               found = 0;
+               if ((which & OPTIONS_EXTENSIONS) != 0) {
+                       if (strcmp(name, "permit-X11-forwarding") == 0) {
+                               opts->permit_x11_forwarding_flag = 1;
+                               found = 1;
+                       } else if (strcmp(name,
+                           "permit-agent-forwarding") == 0) {
+                               opts->permit_agent_forwarding_flag = 1;
+                               found = 1;
+                       } else if (strcmp(name,
+                           "permit-port-forwarding") == 0) {
+                               opts->permit_port_forwarding_flag = 1;
+                               found = 1;
+                       } else if (strcmp(name, "permit-pty") == 0) {
+                               opts->permit_pty_flag = 1;
+                               found = 1;
+                       } else if (strcmp(name, "permit-user-rc") == 0) {
+                               opts->permit_user_rc = 1;
+                               found = 1;
+                       }
+               }
+               if (!found && (which & OPTIONS_CRITICAL) != 0) {
+                       if (strcmp(name, "force-command") == 0) {
+                               if ((r = sshbuf_get_cstring(data, &command,
+                                   NULL)) != 0) {
+                                       error("Unable to parse \"%s\" "
+                                           "section: %s", name, ssh_err(r));
+                                       goto out;
+                               }
+                               if (opts->force_command != NULL) {
+                                       error("Certificate has multiple "
+                                           "force-command options");
+                                       free(command);
+                                       goto out;
+                               }
+                               opts->force_command = command;
+                               found = 1;
+                       }
+                       if (strcmp(name, "source-address") == 0) {
+                               if ((r = sshbuf_get_cstring(data, &allowed,
+                                   NULL)) != 0) {
+                                       error("Unable to parse \"%s\" "
+                                           "section: %s", name, ssh_err(r));
+                                       goto out;
+                               }
+                               if (opts->required_from_host_cert != NULL) {
+                                       error("Certificate has multiple "
+                                           "source-address options");
+                                       free(allowed);
+                                       goto out;
+                               }
+                               /* 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;
+                       }
+               }
+
+               if (!found) {
+                       if (crit) {
+                               error("Certificate critical option \"%s\" "
+                                   "is not supported", name);
+                               goto out;
+                       } else {
+                               logit("Certificate extension \"%s\" "
+                                   "is not supported", name);
+                       }
+               } else if (sshbuf_len(data) != 0) {
+                       error("Certificate option \"%s\" corrupt "
+                           "(extra data)", name);
+                       goto out;
+               }
+               free(name);
+               name = NULL;
+       }
+       /* successfully parsed all options */
+       ret = 0;
+
+ out:
+       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);
+
+       explicit_bzero(opts, sizeof(*opts));
+       free(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;
+}
+
+struct sshauthopt *
+sshauthopt_parse(const char *opts, const char **errstrp)
+{
+       char **oarray, *opt, *cp, *tmp, *host;
+       int r;
+       struct sshauthopt *ret = NULL;
+       const char *errstr = "unknown error";
+
+       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("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, "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;
+                       }
+                       for (cp = opt; cp < tmp; cp++) {
+                               if (!isalnum((u_char)*cp)) {
+                                       free(opt);
+                                       errstr = "invalid environment string";
+                                       goto fail;
+                               }
+                       }
+                       /* 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 (ret->npermitopen > INT_MAX) {
+                               errstr = "too many permitopens";
+                               goto fail;
+                       }
+                       if ((opt = opt_dequote(&opts, &errstr)) == NULL)
+                               goto fail;
+                       if ((tmp = strdup(opt)) == NULL) {
+                               free(opt);
+                               goto alloc_fail;
+                       }
+                       cp = tmp;
+                       /* validate syntax of permitopen before recording it. */
+                       host = hpdelim(&cp);
+                       if (host == NULL || strlen(host) >= NI_MAXHOST) {
+                               free(tmp);
+                               free(opt);
+                               errstr = "invalid permitopen hostname";
+                               goto fail;
+                       }
+                       /*
+                        * 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);
+                               errstr = "invalid permitopen port";
+                               goto fail;
+                       }
+                       /* XXX - add streamlocal support */
+                       free(tmp);
+                       /* Record it */
+                       oarray = ret->permitopen;
+                       if ((ret->permitopen = recallocarray(ret->permitopen,
+                           ret->npermitopen, ret->npermitopen + 1,
+                           sizeof(*ret->permitopen))) == NULL) {
+                               free(opt);
+                               ret->permitopen = oarray;
+                               goto alloc_fail;
+                       }
+                       ret->permitopen[ret->npermitopen++] = opt;
+               } 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 and environment 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;
+       }
+
+       /* Flags are logical-AND (i.e. must be set in both for permission) */
+#define OPTFLAG(x) ret->x = (primary->x == 1) && (additional->x == 1)
+       OPTFLAG(permit_port_forwarding_flag);
+       OPTFLAG(permit_agent_forwarding_flag);
+       OPTFLAG(permit_x11_forwarding_flag);
+       OPTFLAG(permit_pty_flag);
+       OPTFLAG(permit_user_rc);
+#undef OPTFLAG
+
+       /*
+        * 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);
+#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) {
+               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;
+}
+
+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:
+       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)
+               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)
+               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;
+
+#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);
+#undef OPT_FLAG
+
+       /* 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)
+               goto out;
+
+       /* success */
+       r = 0;
+       *optsp = opts;
+       opts = NULL;
+ out:
+       sshauthopt_free(opts);
+       return r;
+}
index 547f016355a98698154cef054a5e0de30a7bb6c8..0dbfc325e834345293796a0403f664dbcf1a52fb 100644 (file)
@@ -1,4 +1,4 @@
-/* $OpenBSD: auth-options.h,v 1.23 2017/05/31 10:54:00 markus Exp $ */
+/* $OpenBSD: auth-options.h,v 1.24 2018/03/03 03:06:02 djm Exp $ */
 
 /*
  * Author: Tatu Ylonen <ylo@cs.hut.fi>
@@ -15,6 +15,9 @@
 #ifndef AUTH_OPTIONS_H
 #define AUTH_OPTIONS_H
 
+struct passwd;
+struct sshkey;
+
 /* Linked list of custom environment strings */
 struct envstring {
        struct envstring *next;
@@ -37,4 +40,69 @@ int  auth_parse_options(struct passwd *, char *, const char *, u_long);
 void   auth_clear_options(void);
 int    auth_cert_options(struct sshkey *, struct passwd *, const char **);
 
+/* authorized_keys options handling */
+
+/*
+ * sshauthopt represents key options parsed from authorized_keys or
+ * from certificate extensions/options.
+ */
+struct sshauthopt {
+       /* Feature flags */
+       int permit_port_forwarding_flag;
+       int permit_agent_forwarding_flag;
+       int permit_x11_forwarding_flag;
+       int permit_pty_flag;
+       int permit_user_rc;
+
+       /* "restrict" keyword was invoked */
+       int restricted;
+
+       /* Certificate-related options */
+       int cert_authority;
+       char *cert_principals;
+
+       int force_tun_device;
+       char *force_command;
+
+       /* Custom environment */
+       size_t nenv;
+       char **env;
+
+       /* Permitted port forwardings */
+       size_t npermitopen;
+       char **permitopen;
+
+       /*
+        * Permitted host/addresses (comma-separated)
+        * Caller must check source address matches both lists (if present).
+        */
+       char *required_from_host_cert;
+       char *required_from_host_keys;
+};
+
+struct sshauthopt *sshauthopt_new(void);
+struct sshauthopt *sshauthopt_new_with_keys_defaults(void);
+void sshauthopt_free(struct sshauthopt *opts);
+struct sshauthopt *sshauthopt_copy(const struct sshauthopt *orig);
+int sshauthopt_serialise(const struct sshauthopt *opts, struct sshbuf *m, int);
+int sshauthopt_deserialise(struct sshbuf *m, struct sshauthopt **opts);
+
+/*
+ * Parse authorized_keys options. Returns an options structure on success
+ * or NULL on failure. Will set errstr on failure.
+ */
+struct sshauthopt *sshauthopt_parse(const char *s, const char **errstr);
+
+/*
+ * Parse certification options to a struct sshauthopt.
+ * Returns options on success or NULL on failure.
+ */
+struct sshauthopt *sshauthopt_from_cert(struct sshkey *k);
+
+/*
+ * Merge key options.
+ */
+struct sshauthopt *sshauthopt_merge(const struct sshauthopt *primary,
+    const struct sshauthopt *additional, const char **errstrp);
+
 #endif