1*2f513db7SEd Maste /* $OpenBSD: auth-options.c,v 1.84 2018/10/03 06:38:35 djm Exp $ */ 2b66f2d16SKris Kennaway /* 347dd1d1bSDag-Erling Smørgrav * Copyright (c) 2018 Damien Miller <djm@mindrot.org> 447dd1d1bSDag-Erling Smørgrav * 547dd1d1bSDag-Erling Smørgrav * Permission to use, copy, modify, and distribute this software for any 647dd1d1bSDag-Erling Smørgrav * purpose with or without fee is hereby granted, provided that the above 747dd1d1bSDag-Erling Smørgrav * copyright notice and this permission notice appear in all copies. 847dd1d1bSDag-Erling Smørgrav * 947dd1d1bSDag-Erling Smørgrav * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1047dd1d1bSDag-Erling Smørgrav * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1147dd1d1bSDag-Erling Smørgrav * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1247dd1d1bSDag-Erling Smørgrav * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1347dd1d1bSDag-Erling Smørgrav * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1447dd1d1bSDag-Erling Smørgrav * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1547dd1d1bSDag-Erling Smørgrav * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16b66f2d16SKris Kennaway */ 17b66f2d16SKris Kennaway 18b66f2d16SKris Kennaway #include "includes.h" 19761efaa7SDag-Erling Smørgrav 20761efaa7SDag-Erling Smørgrav #include <sys/types.h> 21761efaa7SDag-Erling Smørgrav 22761efaa7SDag-Erling Smørgrav #include <netdb.h> 23761efaa7SDag-Erling Smørgrav #include <pwd.h> 24761efaa7SDag-Erling Smørgrav #include <string.h> 25761efaa7SDag-Erling Smørgrav #include <stdio.h> 26761efaa7SDag-Erling Smørgrav #include <stdarg.h> 2747dd1d1bSDag-Erling Smørgrav #include <ctype.h> 2847dd1d1bSDag-Erling Smørgrav #include <limits.h> 29b66f2d16SKris Kennaway 30d4af9e69SDag-Erling Smørgrav #include "openbsd-compat/sys-queue.h" 31bc5531deSDag-Erling Smørgrav 32b66f2d16SKris Kennaway #include "xmalloc.h" 33bc5531deSDag-Erling Smørgrav #include "ssherr.h" 341e8db6e2SBrian Feldman #include "log.h" 35bc5531deSDag-Erling Smørgrav #include "sshbuf.h" 36a0ee8cc6SDag-Erling Smørgrav #include "misc.h" 37bc5531deSDag-Erling Smørgrav #include "sshkey.h" 3847dd1d1bSDag-Erling Smørgrav #include "match.h" 3947dd1d1bSDag-Erling Smørgrav #include "ssh2.h" 40e2f6069cSDag-Erling Smørgrav #include "auth-options.h" 415b9b2fafSBrian Feldman 421e8db6e2SBrian Feldman /* 43acc1a9efSDag-Erling Smørgrav * Match flag 'opt' in *optsp, and if allow_negate is set then also match 44acc1a9efSDag-Erling Smørgrav * 'no-opt'. Returns -1 if option not matched, 1 if option matches or 0 45acc1a9efSDag-Erling Smørgrav * if negated option matches. 46acc1a9efSDag-Erling Smørgrav * If the option or negated option matches, then *optsp is updated to 4747dd1d1bSDag-Erling Smørgrav * point to the first character after the option. 48acc1a9efSDag-Erling Smørgrav */ 49acc1a9efSDag-Erling Smørgrav static int 5047dd1d1bSDag-Erling Smørgrav opt_flag(const char *opt, int allow_negate, const char **optsp) 51acc1a9efSDag-Erling Smørgrav { 52acc1a9efSDag-Erling Smørgrav size_t opt_len = strlen(opt); 5347dd1d1bSDag-Erling Smørgrav const char *opts = *optsp; 54acc1a9efSDag-Erling Smørgrav int negate = 0; 55acc1a9efSDag-Erling Smørgrav 56acc1a9efSDag-Erling Smørgrav if (allow_negate && strncasecmp(opts, "no-", 3) == 0) { 57acc1a9efSDag-Erling Smørgrav opts += 3; 58acc1a9efSDag-Erling Smørgrav negate = 1; 59acc1a9efSDag-Erling Smørgrav } 60acc1a9efSDag-Erling Smørgrav if (strncasecmp(opts, opt, opt_len) == 0) { 61acc1a9efSDag-Erling Smørgrav *optsp = opts + opt_len; 62acc1a9efSDag-Erling Smørgrav return negate ? 0 : 1; 63acc1a9efSDag-Erling Smørgrav } 64acc1a9efSDag-Erling Smørgrav return -1; 65acc1a9efSDag-Erling Smørgrav } 66acc1a9efSDag-Erling Smørgrav 6747dd1d1bSDag-Erling Smørgrav static char * 6847dd1d1bSDag-Erling Smørgrav opt_dequote(const char **sp, const char **errstrp) 69b66f2d16SKris Kennaway { 7047dd1d1bSDag-Erling Smørgrav const char *s = *sp; 7147dd1d1bSDag-Erling Smørgrav char *ret; 7247dd1d1bSDag-Erling Smørgrav size_t i; 735b9b2fafSBrian Feldman 7447dd1d1bSDag-Erling Smørgrav *errstrp = NULL; 7547dd1d1bSDag-Erling Smørgrav if (*s != '"') { 7647dd1d1bSDag-Erling Smørgrav *errstrp = "missing start quote"; 7747dd1d1bSDag-Erling Smørgrav return NULL; 7847dd1d1bSDag-Erling Smørgrav } 7947dd1d1bSDag-Erling Smørgrav s++; 8047dd1d1bSDag-Erling Smørgrav if ((ret = malloc(strlen((s)) + 1)) == NULL) { 8147dd1d1bSDag-Erling Smørgrav *errstrp = "memory allocation failed"; 8247dd1d1bSDag-Erling Smørgrav return NULL; 8347dd1d1bSDag-Erling Smørgrav } 8447dd1d1bSDag-Erling Smørgrav for (i = 0; *s != '\0' && *s != '"';) { 8547dd1d1bSDag-Erling Smørgrav if (s[0] == '\\' && s[1] == '"') 8647dd1d1bSDag-Erling Smørgrav s++; 8747dd1d1bSDag-Erling Smørgrav ret[i++] = *s++; 8847dd1d1bSDag-Erling Smørgrav } 8947dd1d1bSDag-Erling Smørgrav if (*s == '\0') { 9047dd1d1bSDag-Erling Smørgrav *errstrp = "missing end quote"; 9147dd1d1bSDag-Erling Smørgrav free(ret); 9247dd1d1bSDag-Erling Smørgrav return NULL; 9347dd1d1bSDag-Erling Smørgrav } 9447dd1d1bSDag-Erling Smørgrav ret[i] = '\0'; 9547dd1d1bSDag-Erling Smørgrav s++; 9647dd1d1bSDag-Erling Smørgrav *sp = s; 9747dd1d1bSDag-Erling Smørgrav return ret; 9847dd1d1bSDag-Erling Smørgrav } 995b9b2fafSBrian Feldman 10047dd1d1bSDag-Erling Smørgrav static int 10147dd1d1bSDag-Erling Smørgrav opt_match(const char **opts, const char *term) 10247dd1d1bSDag-Erling Smørgrav { 10347dd1d1bSDag-Erling Smørgrav if (strncasecmp((*opts), term, strlen(term)) == 0 && 10447dd1d1bSDag-Erling Smørgrav (*opts)[strlen(term)] == '=') { 10547dd1d1bSDag-Erling Smørgrav *opts += strlen(term) + 1; 1061e8db6e2SBrian Feldman return 1; 107b15c8340SDag-Erling Smørgrav } 108b66f2d16SKris Kennaway return 0; 109b66f2d16SKris Kennaway } 1101e8db6e2SBrian Feldman 11147dd1d1bSDag-Erling Smørgrav static int 11247dd1d1bSDag-Erling Smørgrav dup_strings(char ***dstp, size_t *ndstp, char **src, size_t nsrc) 11347dd1d1bSDag-Erling Smørgrav { 11447dd1d1bSDag-Erling Smørgrav char **dst; 11547dd1d1bSDag-Erling Smørgrav size_t i, j; 116545d5ecaSDag-Erling Smørgrav 11747dd1d1bSDag-Erling Smørgrav *dstp = NULL; 11847dd1d1bSDag-Erling Smørgrav *ndstp = 0; 11947dd1d1bSDag-Erling Smørgrav if (nsrc == 0) 12047dd1d1bSDag-Erling Smørgrav return 0; 121b66f2d16SKris Kennaway 12247dd1d1bSDag-Erling Smørgrav if ((dst = calloc(nsrc, sizeof(*src))) == NULL) 12347dd1d1bSDag-Erling Smørgrav return -1; 12447dd1d1bSDag-Erling Smørgrav for (i = 0; i < nsrc; i++) { 12547dd1d1bSDag-Erling Smørgrav if ((dst[i] = strdup(src[i])) == NULL) { 12647dd1d1bSDag-Erling Smørgrav for (j = 0; j < i; j++) 12747dd1d1bSDag-Erling Smørgrav free(dst[j]); 12847dd1d1bSDag-Erling Smørgrav free(dst); 12947dd1d1bSDag-Erling Smørgrav return -1; 13047dd1d1bSDag-Erling Smørgrav } 13147dd1d1bSDag-Erling Smørgrav } 13247dd1d1bSDag-Erling Smørgrav /* success */ 13347dd1d1bSDag-Erling Smørgrav *dstp = dst; 13447dd1d1bSDag-Erling Smørgrav *ndstp = nsrc; 135b66f2d16SKris Kennaway return 0; 136b66f2d16SKris Kennaway } 137b15c8340SDag-Erling Smørgrav 138e2f6069cSDag-Erling Smørgrav #define OPTIONS_CRITICAL 1 139e2f6069cSDag-Erling Smørgrav #define OPTIONS_EXTENSIONS 2 140e2f6069cSDag-Erling Smørgrav static int 14147dd1d1bSDag-Erling Smørgrav cert_option_list(struct sshauthopt *opts, struct sshbuf *oblob, 14247dd1d1bSDag-Erling Smørgrav u_int which, int crit) 143b15c8340SDag-Erling Smørgrav { 144e2f6069cSDag-Erling Smørgrav char *command, *allowed; 145e4a9863fSDag-Erling Smørgrav char *name = NULL; 146bc5531deSDag-Erling Smørgrav struct sshbuf *c = NULL, *data = NULL; 14747dd1d1bSDag-Erling Smørgrav int r, ret = -1, found; 148b15c8340SDag-Erling Smørgrav 149bc5531deSDag-Erling Smørgrav if ((c = sshbuf_fromb(oblob)) == NULL) { 150bc5531deSDag-Erling Smørgrav error("%s: sshbuf_fromb failed", __func__); 151b15c8340SDag-Erling Smørgrav goto out; 152b15c8340SDag-Erling Smørgrav } 153bc5531deSDag-Erling Smørgrav 154bc5531deSDag-Erling Smørgrav while (sshbuf_len(c) > 0) { 155bc5531deSDag-Erling Smørgrav sshbuf_free(data); 156bc5531deSDag-Erling Smørgrav data = NULL; 157bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_cstring(c, &name, NULL)) != 0 || 158bc5531deSDag-Erling Smørgrav (r = sshbuf_froms(c, &data)) != 0) { 159bc5531deSDag-Erling Smørgrav error("Unable to parse certificate options: %s", 160bc5531deSDag-Erling Smørgrav ssh_err(r)); 161bc5531deSDag-Erling Smørgrav goto out; 162bc5531deSDag-Erling Smørgrav } 163bc5531deSDag-Erling Smørgrav debug3("found certificate option \"%.100s\" len %zu", 164bc5531deSDag-Erling Smørgrav name, sshbuf_len(data)); 165e2f6069cSDag-Erling Smørgrav found = 0; 166e2f6069cSDag-Erling Smørgrav if ((which & OPTIONS_EXTENSIONS) != 0) { 167e2f6069cSDag-Erling Smørgrav if (strcmp(name, "permit-X11-forwarding") == 0) { 16847dd1d1bSDag-Erling Smørgrav opts->permit_x11_forwarding_flag = 1; 169e2f6069cSDag-Erling Smørgrav found = 1; 170e2f6069cSDag-Erling Smørgrav } else if (strcmp(name, 171e2f6069cSDag-Erling Smørgrav "permit-agent-forwarding") == 0) { 17247dd1d1bSDag-Erling Smørgrav opts->permit_agent_forwarding_flag = 1; 173e2f6069cSDag-Erling Smørgrav found = 1; 174e2f6069cSDag-Erling Smørgrav } else if (strcmp(name, 175e2f6069cSDag-Erling Smørgrav "permit-port-forwarding") == 0) { 17647dd1d1bSDag-Erling Smørgrav opts->permit_port_forwarding_flag = 1; 177e2f6069cSDag-Erling Smørgrav found = 1; 178e2f6069cSDag-Erling Smørgrav } else if (strcmp(name, "permit-pty") == 0) { 17947dd1d1bSDag-Erling Smørgrav opts->permit_pty_flag = 1; 180e2f6069cSDag-Erling Smørgrav found = 1; 181e2f6069cSDag-Erling Smørgrav } else if (strcmp(name, "permit-user-rc") == 0) { 18247dd1d1bSDag-Erling Smørgrav opts->permit_user_rc = 1; 183e2f6069cSDag-Erling Smørgrav found = 1; 184e2f6069cSDag-Erling Smørgrav } 185e2f6069cSDag-Erling Smørgrav } 186e2f6069cSDag-Erling Smørgrav if (!found && (which & OPTIONS_CRITICAL) != 0) { 187e2f6069cSDag-Erling Smørgrav if (strcmp(name, "force-command") == 0) { 188bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_cstring(data, &command, 189bc5531deSDag-Erling Smørgrav NULL)) != 0) { 190bc5531deSDag-Erling Smørgrav error("Unable to parse \"%s\" " 191bc5531deSDag-Erling Smørgrav "section: %s", name, ssh_err(r)); 192b15c8340SDag-Erling Smørgrav goto out; 193b15c8340SDag-Erling Smørgrav } 19447dd1d1bSDag-Erling Smørgrav if (opts->force_command != NULL) { 195b15c8340SDag-Erling Smørgrav error("Certificate has multiple " 196e2f6069cSDag-Erling Smørgrav "force-command options"); 197e4a9863fSDag-Erling Smørgrav free(command); 198b15c8340SDag-Erling Smørgrav goto out; 199b15c8340SDag-Erling Smørgrav } 20047dd1d1bSDag-Erling Smørgrav opts->force_command = command; 201e2f6069cSDag-Erling Smørgrav found = 1; 202e2f6069cSDag-Erling Smørgrav } 203e2f6069cSDag-Erling Smørgrav if (strcmp(name, "source-address") == 0) { 204bc5531deSDag-Erling Smørgrav if ((r = sshbuf_get_cstring(data, &allowed, 205bc5531deSDag-Erling Smørgrav NULL)) != 0) { 206bc5531deSDag-Erling Smørgrav error("Unable to parse \"%s\" " 207bc5531deSDag-Erling Smørgrav "section: %s", name, ssh_err(r)); 208b15c8340SDag-Erling Smørgrav goto out; 209b15c8340SDag-Erling Smørgrav } 21047dd1d1bSDag-Erling Smørgrav if (opts->required_from_host_cert != NULL) { 211b15c8340SDag-Erling Smørgrav error("Certificate has multiple " 212e2f6069cSDag-Erling Smørgrav "source-address options"); 213e4a9863fSDag-Erling Smørgrav free(allowed); 214b15c8340SDag-Erling Smørgrav goto out; 215b15c8340SDag-Erling Smørgrav } 21647dd1d1bSDag-Erling Smørgrav /* Check syntax */ 21747dd1d1bSDag-Erling Smørgrav if (addr_match_cidr_list(NULL, allowed) == -1) { 218e2f6069cSDag-Erling Smørgrav error("Certificate source-address " 219e2f6069cSDag-Erling Smørgrav "contents invalid"); 220b15c8340SDag-Erling Smørgrav goto out; 221b15c8340SDag-Erling Smørgrav } 22247dd1d1bSDag-Erling Smørgrav opts->required_from_host_cert = allowed; 223e2f6069cSDag-Erling Smørgrav found = 1; 224e2f6069cSDag-Erling Smørgrav } 225b15c8340SDag-Erling Smørgrav } 226b15c8340SDag-Erling Smørgrav 227e2f6069cSDag-Erling Smørgrav if (!found) { 228e2f6069cSDag-Erling Smørgrav if (crit) { 229e2f6069cSDag-Erling Smørgrav error("Certificate critical option \"%s\" " 230e2f6069cSDag-Erling Smørgrav "is not supported", name); 231e2f6069cSDag-Erling Smørgrav goto out; 232e2f6069cSDag-Erling Smørgrav } else { 233e2f6069cSDag-Erling Smørgrav logit("Certificate extension \"%s\" " 234e2f6069cSDag-Erling Smørgrav "is not supported", name); 235e2f6069cSDag-Erling Smørgrav } 236bc5531deSDag-Erling Smørgrav } else if (sshbuf_len(data) != 0) { 237e2f6069cSDag-Erling Smørgrav error("Certificate option \"%s\" corrupt " 238b15c8340SDag-Erling Smørgrav "(extra data)", name); 239b15c8340SDag-Erling Smørgrav goto out; 240b15c8340SDag-Erling Smørgrav } 241e4a9863fSDag-Erling Smørgrav free(name); 242e4a9863fSDag-Erling Smørgrav name = NULL; 243b15c8340SDag-Erling Smørgrav } 244e2f6069cSDag-Erling Smørgrav /* successfully parsed all options */ 245b15c8340SDag-Erling Smørgrav ret = 0; 246b15c8340SDag-Erling Smørgrav 247e2f6069cSDag-Erling Smørgrav out: 248e4a9863fSDag-Erling Smørgrav free(name); 249bc5531deSDag-Erling Smørgrav sshbuf_free(data); 250bc5531deSDag-Erling Smørgrav sshbuf_free(c); 251e2f6069cSDag-Erling Smørgrav return ret; 252e2f6069cSDag-Erling Smørgrav } 253e2f6069cSDag-Erling Smørgrav 25447dd1d1bSDag-Erling Smørgrav struct sshauthopt * 25547dd1d1bSDag-Erling Smørgrav sshauthopt_new(void) 256e2f6069cSDag-Erling Smørgrav { 25747dd1d1bSDag-Erling Smørgrav struct sshauthopt *ret; 258e2f6069cSDag-Erling Smørgrav 25947dd1d1bSDag-Erling Smørgrav if ((ret = calloc(1, sizeof(*ret))) == NULL) 26047dd1d1bSDag-Erling Smørgrav return NULL; 26147dd1d1bSDag-Erling Smørgrav ret->force_tun_device = -1; 26247dd1d1bSDag-Erling Smørgrav return ret; 263b15c8340SDag-Erling Smørgrav } 26447dd1d1bSDag-Erling Smørgrav 26547dd1d1bSDag-Erling Smørgrav void 26647dd1d1bSDag-Erling Smørgrav sshauthopt_free(struct sshauthopt *opts) 26747dd1d1bSDag-Erling Smørgrav { 26847dd1d1bSDag-Erling Smørgrav size_t i; 26947dd1d1bSDag-Erling Smørgrav 27047dd1d1bSDag-Erling Smørgrav if (opts == NULL) 27147dd1d1bSDag-Erling Smørgrav return; 27247dd1d1bSDag-Erling Smørgrav 27347dd1d1bSDag-Erling Smørgrav free(opts->cert_principals); 27447dd1d1bSDag-Erling Smørgrav free(opts->force_command); 27547dd1d1bSDag-Erling Smørgrav free(opts->required_from_host_cert); 27647dd1d1bSDag-Erling Smørgrav free(opts->required_from_host_keys); 27747dd1d1bSDag-Erling Smørgrav 27847dd1d1bSDag-Erling Smørgrav for (i = 0; i < opts->nenv; i++) 27947dd1d1bSDag-Erling Smørgrav free(opts->env[i]); 28047dd1d1bSDag-Erling Smørgrav free(opts->env); 28147dd1d1bSDag-Erling Smørgrav 28247dd1d1bSDag-Erling Smørgrav for (i = 0; i < opts->npermitopen; i++) 28347dd1d1bSDag-Erling Smørgrav free(opts->permitopen[i]); 28447dd1d1bSDag-Erling Smørgrav free(opts->permitopen); 28547dd1d1bSDag-Erling Smørgrav 286190cef3dSDag-Erling Smørgrav for (i = 0; i < opts->npermitlisten; i++) 287190cef3dSDag-Erling Smørgrav free(opts->permitlisten[i]); 288190cef3dSDag-Erling Smørgrav free(opts->permitlisten); 289190cef3dSDag-Erling Smørgrav 29047dd1d1bSDag-Erling Smørgrav explicit_bzero(opts, sizeof(*opts)); 29147dd1d1bSDag-Erling Smørgrav free(opts); 29247dd1d1bSDag-Erling Smørgrav } 29347dd1d1bSDag-Erling Smørgrav 29447dd1d1bSDag-Erling Smørgrav struct sshauthopt * 29547dd1d1bSDag-Erling Smørgrav sshauthopt_new_with_keys_defaults(void) 29647dd1d1bSDag-Erling Smørgrav { 29747dd1d1bSDag-Erling Smørgrav struct sshauthopt *ret = NULL; 29847dd1d1bSDag-Erling Smørgrav 29947dd1d1bSDag-Erling Smørgrav if ((ret = sshauthopt_new()) == NULL) 30047dd1d1bSDag-Erling Smørgrav return NULL; 30147dd1d1bSDag-Erling Smørgrav 30247dd1d1bSDag-Erling Smørgrav /* Defaults for authorized_keys flags */ 30347dd1d1bSDag-Erling Smørgrav ret->permit_port_forwarding_flag = 1; 30447dd1d1bSDag-Erling Smørgrav ret->permit_agent_forwarding_flag = 1; 30547dd1d1bSDag-Erling Smørgrav ret->permit_x11_forwarding_flag = 1; 30647dd1d1bSDag-Erling Smørgrav ret->permit_pty_flag = 1; 30747dd1d1bSDag-Erling Smørgrav ret->permit_user_rc = 1; 30847dd1d1bSDag-Erling Smørgrav return ret; 30947dd1d1bSDag-Erling Smørgrav } 31047dd1d1bSDag-Erling Smørgrav 311190cef3dSDag-Erling Smørgrav /* 312190cef3dSDag-Erling Smørgrav * Parse and record a permitopen/permitlisten directive. 313190cef3dSDag-Erling Smørgrav * Return 0 on success. Return -1 on failure and sets *errstrp to error reason. 314190cef3dSDag-Erling Smørgrav */ 315190cef3dSDag-Erling Smørgrav static int 316190cef3dSDag-Erling Smørgrav handle_permit(const char **optsp, int allow_bare_port, 317190cef3dSDag-Erling Smørgrav char ***permitsp, size_t *npermitsp, const char **errstrp) 318190cef3dSDag-Erling Smørgrav { 319190cef3dSDag-Erling Smørgrav char *opt, *tmp, *cp, *host, **permits = *permitsp; 320190cef3dSDag-Erling Smørgrav size_t npermits = *npermitsp; 321190cef3dSDag-Erling Smørgrav const char *errstr = "unknown error"; 322190cef3dSDag-Erling Smørgrav 323190cef3dSDag-Erling Smørgrav if (npermits > INT_MAX) { 324190cef3dSDag-Erling Smørgrav *errstrp = "too many permission directives"; 325190cef3dSDag-Erling Smørgrav return -1; 326190cef3dSDag-Erling Smørgrav } 327190cef3dSDag-Erling Smørgrav if ((opt = opt_dequote(optsp, &errstr)) == NULL) { 328190cef3dSDag-Erling Smørgrav return -1; 329190cef3dSDag-Erling Smørgrav } 330190cef3dSDag-Erling Smørgrav if (allow_bare_port && strchr(opt, ':') == NULL) { 331190cef3dSDag-Erling Smørgrav /* 332190cef3dSDag-Erling Smørgrav * Allow a bare port number in permitlisten to indicate a 333190cef3dSDag-Erling Smørgrav * listen_host wildcard. 334190cef3dSDag-Erling Smørgrav */ 335190cef3dSDag-Erling Smørgrav if (asprintf(&tmp, "*:%s", opt) < 0) { 336190cef3dSDag-Erling Smørgrav *errstrp = "memory allocation failed"; 337190cef3dSDag-Erling Smørgrav return -1; 338190cef3dSDag-Erling Smørgrav } 339190cef3dSDag-Erling Smørgrav free(opt); 340190cef3dSDag-Erling Smørgrav opt = tmp; 341190cef3dSDag-Erling Smørgrav } 342190cef3dSDag-Erling Smørgrav if ((tmp = strdup(opt)) == NULL) { 343190cef3dSDag-Erling Smørgrav free(opt); 344190cef3dSDag-Erling Smørgrav *errstrp = "memory allocation failed"; 345190cef3dSDag-Erling Smørgrav return -1; 346190cef3dSDag-Erling Smørgrav } 347190cef3dSDag-Erling Smørgrav cp = tmp; 348190cef3dSDag-Erling Smørgrav /* validate syntax before recording it. */ 349190cef3dSDag-Erling Smørgrav host = hpdelim(&cp); 350190cef3dSDag-Erling Smørgrav if (host == NULL || strlen(host) >= NI_MAXHOST) { 351190cef3dSDag-Erling Smørgrav free(tmp); 352190cef3dSDag-Erling Smørgrav free(opt); 353190cef3dSDag-Erling Smørgrav *errstrp = "invalid permission hostname"; 354190cef3dSDag-Erling Smørgrav return -1; 355190cef3dSDag-Erling Smørgrav } 356190cef3dSDag-Erling Smørgrav /* 357190cef3dSDag-Erling Smørgrav * don't want to use permitopen_port to avoid 358190cef3dSDag-Erling Smørgrav * dependency on channels.[ch] here. 359190cef3dSDag-Erling Smørgrav */ 360190cef3dSDag-Erling Smørgrav if (cp == NULL || 361190cef3dSDag-Erling Smørgrav (strcmp(cp, "*") != 0 && a2port(cp) <= 0)) { 362190cef3dSDag-Erling Smørgrav free(tmp); 363190cef3dSDag-Erling Smørgrav free(opt); 364190cef3dSDag-Erling Smørgrav *errstrp = "invalid permission port"; 365190cef3dSDag-Erling Smørgrav return -1; 366190cef3dSDag-Erling Smørgrav } 367190cef3dSDag-Erling Smørgrav /* XXX - add streamlocal support */ 368190cef3dSDag-Erling Smørgrav free(tmp); 369190cef3dSDag-Erling Smørgrav /* Record it */ 370190cef3dSDag-Erling Smørgrav if ((permits = recallocarray(permits, npermits, npermits + 1, 371190cef3dSDag-Erling Smørgrav sizeof(*permits))) == NULL) { 372190cef3dSDag-Erling Smørgrav free(opt); 373190cef3dSDag-Erling Smørgrav /* NB. don't update *permitsp if alloc fails */ 374190cef3dSDag-Erling Smørgrav *errstrp = "memory allocation failed"; 375190cef3dSDag-Erling Smørgrav return -1; 376190cef3dSDag-Erling Smørgrav } 377190cef3dSDag-Erling Smørgrav permits[npermits++] = opt; 378190cef3dSDag-Erling Smørgrav *permitsp = permits; 379190cef3dSDag-Erling Smørgrav *npermitsp = npermits; 380190cef3dSDag-Erling Smørgrav return 0; 381190cef3dSDag-Erling Smørgrav } 382190cef3dSDag-Erling Smørgrav 38347dd1d1bSDag-Erling Smørgrav struct sshauthopt * 38447dd1d1bSDag-Erling Smørgrav sshauthopt_parse(const char *opts, const char **errstrp) 38547dd1d1bSDag-Erling Smørgrav { 386190cef3dSDag-Erling Smørgrav char **oarray, *opt, *cp, *tmp; 38747dd1d1bSDag-Erling Smørgrav int r; 38847dd1d1bSDag-Erling Smørgrav struct sshauthopt *ret = NULL; 38947dd1d1bSDag-Erling Smørgrav const char *errstr = "unknown error"; 39047dd1d1bSDag-Erling Smørgrav uint64_t valid_before; 39147dd1d1bSDag-Erling Smørgrav 39247dd1d1bSDag-Erling Smørgrav if (errstrp != NULL) 39347dd1d1bSDag-Erling Smørgrav *errstrp = NULL; 39447dd1d1bSDag-Erling Smørgrav if ((ret = sshauthopt_new_with_keys_defaults()) == NULL) 39547dd1d1bSDag-Erling Smørgrav goto alloc_fail; 39647dd1d1bSDag-Erling Smørgrav 39747dd1d1bSDag-Erling Smørgrav if (opts == NULL) 39847dd1d1bSDag-Erling Smørgrav return ret; 39947dd1d1bSDag-Erling Smørgrav 40047dd1d1bSDag-Erling Smørgrav while (*opts && *opts != ' ' && *opts != '\t') { 40147dd1d1bSDag-Erling Smørgrav /* flag options */ 40247dd1d1bSDag-Erling Smørgrav if ((r = opt_flag("restrict", 0, &opts)) != -1) { 40347dd1d1bSDag-Erling Smørgrav ret->restricted = 1; 40447dd1d1bSDag-Erling Smørgrav ret->permit_port_forwarding_flag = 0; 40547dd1d1bSDag-Erling Smørgrav ret->permit_agent_forwarding_flag = 0; 40647dd1d1bSDag-Erling Smørgrav ret->permit_x11_forwarding_flag = 0; 40747dd1d1bSDag-Erling Smørgrav ret->permit_pty_flag = 0; 40847dd1d1bSDag-Erling Smørgrav ret->permit_user_rc = 0; 40947dd1d1bSDag-Erling Smørgrav } else if ((r = opt_flag("cert-authority", 0, &opts)) != -1) { 41047dd1d1bSDag-Erling Smørgrav ret->cert_authority = r; 41147dd1d1bSDag-Erling Smørgrav } else if ((r = opt_flag("port-forwarding", 1, &opts)) != -1) { 41247dd1d1bSDag-Erling Smørgrav ret->permit_port_forwarding_flag = r == 1; 41347dd1d1bSDag-Erling Smørgrav } else if ((r = opt_flag("agent-forwarding", 1, &opts)) != -1) { 41447dd1d1bSDag-Erling Smørgrav ret->permit_agent_forwarding_flag = r == 1; 41547dd1d1bSDag-Erling Smørgrav } else if ((r = opt_flag("x11-forwarding", 1, &opts)) != -1) { 41647dd1d1bSDag-Erling Smørgrav ret->permit_x11_forwarding_flag = r == 1; 41747dd1d1bSDag-Erling Smørgrav } else if ((r = opt_flag("pty", 1, &opts)) != -1) { 41847dd1d1bSDag-Erling Smørgrav ret->permit_pty_flag = r == 1; 41947dd1d1bSDag-Erling Smørgrav } else if ((r = opt_flag("user-rc", 1, &opts)) != -1) { 42047dd1d1bSDag-Erling Smørgrav ret->permit_user_rc = r == 1; 42147dd1d1bSDag-Erling Smørgrav } else if (opt_match(&opts, "command")) { 42247dd1d1bSDag-Erling Smørgrav if (ret->force_command != NULL) { 42347dd1d1bSDag-Erling Smørgrav errstr = "multiple \"command\" clauses"; 42447dd1d1bSDag-Erling Smørgrav goto fail; 42547dd1d1bSDag-Erling Smørgrav } 42647dd1d1bSDag-Erling Smørgrav ret->force_command = opt_dequote(&opts, &errstr); 42747dd1d1bSDag-Erling Smørgrav if (ret->force_command == NULL) 42847dd1d1bSDag-Erling Smørgrav goto fail; 42947dd1d1bSDag-Erling Smørgrav } else if (opt_match(&opts, "principals")) { 43047dd1d1bSDag-Erling Smørgrav if (ret->cert_principals != NULL) { 43147dd1d1bSDag-Erling Smørgrav errstr = "multiple \"principals\" clauses"; 43247dd1d1bSDag-Erling Smørgrav goto fail; 43347dd1d1bSDag-Erling Smørgrav } 43447dd1d1bSDag-Erling Smørgrav ret->cert_principals = opt_dequote(&opts, &errstr); 43547dd1d1bSDag-Erling Smørgrav if (ret->cert_principals == NULL) 43647dd1d1bSDag-Erling Smørgrav goto fail; 43747dd1d1bSDag-Erling Smørgrav } else if (opt_match(&opts, "from")) { 43847dd1d1bSDag-Erling Smørgrav if (ret->required_from_host_keys != NULL) { 43947dd1d1bSDag-Erling Smørgrav errstr = "multiple \"from\" clauses"; 44047dd1d1bSDag-Erling Smørgrav goto fail; 44147dd1d1bSDag-Erling Smørgrav } 44247dd1d1bSDag-Erling Smørgrav ret->required_from_host_keys = opt_dequote(&opts, 44347dd1d1bSDag-Erling Smørgrav &errstr); 44447dd1d1bSDag-Erling Smørgrav if (ret->required_from_host_keys == NULL) 44547dd1d1bSDag-Erling Smørgrav goto fail; 44647dd1d1bSDag-Erling Smørgrav } else if (opt_match(&opts, "expiry-time")) { 44747dd1d1bSDag-Erling Smørgrav if ((opt = opt_dequote(&opts, &errstr)) == NULL) 44847dd1d1bSDag-Erling Smørgrav goto fail; 44947dd1d1bSDag-Erling Smørgrav if (parse_absolute_time(opt, &valid_before) != 0 || 45047dd1d1bSDag-Erling Smørgrav valid_before == 0) { 45147dd1d1bSDag-Erling Smørgrav free(opt); 45247dd1d1bSDag-Erling Smørgrav errstr = "invalid expires time"; 45347dd1d1bSDag-Erling Smørgrav goto fail; 45447dd1d1bSDag-Erling Smørgrav } 45547dd1d1bSDag-Erling Smørgrav free(opt); 45647dd1d1bSDag-Erling Smørgrav if (ret->valid_before == 0 || 45747dd1d1bSDag-Erling Smørgrav valid_before < ret->valid_before) 45847dd1d1bSDag-Erling Smørgrav ret->valid_before = valid_before; 45947dd1d1bSDag-Erling Smørgrav } else if (opt_match(&opts, "environment")) { 46047dd1d1bSDag-Erling Smørgrav if (ret->nenv > INT_MAX) { 46147dd1d1bSDag-Erling Smørgrav errstr = "too many environment strings"; 46247dd1d1bSDag-Erling Smørgrav goto fail; 46347dd1d1bSDag-Erling Smørgrav } 46447dd1d1bSDag-Erling Smørgrav if ((opt = opt_dequote(&opts, &errstr)) == NULL) 46547dd1d1bSDag-Erling Smørgrav goto fail; 46647dd1d1bSDag-Erling Smørgrav /* env name must be alphanumeric and followed by '=' */ 46747dd1d1bSDag-Erling Smørgrav if ((tmp = strchr(opt, '=')) == NULL) { 46847dd1d1bSDag-Erling Smørgrav free(opt); 46947dd1d1bSDag-Erling Smørgrav errstr = "invalid environment string"; 47047dd1d1bSDag-Erling Smørgrav goto fail; 47147dd1d1bSDag-Erling Smørgrav } 472*2f513db7SEd Maste if ((cp = strdup(opt)) == NULL) 473*2f513db7SEd Maste goto alloc_fail; 474*2f513db7SEd Maste cp[tmp - opt] = '\0'; /* truncate at '=' */ 475*2f513db7SEd Maste if (!valid_env_name(cp)) { 476*2f513db7SEd Maste free(cp); 47747dd1d1bSDag-Erling Smørgrav free(opt); 47847dd1d1bSDag-Erling Smørgrav errstr = "invalid environment string"; 47947dd1d1bSDag-Erling Smørgrav goto fail; 48047dd1d1bSDag-Erling Smørgrav } 481*2f513db7SEd Maste free(cp); 48247dd1d1bSDag-Erling Smørgrav /* Append it. */ 48347dd1d1bSDag-Erling Smørgrav oarray = ret->env; 48447dd1d1bSDag-Erling Smørgrav if ((ret->env = recallocarray(ret->env, ret->nenv, 48547dd1d1bSDag-Erling Smørgrav ret->nenv + 1, sizeof(*ret->env))) == NULL) { 48647dd1d1bSDag-Erling Smørgrav free(opt); 48747dd1d1bSDag-Erling Smørgrav ret->env = oarray; /* put it back for cleanup */ 48847dd1d1bSDag-Erling Smørgrav goto alloc_fail; 48947dd1d1bSDag-Erling Smørgrav } 49047dd1d1bSDag-Erling Smørgrav ret->env[ret->nenv++] = opt; 49147dd1d1bSDag-Erling Smørgrav } else if (opt_match(&opts, "permitopen")) { 492190cef3dSDag-Erling Smørgrav if (handle_permit(&opts, 0, &ret->permitopen, 493190cef3dSDag-Erling Smørgrav &ret->npermitopen, &errstr) != 0) 49447dd1d1bSDag-Erling Smørgrav goto fail; 495190cef3dSDag-Erling Smørgrav } else if (opt_match(&opts, "permitlisten")) { 496190cef3dSDag-Erling Smørgrav if (handle_permit(&opts, 1, &ret->permitlisten, 497190cef3dSDag-Erling Smørgrav &ret->npermitlisten, &errstr) != 0) 49847dd1d1bSDag-Erling Smørgrav goto fail; 49947dd1d1bSDag-Erling Smørgrav } else if (opt_match(&opts, "tunnel")) { 50047dd1d1bSDag-Erling Smørgrav if ((opt = opt_dequote(&opts, &errstr)) == NULL) 50147dd1d1bSDag-Erling Smørgrav goto fail; 50247dd1d1bSDag-Erling Smørgrav ret->force_tun_device = a2tun(opt, NULL); 50347dd1d1bSDag-Erling Smørgrav free(opt); 50447dd1d1bSDag-Erling Smørgrav if (ret->force_tun_device == SSH_TUNID_ERR) { 50547dd1d1bSDag-Erling Smørgrav errstr = "invalid tun device"; 50647dd1d1bSDag-Erling Smørgrav goto fail; 50747dd1d1bSDag-Erling Smørgrav } 50847dd1d1bSDag-Erling Smørgrav } 50947dd1d1bSDag-Erling Smørgrav /* 51047dd1d1bSDag-Erling Smørgrav * Skip the comma, and move to the next option 51147dd1d1bSDag-Erling Smørgrav * (or break out if there are no more). 51247dd1d1bSDag-Erling Smørgrav */ 51347dd1d1bSDag-Erling Smørgrav if (*opts == '\0' || *opts == ' ' || *opts == '\t') 51447dd1d1bSDag-Erling Smørgrav break; /* End of options. */ 51547dd1d1bSDag-Erling Smørgrav /* Anything other than a comma is an unknown option */ 51647dd1d1bSDag-Erling Smørgrav if (*opts != ',') { 51747dd1d1bSDag-Erling Smørgrav errstr = "unknown key option"; 51847dd1d1bSDag-Erling Smørgrav goto fail; 51947dd1d1bSDag-Erling Smørgrav } 52047dd1d1bSDag-Erling Smørgrav opts++; 52147dd1d1bSDag-Erling Smørgrav if (*opts == '\0') { 52247dd1d1bSDag-Erling Smørgrav errstr = "unexpected end-of-options"; 52347dd1d1bSDag-Erling Smørgrav goto fail; 52447dd1d1bSDag-Erling Smørgrav } 52547dd1d1bSDag-Erling Smørgrav } 52647dd1d1bSDag-Erling Smørgrav 527ca86bcf2SDag-Erling Smørgrav /* success */ 52847dd1d1bSDag-Erling Smørgrav if (errstrp != NULL) 52947dd1d1bSDag-Erling Smørgrav *errstrp = NULL; 53047dd1d1bSDag-Erling Smørgrav return ret; 53147dd1d1bSDag-Erling Smørgrav 53247dd1d1bSDag-Erling Smørgrav alloc_fail: 53347dd1d1bSDag-Erling Smørgrav errstr = "memory allocation failed"; 53447dd1d1bSDag-Erling Smørgrav fail: 53547dd1d1bSDag-Erling Smørgrav sshauthopt_free(ret); 53647dd1d1bSDag-Erling Smørgrav if (errstrp != NULL) 53747dd1d1bSDag-Erling Smørgrav *errstrp = errstr; 53847dd1d1bSDag-Erling Smørgrav return NULL; 53947dd1d1bSDag-Erling Smørgrav } 54047dd1d1bSDag-Erling Smørgrav 54147dd1d1bSDag-Erling Smørgrav struct sshauthopt * 54247dd1d1bSDag-Erling Smørgrav sshauthopt_from_cert(struct sshkey *k) 54347dd1d1bSDag-Erling Smørgrav { 54447dd1d1bSDag-Erling Smørgrav struct sshauthopt *ret; 54547dd1d1bSDag-Erling Smørgrav 54647dd1d1bSDag-Erling Smørgrav if (k == NULL || !sshkey_type_is_cert(k->type) || k->cert == NULL || 54747dd1d1bSDag-Erling Smørgrav k->cert->type != SSH2_CERT_TYPE_USER) 54847dd1d1bSDag-Erling Smørgrav return NULL; 54947dd1d1bSDag-Erling Smørgrav 55047dd1d1bSDag-Erling Smørgrav if ((ret = sshauthopt_new()) == NULL) 55147dd1d1bSDag-Erling Smørgrav return NULL; 55247dd1d1bSDag-Erling Smørgrav 55347dd1d1bSDag-Erling Smørgrav /* Handle options and critical extensions separately */ 55447dd1d1bSDag-Erling Smørgrav if (cert_option_list(ret, k->cert->critical, 55547dd1d1bSDag-Erling Smørgrav OPTIONS_CRITICAL, 1) == -1) { 55647dd1d1bSDag-Erling Smørgrav sshauthopt_free(ret); 55747dd1d1bSDag-Erling Smørgrav return NULL; 55847dd1d1bSDag-Erling Smørgrav } 55947dd1d1bSDag-Erling Smørgrav if (cert_option_list(ret, k->cert->extensions, 56047dd1d1bSDag-Erling Smørgrav OPTIONS_EXTENSIONS, 0) == -1) { 56147dd1d1bSDag-Erling Smørgrav sshauthopt_free(ret); 56247dd1d1bSDag-Erling Smørgrav return NULL; 56347dd1d1bSDag-Erling Smørgrav } 56447dd1d1bSDag-Erling Smørgrav /* success */ 56547dd1d1bSDag-Erling Smørgrav return ret; 56647dd1d1bSDag-Erling Smørgrav } 56747dd1d1bSDag-Erling Smørgrav 56847dd1d1bSDag-Erling Smørgrav /* 56947dd1d1bSDag-Erling Smørgrav * Merges "additional" options to "primary" and returns the result. 57047dd1d1bSDag-Erling Smørgrav * NB. Some options from primary have primacy. 57147dd1d1bSDag-Erling Smørgrav */ 57247dd1d1bSDag-Erling Smørgrav struct sshauthopt * 57347dd1d1bSDag-Erling Smørgrav sshauthopt_merge(const struct sshauthopt *primary, 57447dd1d1bSDag-Erling Smørgrav const struct sshauthopt *additional, const char **errstrp) 57547dd1d1bSDag-Erling Smørgrav { 57647dd1d1bSDag-Erling Smørgrav struct sshauthopt *ret; 57747dd1d1bSDag-Erling Smørgrav const char *errstr = "internal error"; 57847dd1d1bSDag-Erling Smørgrav const char *tmp; 57947dd1d1bSDag-Erling Smørgrav 58047dd1d1bSDag-Erling Smørgrav if (errstrp != NULL) 58147dd1d1bSDag-Erling Smørgrav *errstrp = NULL; 58247dd1d1bSDag-Erling Smørgrav 58347dd1d1bSDag-Erling Smørgrav if ((ret = sshauthopt_new()) == NULL) 58447dd1d1bSDag-Erling Smørgrav goto alloc_fail; 58547dd1d1bSDag-Erling Smørgrav 58647dd1d1bSDag-Erling Smørgrav /* cert_authority and cert_principals are cleared in result */ 58747dd1d1bSDag-Erling Smørgrav 58847dd1d1bSDag-Erling Smørgrav /* Prefer access lists from primary. */ 58947dd1d1bSDag-Erling Smørgrav /* XXX err is both set and mismatch? */ 59047dd1d1bSDag-Erling Smørgrav tmp = primary->required_from_host_cert; 59147dd1d1bSDag-Erling Smørgrav if (tmp == NULL) 59247dd1d1bSDag-Erling Smørgrav tmp = additional->required_from_host_cert; 59347dd1d1bSDag-Erling Smørgrav if (tmp != NULL && (ret->required_from_host_cert = strdup(tmp)) == NULL) 59447dd1d1bSDag-Erling Smørgrav goto alloc_fail; 59547dd1d1bSDag-Erling Smørgrav tmp = primary->required_from_host_keys; 59647dd1d1bSDag-Erling Smørgrav if (tmp == NULL) 59747dd1d1bSDag-Erling Smørgrav tmp = additional->required_from_host_keys; 59847dd1d1bSDag-Erling Smørgrav if (tmp != NULL && (ret->required_from_host_keys = strdup(tmp)) == NULL) 59947dd1d1bSDag-Erling Smørgrav goto alloc_fail; 60047dd1d1bSDag-Erling Smørgrav 601190cef3dSDag-Erling Smørgrav /* 602190cef3dSDag-Erling Smørgrav * force_tun_device, permitopen/permitlisten and environment all 603190cef3dSDag-Erling Smørgrav * prefer the primary. 604190cef3dSDag-Erling Smørgrav */ 60547dd1d1bSDag-Erling Smørgrav ret->force_tun_device = primary->force_tun_device; 60647dd1d1bSDag-Erling Smørgrav if (ret->force_tun_device == -1) 60747dd1d1bSDag-Erling Smørgrav ret->force_tun_device = additional->force_tun_device; 60847dd1d1bSDag-Erling Smørgrav if (primary->nenv > 0) { 60947dd1d1bSDag-Erling Smørgrav if (dup_strings(&ret->env, &ret->nenv, 61047dd1d1bSDag-Erling Smørgrav primary->env, primary->nenv) != 0) 61147dd1d1bSDag-Erling Smørgrav goto alloc_fail; 61247dd1d1bSDag-Erling Smørgrav } else if (additional->nenv) { 61347dd1d1bSDag-Erling Smørgrav if (dup_strings(&ret->env, &ret->nenv, 61447dd1d1bSDag-Erling Smørgrav additional->env, additional->nenv) != 0) 61547dd1d1bSDag-Erling Smørgrav goto alloc_fail; 61647dd1d1bSDag-Erling Smørgrav } 61747dd1d1bSDag-Erling Smørgrav if (primary->npermitopen > 0) { 61847dd1d1bSDag-Erling Smørgrav if (dup_strings(&ret->permitopen, &ret->npermitopen, 61947dd1d1bSDag-Erling Smørgrav primary->permitopen, primary->npermitopen) != 0) 62047dd1d1bSDag-Erling Smørgrav goto alloc_fail; 62147dd1d1bSDag-Erling Smørgrav } else if (additional->npermitopen > 0) { 62247dd1d1bSDag-Erling Smørgrav if (dup_strings(&ret->permitopen, &ret->npermitopen, 62347dd1d1bSDag-Erling Smørgrav additional->permitopen, additional->npermitopen) != 0) 62447dd1d1bSDag-Erling Smørgrav goto alloc_fail; 62547dd1d1bSDag-Erling Smørgrav } 62647dd1d1bSDag-Erling Smørgrav 627190cef3dSDag-Erling Smørgrav if (primary->npermitlisten > 0) { 628190cef3dSDag-Erling Smørgrav if (dup_strings(&ret->permitlisten, &ret->npermitlisten, 629190cef3dSDag-Erling Smørgrav primary->permitlisten, primary->npermitlisten) != 0) 630190cef3dSDag-Erling Smørgrav goto alloc_fail; 631190cef3dSDag-Erling Smørgrav } else if (additional->npermitlisten > 0) { 632190cef3dSDag-Erling Smørgrav if (dup_strings(&ret->permitlisten, &ret->npermitlisten, 633190cef3dSDag-Erling Smørgrav additional->permitlisten, additional->npermitlisten) != 0) 634190cef3dSDag-Erling Smørgrav goto alloc_fail; 635190cef3dSDag-Erling Smørgrav } 636190cef3dSDag-Erling Smørgrav 63747dd1d1bSDag-Erling Smørgrav /* Flags are logical-AND (i.e. must be set in both for permission) */ 63847dd1d1bSDag-Erling Smørgrav #define OPTFLAG(x) ret->x = (primary->x == 1) && (additional->x == 1) 63947dd1d1bSDag-Erling Smørgrav OPTFLAG(permit_port_forwarding_flag); 64047dd1d1bSDag-Erling Smørgrav OPTFLAG(permit_agent_forwarding_flag); 64147dd1d1bSDag-Erling Smørgrav OPTFLAG(permit_x11_forwarding_flag); 64247dd1d1bSDag-Erling Smørgrav OPTFLAG(permit_pty_flag); 64347dd1d1bSDag-Erling Smørgrav OPTFLAG(permit_user_rc); 64447dd1d1bSDag-Erling Smørgrav #undef OPTFLAG 64547dd1d1bSDag-Erling Smørgrav 64647dd1d1bSDag-Erling Smørgrav /* Earliest expiry time should win */ 64747dd1d1bSDag-Erling Smørgrav if (primary->valid_before != 0) 64847dd1d1bSDag-Erling Smørgrav ret->valid_before = primary->valid_before; 64947dd1d1bSDag-Erling Smørgrav if (additional->valid_before != 0 && 65047dd1d1bSDag-Erling Smørgrav additional->valid_before < ret->valid_before) 65147dd1d1bSDag-Erling Smørgrav ret->valid_before = additional->valid_before; 65247dd1d1bSDag-Erling Smørgrav 65347dd1d1bSDag-Erling Smørgrav /* 65447dd1d1bSDag-Erling Smørgrav * When both multiple forced-command are specified, only 65547dd1d1bSDag-Erling Smørgrav * proceed if they are identical, otherwise fail. 65647dd1d1bSDag-Erling Smørgrav */ 65747dd1d1bSDag-Erling Smørgrav if (primary->force_command != NULL && 65847dd1d1bSDag-Erling Smørgrav additional->force_command != NULL) { 65947dd1d1bSDag-Erling Smørgrav if (strcmp(primary->force_command, 66047dd1d1bSDag-Erling Smørgrav additional->force_command) == 0) { 66147dd1d1bSDag-Erling Smørgrav /* ok */ 66247dd1d1bSDag-Erling Smørgrav ret->force_command = strdup(primary->force_command); 66347dd1d1bSDag-Erling Smørgrav if (ret->force_command == NULL) 66447dd1d1bSDag-Erling Smørgrav goto alloc_fail; 66547dd1d1bSDag-Erling Smørgrav } else { 66647dd1d1bSDag-Erling Smørgrav errstr = "forced command options do not match"; 66747dd1d1bSDag-Erling Smørgrav goto fail; 66847dd1d1bSDag-Erling Smørgrav } 66947dd1d1bSDag-Erling Smørgrav } else if (primary->force_command != NULL) { 67047dd1d1bSDag-Erling Smørgrav if ((ret->force_command = strdup( 67147dd1d1bSDag-Erling Smørgrav primary->force_command)) == NULL) 67247dd1d1bSDag-Erling Smørgrav goto alloc_fail; 67347dd1d1bSDag-Erling Smørgrav } else if (additional->force_command != NULL) { 67447dd1d1bSDag-Erling Smørgrav if ((ret->force_command = strdup( 67547dd1d1bSDag-Erling Smørgrav additional->force_command)) == NULL) 67647dd1d1bSDag-Erling Smørgrav goto alloc_fail; 67747dd1d1bSDag-Erling Smørgrav } 67847dd1d1bSDag-Erling Smørgrav /* success */ 67947dd1d1bSDag-Erling Smørgrav if (errstrp != NULL) 68047dd1d1bSDag-Erling Smørgrav *errstrp = NULL; 68147dd1d1bSDag-Erling Smørgrav return ret; 68247dd1d1bSDag-Erling Smørgrav 68347dd1d1bSDag-Erling Smørgrav alloc_fail: 68447dd1d1bSDag-Erling Smørgrav errstr = "memory allocation failed"; 68547dd1d1bSDag-Erling Smørgrav fail: 68647dd1d1bSDag-Erling Smørgrav if (errstrp != NULL) 68747dd1d1bSDag-Erling Smørgrav *errstrp = errstr; 68847dd1d1bSDag-Erling Smørgrav sshauthopt_free(ret); 68947dd1d1bSDag-Erling Smørgrav return NULL; 69047dd1d1bSDag-Erling Smørgrav } 69147dd1d1bSDag-Erling Smørgrav 69247dd1d1bSDag-Erling Smørgrav /* 69347dd1d1bSDag-Erling Smørgrav * Copy options 69447dd1d1bSDag-Erling Smørgrav */ 69547dd1d1bSDag-Erling Smørgrav struct sshauthopt * 69647dd1d1bSDag-Erling Smørgrav sshauthopt_copy(const struct sshauthopt *orig) 69747dd1d1bSDag-Erling Smørgrav { 69847dd1d1bSDag-Erling Smørgrav struct sshauthopt *ret; 69947dd1d1bSDag-Erling Smørgrav 70047dd1d1bSDag-Erling Smørgrav if ((ret = sshauthopt_new()) == NULL) 70147dd1d1bSDag-Erling Smørgrav return NULL; 70247dd1d1bSDag-Erling Smørgrav 70347dd1d1bSDag-Erling Smørgrav #define OPTSCALAR(x) ret->x = orig->x 70447dd1d1bSDag-Erling Smørgrav OPTSCALAR(permit_port_forwarding_flag); 70547dd1d1bSDag-Erling Smørgrav OPTSCALAR(permit_agent_forwarding_flag); 70647dd1d1bSDag-Erling Smørgrav OPTSCALAR(permit_x11_forwarding_flag); 70747dd1d1bSDag-Erling Smørgrav OPTSCALAR(permit_pty_flag); 70847dd1d1bSDag-Erling Smørgrav OPTSCALAR(permit_user_rc); 70947dd1d1bSDag-Erling Smørgrav OPTSCALAR(restricted); 71047dd1d1bSDag-Erling Smørgrav OPTSCALAR(cert_authority); 71147dd1d1bSDag-Erling Smørgrav OPTSCALAR(force_tun_device); 71247dd1d1bSDag-Erling Smørgrav OPTSCALAR(valid_before); 71347dd1d1bSDag-Erling Smørgrav #undef OPTSCALAR 71447dd1d1bSDag-Erling Smørgrav #define OPTSTRING(x) \ 71547dd1d1bSDag-Erling Smørgrav do { \ 71647dd1d1bSDag-Erling Smørgrav if (orig->x != NULL && (ret->x = strdup(orig->x)) == NULL) { \ 71747dd1d1bSDag-Erling Smørgrav sshauthopt_free(ret); \ 71847dd1d1bSDag-Erling Smørgrav return NULL; \ 71947dd1d1bSDag-Erling Smørgrav } \ 72047dd1d1bSDag-Erling Smørgrav } while (0) 72147dd1d1bSDag-Erling Smørgrav OPTSTRING(cert_principals); 72247dd1d1bSDag-Erling Smørgrav OPTSTRING(force_command); 72347dd1d1bSDag-Erling Smørgrav OPTSTRING(required_from_host_cert); 72447dd1d1bSDag-Erling Smørgrav OPTSTRING(required_from_host_keys); 72547dd1d1bSDag-Erling Smørgrav #undef OPTSTRING 72647dd1d1bSDag-Erling Smørgrav 72747dd1d1bSDag-Erling Smørgrav if (dup_strings(&ret->env, &ret->nenv, orig->env, orig->nenv) != 0 || 72847dd1d1bSDag-Erling Smørgrav dup_strings(&ret->permitopen, &ret->npermitopen, 729190cef3dSDag-Erling Smørgrav orig->permitopen, orig->npermitopen) != 0 || 730190cef3dSDag-Erling Smørgrav dup_strings(&ret->permitlisten, &ret->npermitlisten, 731190cef3dSDag-Erling Smørgrav orig->permitlisten, orig->npermitlisten) != 0) { 73247dd1d1bSDag-Erling Smørgrav sshauthopt_free(ret); 73347dd1d1bSDag-Erling Smørgrav return NULL; 73447dd1d1bSDag-Erling Smørgrav } 73547dd1d1bSDag-Erling Smørgrav return ret; 73647dd1d1bSDag-Erling Smørgrav } 73747dd1d1bSDag-Erling Smørgrav 73847dd1d1bSDag-Erling Smørgrav static int 73947dd1d1bSDag-Erling Smørgrav serialise_array(struct sshbuf *m, char **a, size_t n) 74047dd1d1bSDag-Erling Smørgrav { 74147dd1d1bSDag-Erling Smørgrav struct sshbuf *b; 74247dd1d1bSDag-Erling Smørgrav size_t i; 74347dd1d1bSDag-Erling Smørgrav int r; 74447dd1d1bSDag-Erling Smørgrav 74547dd1d1bSDag-Erling Smørgrav if (n > INT_MAX) 74647dd1d1bSDag-Erling Smørgrav return SSH_ERR_INTERNAL_ERROR; 74747dd1d1bSDag-Erling Smørgrav 74847dd1d1bSDag-Erling Smørgrav if ((b = sshbuf_new()) == NULL) { 74947dd1d1bSDag-Erling Smørgrav return SSH_ERR_ALLOC_FAIL; 75047dd1d1bSDag-Erling Smørgrav } 75147dd1d1bSDag-Erling Smørgrav for (i = 0; i < n; i++) { 75247dd1d1bSDag-Erling Smørgrav if ((r = sshbuf_put_cstring(b, a[i])) != 0) { 75347dd1d1bSDag-Erling Smørgrav sshbuf_free(b); 75447dd1d1bSDag-Erling Smørgrav return r; 75547dd1d1bSDag-Erling Smørgrav } 75647dd1d1bSDag-Erling Smørgrav } 75747dd1d1bSDag-Erling Smørgrav if ((r = sshbuf_put_u32(m, n)) != 0 || 75847dd1d1bSDag-Erling Smørgrav (r = sshbuf_put_stringb(m, b)) != 0) { 75947dd1d1bSDag-Erling Smørgrav sshbuf_free(b); 76047dd1d1bSDag-Erling Smørgrav return r; 76147dd1d1bSDag-Erling Smørgrav } 76247dd1d1bSDag-Erling Smørgrav /* success */ 763e2f6069cSDag-Erling Smørgrav return 0; 764b15c8340SDag-Erling Smørgrav } 765b15c8340SDag-Erling Smørgrav 76647dd1d1bSDag-Erling Smørgrav static int 76747dd1d1bSDag-Erling Smørgrav deserialise_array(struct sshbuf *m, char ***ap, size_t *np) 76847dd1d1bSDag-Erling Smørgrav { 76947dd1d1bSDag-Erling Smørgrav char **a = NULL; 77047dd1d1bSDag-Erling Smørgrav size_t i, n = 0; 77147dd1d1bSDag-Erling Smørgrav struct sshbuf *b = NULL; 77247dd1d1bSDag-Erling Smørgrav u_int tmp; 77347dd1d1bSDag-Erling Smørgrav int r = SSH_ERR_INTERNAL_ERROR; 77447dd1d1bSDag-Erling Smørgrav 77547dd1d1bSDag-Erling Smørgrav if ((r = sshbuf_get_u32(m, &tmp)) != 0 || 77647dd1d1bSDag-Erling Smørgrav (r = sshbuf_froms(m, &b)) != 0) 77747dd1d1bSDag-Erling Smørgrav goto out; 77847dd1d1bSDag-Erling Smørgrav if (tmp > INT_MAX) { 77947dd1d1bSDag-Erling Smørgrav r = SSH_ERR_INVALID_FORMAT; 78047dd1d1bSDag-Erling Smørgrav goto out; 78147dd1d1bSDag-Erling Smørgrav } 78247dd1d1bSDag-Erling Smørgrav n = tmp; 78347dd1d1bSDag-Erling Smørgrav if (n > 0 && (a = calloc(n, sizeof(*a))) == NULL) { 78447dd1d1bSDag-Erling Smørgrav r = SSH_ERR_ALLOC_FAIL; 78547dd1d1bSDag-Erling Smørgrav goto out; 78647dd1d1bSDag-Erling Smørgrav } 78747dd1d1bSDag-Erling Smørgrav for (i = 0; i < n; i++) { 78847dd1d1bSDag-Erling Smørgrav if ((r = sshbuf_get_cstring(b, &a[i], NULL)) != 0) 78947dd1d1bSDag-Erling Smørgrav goto out; 79047dd1d1bSDag-Erling Smørgrav } 79147dd1d1bSDag-Erling Smørgrav /* success */ 79247dd1d1bSDag-Erling Smørgrav r = 0; 79347dd1d1bSDag-Erling Smørgrav *ap = a; 79447dd1d1bSDag-Erling Smørgrav a = NULL; 79547dd1d1bSDag-Erling Smørgrav *np = n; 79647dd1d1bSDag-Erling Smørgrav n = 0; 79747dd1d1bSDag-Erling Smørgrav out: 79847dd1d1bSDag-Erling Smørgrav for (i = 0; i < n; i++) 79947dd1d1bSDag-Erling Smørgrav free(a[i]); 80047dd1d1bSDag-Erling Smørgrav free(a); 80147dd1d1bSDag-Erling Smørgrav sshbuf_free(b); 80247dd1d1bSDag-Erling Smørgrav return r; 80347dd1d1bSDag-Erling Smørgrav } 80447dd1d1bSDag-Erling Smørgrav 80547dd1d1bSDag-Erling Smørgrav static int 80647dd1d1bSDag-Erling Smørgrav serialise_nullable_string(struct sshbuf *m, const char *s) 80747dd1d1bSDag-Erling Smørgrav { 80847dd1d1bSDag-Erling Smørgrav int r; 80947dd1d1bSDag-Erling Smørgrav 81047dd1d1bSDag-Erling Smørgrav if ((r = sshbuf_put_u8(m, s == NULL)) != 0 || 81147dd1d1bSDag-Erling Smørgrav (r = sshbuf_put_cstring(m, s)) != 0) 81247dd1d1bSDag-Erling Smørgrav return r; 81347dd1d1bSDag-Erling Smørgrav return 0; 81447dd1d1bSDag-Erling Smørgrav } 81547dd1d1bSDag-Erling Smørgrav 81647dd1d1bSDag-Erling Smørgrav static int 81747dd1d1bSDag-Erling Smørgrav deserialise_nullable_string(struct sshbuf *m, char **sp) 81847dd1d1bSDag-Erling Smørgrav { 81947dd1d1bSDag-Erling Smørgrav int r; 82047dd1d1bSDag-Erling Smørgrav u_char flag; 82147dd1d1bSDag-Erling Smørgrav 82247dd1d1bSDag-Erling Smørgrav *sp = NULL; 82347dd1d1bSDag-Erling Smørgrav if ((r = sshbuf_get_u8(m, &flag)) != 0 || 82447dd1d1bSDag-Erling Smørgrav (r = sshbuf_get_cstring(m, flag ? NULL : sp, NULL)) != 0) 82547dd1d1bSDag-Erling Smørgrav return r; 82647dd1d1bSDag-Erling Smørgrav return 0; 82747dd1d1bSDag-Erling Smørgrav } 82847dd1d1bSDag-Erling Smørgrav 82947dd1d1bSDag-Erling Smørgrav int 83047dd1d1bSDag-Erling Smørgrav sshauthopt_serialise(const struct sshauthopt *opts, struct sshbuf *m, 83147dd1d1bSDag-Erling Smørgrav int untrusted) 83247dd1d1bSDag-Erling Smørgrav { 83347dd1d1bSDag-Erling Smørgrav int r = SSH_ERR_INTERNAL_ERROR; 83447dd1d1bSDag-Erling Smørgrav 83547dd1d1bSDag-Erling Smørgrav /* Flag and simple integer options */ 83647dd1d1bSDag-Erling Smørgrav if ((r = sshbuf_put_u8(m, opts->permit_port_forwarding_flag)) != 0 || 83747dd1d1bSDag-Erling Smørgrav (r = sshbuf_put_u8(m, opts->permit_agent_forwarding_flag)) != 0 || 83847dd1d1bSDag-Erling Smørgrav (r = sshbuf_put_u8(m, opts->permit_x11_forwarding_flag)) != 0 || 83947dd1d1bSDag-Erling Smørgrav (r = sshbuf_put_u8(m, opts->permit_pty_flag)) != 0 || 84047dd1d1bSDag-Erling Smørgrav (r = sshbuf_put_u8(m, opts->permit_user_rc)) != 0 || 84147dd1d1bSDag-Erling Smørgrav (r = sshbuf_put_u8(m, opts->restricted)) != 0 || 84247dd1d1bSDag-Erling Smørgrav (r = sshbuf_put_u8(m, opts->cert_authority)) != 0 || 84347dd1d1bSDag-Erling Smørgrav (r = sshbuf_put_u64(m, opts->valid_before)) != 0) 84447dd1d1bSDag-Erling Smørgrav return r; 84547dd1d1bSDag-Erling Smørgrav 84647dd1d1bSDag-Erling Smørgrav /* tunnel number can be negative to indicate "unset" */ 84747dd1d1bSDag-Erling Smørgrav if ((r = sshbuf_put_u8(m, opts->force_tun_device == -1)) != 0 || 84847dd1d1bSDag-Erling Smørgrav (r = sshbuf_put_u32(m, (opts->force_tun_device < 0) ? 84947dd1d1bSDag-Erling Smørgrav 0 : (u_int)opts->force_tun_device)) != 0) 85047dd1d1bSDag-Erling Smørgrav return r; 85147dd1d1bSDag-Erling Smørgrav 85247dd1d1bSDag-Erling Smørgrav /* String options; these may be NULL */ 85347dd1d1bSDag-Erling Smørgrav if ((r = serialise_nullable_string(m, 85447dd1d1bSDag-Erling Smørgrav untrusted ? "yes" : opts->cert_principals)) != 0 || 85547dd1d1bSDag-Erling Smørgrav (r = serialise_nullable_string(m, 85647dd1d1bSDag-Erling Smørgrav untrusted ? "true" : opts->force_command)) != 0 || 85747dd1d1bSDag-Erling Smørgrav (r = serialise_nullable_string(m, 85847dd1d1bSDag-Erling Smørgrav untrusted ? NULL : opts->required_from_host_cert)) != 0 || 85947dd1d1bSDag-Erling Smørgrav (r = serialise_nullable_string(m, 86047dd1d1bSDag-Erling Smørgrav untrusted ? NULL : opts->required_from_host_keys)) != 0) 86147dd1d1bSDag-Erling Smørgrav return r; 86247dd1d1bSDag-Erling Smørgrav 86347dd1d1bSDag-Erling Smørgrav /* Array options */ 86447dd1d1bSDag-Erling Smørgrav if ((r = serialise_array(m, opts->env, 86547dd1d1bSDag-Erling Smørgrav untrusted ? 0 : opts->nenv)) != 0 || 86647dd1d1bSDag-Erling Smørgrav (r = serialise_array(m, opts->permitopen, 867190cef3dSDag-Erling Smørgrav untrusted ? 0 : opts->npermitopen)) != 0 || 868190cef3dSDag-Erling Smørgrav (r = serialise_array(m, opts->permitlisten, 869190cef3dSDag-Erling Smørgrav untrusted ? 0 : opts->npermitlisten)) != 0) 87047dd1d1bSDag-Erling Smørgrav return r; 87147dd1d1bSDag-Erling Smørgrav 87247dd1d1bSDag-Erling Smørgrav /* success */ 87347dd1d1bSDag-Erling Smørgrav return 0; 87447dd1d1bSDag-Erling Smørgrav } 87547dd1d1bSDag-Erling Smørgrav 87647dd1d1bSDag-Erling Smørgrav int 87747dd1d1bSDag-Erling Smørgrav sshauthopt_deserialise(struct sshbuf *m, struct sshauthopt **optsp) 87847dd1d1bSDag-Erling Smørgrav { 87947dd1d1bSDag-Erling Smørgrav struct sshauthopt *opts = NULL; 88047dd1d1bSDag-Erling Smørgrav int r = SSH_ERR_INTERNAL_ERROR; 88147dd1d1bSDag-Erling Smørgrav u_char f; 88247dd1d1bSDag-Erling Smørgrav u_int tmp; 88347dd1d1bSDag-Erling Smørgrav 88447dd1d1bSDag-Erling Smørgrav if ((opts = calloc(1, sizeof(*opts))) == NULL) 88547dd1d1bSDag-Erling Smørgrav return SSH_ERR_ALLOC_FAIL; 88647dd1d1bSDag-Erling Smørgrav 88747dd1d1bSDag-Erling Smørgrav #define OPT_FLAG(x) \ 88847dd1d1bSDag-Erling Smørgrav do { \ 88947dd1d1bSDag-Erling Smørgrav if ((r = sshbuf_get_u8(m, &f)) != 0) \ 89047dd1d1bSDag-Erling Smørgrav goto out; \ 89147dd1d1bSDag-Erling Smørgrav opts->x = f; \ 89247dd1d1bSDag-Erling Smørgrav } while (0) 89347dd1d1bSDag-Erling Smørgrav OPT_FLAG(permit_port_forwarding_flag); 89447dd1d1bSDag-Erling Smørgrav OPT_FLAG(permit_agent_forwarding_flag); 89547dd1d1bSDag-Erling Smørgrav OPT_FLAG(permit_x11_forwarding_flag); 89647dd1d1bSDag-Erling Smørgrav OPT_FLAG(permit_pty_flag); 89747dd1d1bSDag-Erling Smørgrav OPT_FLAG(permit_user_rc); 89847dd1d1bSDag-Erling Smørgrav OPT_FLAG(restricted); 89947dd1d1bSDag-Erling Smørgrav OPT_FLAG(cert_authority); 90047dd1d1bSDag-Erling Smørgrav #undef OPT_FLAG 90147dd1d1bSDag-Erling Smørgrav 90247dd1d1bSDag-Erling Smørgrav if ((r = sshbuf_get_u64(m, &opts->valid_before)) != 0) 90347dd1d1bSDag-Erling Smørgrav goto out; 90447dd1d1bSDag-Erling Smørgrav 90547dd1d1bSDag-Erling Smørgrav /* tunnel number can be negative to indicate "unset" */ 90647dd1d1bSDag-Erling Smørgrav if ((r = sshbuf_get_u8(m, &f)) != 0 || 90747dd1d1bSDag-Erling Smørgrav (r = sshbuf_get_u32(m, &tmp)) != 0) 90847dd1d1bSDag-Erling Smørgrav goto out; 90947dd1d1bSDag-Erling Smørgrav opts->force_tun_device = f ? -1 : (int)tmp; 91047dd1d1bSDag-Erling Smørgrav 91147dd1d1bSDag-Erling Smørgrav /* String options may be NULL */ 91247dd1d1bSDag-Erling Smørgrav if ((r = deserialise_nullable_string(m, &opts->cert_principals)) != 0 || 91347dd1d1bSDag-Erling Smørgrav (r = deserialise_nullable_string(m, &opts->force_command)) != 0 || 91447dd1d1bSDag-Erling Smørgrav (r = deserialise_nullable_string(m, 91547dd1d1bSDag-Erling Smørgrav &opts->required_from_host_cert)) != 0 || 91647dd1d1bSDag-Erling Smørgrav (r = deserialise_nullable_string(m, 91747dd1d1bSDag-Erling Smørgrav &opts->required_from_host_keys)) != 0) 91847dd1d1bSDag-Erling Smørgrav goto out; 91947dd1d1bSDag-Erling Smørgrav 92047dd1d1bSDag-Erling Smørgrav /* Array options */ 92147dd1d1bSDag-Erling Smørgrav if ((r = deserialise_array(m, &opts->env, &opts->nenv)) != 0 || 92247dd1d1bSDag-Erling Smørgrav (r = deserialise_array(m, 923190cef3dSDag-Erling Smørgrav &opts->permitopen, &opts->npermitopen)) != 0 || 924190cef3dSDag-Erling Smørgrav (r = deserialise_array(m, 925190cef3dSDag-Erling Smørgrav &opts->permitlisten, &opts->npermitlisten)) != 0) 92647dd1d1bSDag-Erling Smørgrav goto out; 92747dd1d1bSDag-Erling Smørgrav 92847dd1d1bSDag-Erling Smørgrav /* success */ 92947dd1d1bSDag-Erling Smørgrav r = 0; 93047dd1d1bSDag-Erling Smørgrav *optsp = opts; 93147dd1d1bSDag-Erling Smørgrav opts = NULL; 93247dd1d1bSDag-Erling Smørgrav out: 93347dd1d1bSDag-Erling Smørgrav sshauthopt_free(opts); 93447dd1d1bSDag-Erling Smørgrav return r; 93547dd1d1bSDag-Erling Smørgrav } 936