xref: /openbsd-src/usr.bin/ssh/auth-options.c (revision 6c6408334dbede3a2c0dcd9ff9c489157df0c856)
1 /* $OpenBSD: auth-options.c,v 1.76 2018/03/03 03:15:51 djm Exp $ */
2 /*
3  * Copyright (c) 2018 Damien Miller <djm@mindrot.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/types.h>
19 #include <sys/queue.h>
20 
21 #include <netdb.h>
22 #include <pwd.h>
23 #include <string.h>
24 #include <stdio.h>
25 #include <stdarg.h>
26 #include <ctype.h>
27 #include <limits.h>
28 
29 #include "xmalloc.h"
30 #include "ssherr.h"
31 #include "log.h"
32 #include "sshbuf.h"
33 #include "misc.h"
34 #include "sshkey.h"
35 #include "match.h"
36 #include "ssh2.h"
37 #include "auth-options.h"
38 
39 /*
40  * Match flag 'opt' in *optsp, and if allow_negate is set then also match
41  * 'no-opt'. Returns -1 if option not matched, 1 if option matches or 0
42  * if negated option matches.
43  * If the option or negated option matches, then *optsp is updated to
44  * point to the first character after the option.
45  */
46 static int
47 opt_flag(const char *opt, int allow_negate, const char **optsp)
48 {
49 	size_t opt_len = strlen(opt);
50 	const char *opts = *optsp;
51 	int negate = 0;
52 
53 	if (allow_negate && strncasecmp(opts, "no-", 3) == 0) {
54 		opts += 3;
55 		negate = 1;
56 	}
57 	if (strncasecmp(opts, opt, opt_len) == 0) {
58 		*optsp = opts + opt_len;
59 		return negate ? 0 : 1;
60 	}
61 	return -1;
62 }
63 
64 static char *
65 opt_dequote(const char **sp, const char **errstrp)
66 {
67 	const char *s = *sp;
68 	char *ret;
69 	size_t i;
70 
71 	*errstrp = NULL;
72 	if (*s != '"') {
73 		*errstrp = "missing start quote";
74 		return NULL;
75 	}
76 	s++;
77 	if ((ret = malloc(strlen((s)) + 1)) == NULL) {
78 		*errstrp = "memory allocation failed";
79 		return NULL;
80 	}
81 	for (i = 0; *s != '\0' && *s != '"';) {
82 		if (s[0] == '\\' && s[1] == '"')
83 			s++;
84 		ret[i++] = *s++;
85 	}
86 	if (*s == '\0') {
87 		*errstrp = "missing end quote";
88 		free(ret);
89 		return NULL;
90 	}
91 	ret[i] = '\0';
92 	s++;
93 	*sp = s;
94 	return ret;
95 }
96 
97 static int
98 opt_match(const char **opts, const char *term)
99 {
100 	if (strncasecmp((*opts), term, strlen(term)) == 0 &&
101 	    (*opts)[strlen(term)] == '=') {
102 		*opts += strlen(term) + 1;
103 		return 1;
104 	}
105 	return 0;
106 }
107 
108 static int
109 dup_strings(char ***dstp, size_t *ndstp, char **src, size_t nsrc)
110 {
111 	char **dst;
112 	size_t i, j;
113 
114 	*dstp = NULL;
115 	*ndstp = 0;
116 	if (nsrc == 0)
117 		return 0;
118 
119 	if ((dst = calloc(nsrc, sizeof(*src))) == NULL)
120 		return -1;
121 	for (i = 0; i < nsrc; i++) {
122 		if ((dst[i] = strdup(src[i])) == NULL) {
123 			for (j = 0; j < i; j++)
124 				free(dst[j]);
125 			free(dst);
126 			return -1;
127 		}
128 	}
129 	/* success */
130 	*dstp = dst;
131 	*ndstp = nsrc;
132 	return 0;
133 }
134 
135 #define OPTIONS_CRITICAL	1
136 #define OPTIONS_EXTENSIONS	2
137 static int
138 cert_option_list(struct sshauthopt *opts, struct sshbuf *oblob,
139     u_int which, int crit)
140 {
141 	char *command, *allowed;
142 	char *name = NULL;
143 	struct sshbuf *c = NULL, *data = NULL;
144 	int r, ret = -1, found;
145 
146 	if ((c = sshbuf_fromb(oblob)) == NULL) {
147 		error("%s: sshbuf_fromb failed", __func__);
148 		goto out;
149 	}
150 
151 	while (sshbuf_len(c) > 0) {
152 		sshbuf_free(data);
153 		data = NULL;
154 		if ((r = sshbuf_get_cstring(c, &name, NULL)) != 0 ||
155 		    (r = sshbuf_froms(c, &data)) != 0) {
156 			error("Unable to parse certificate options: %s",
157 			    ssh_err(r));
158 			goto out;
159 		}
160 		debug3("found certificate option \"%.100s\" len %zu",
161 		    name, sshbuf_len(data));
162 		found = 0;
163 		if ((which & OPTIONS_EXTENSIONS) != 0) {
164 			if (strcmp(name, "permit-X11-forwarding") == 0) {
165 				opts->permit_x11_forwarding_flag = 1;
166 				found = 1;
167 			} else if (strcmp(name,
168 			    "permit-agent-forwarding") == 0) {
169 				opts->permit_agent_forwarding_flag = 1;
170 				found = 1;
171 			} else if (strcmp(name,
172 			    "permit-port-forwarding") == 0) {
173 				opts->permit_port_forwarding_flag = 1;
174 				found = 1;
175 			} else if (strcmp(name, "permit-pty") == 0) {
176 				opts->permit_pty_flag = 1;
177 				found = 1;
178 			} else if (strcmp(name, "permit-user-rc") == 0) {
179 				opts->permit_user_rc = 1;
180 				found = 1;
181 			}
182 		}
183 		if (!found && (which & OPTIONS_CRITICAL) != 0) {
184 			if (strcmp(name, "force-command") == 0) {
185 				if ((r = sshbuf_get_cstring(data, &command,
186 				    NULL)) != 0) {
187 					error("Unable to parse \"%s\" "
188 					    "section: %s", name, ssh_err(r));
189 					goto out;
190 				}
191 				if (opts->force_command != NULL) {
192 					error("Certificate has multiple "
193 					    "force-command options");
194 					free(command);
195 					goto out;
196 				}
197 				opts->force_command = command;
198 				found = 1;
199 			}
200 			if (strcmp(name, "source-address") == 0) {
201 				if ((r = sshbuf_get_cstring(data, &allowed,
202 				    NULL)) != 0) {
203 					error("Unable to parse \"%s\" "
204 					    "section: %s", name, ssh_err(r));
205 					goto out;
206 				}
207 				if (opts->required_from_host_cert != NULL) {
208 					error("Certificate has multiple "
209 					    "source-address options");
210 					free(allowed);
211 					goto out;
212 				}
213 				/* Check syntax */
214 				if (addr_match_cidr_list(NULL, allowed) == -1) {
215 					error("Certificate source-address "
216 					    "contents invalid");
217 					goto out;
218 				}
219 				opts->required_from_host_cert = allowed;
220 				found = 1;
221 			}
222 		}
223 
224 		if (!found) {
225 			if (crit) {
226 				error("Certificate critical option \"%s\" "
227 				    "is not supported", name);
228 				goto out;
229 			} else {
230 				logit("Certificate extension \"%s\" "
231 				    "is not supported", name);
232 			}
233 		} else if (sshbuf_len(data) != 0) {
234 			error("Certificate option \"%s\" corrupt "
235 			    "(extra data)", name);
236 			goto out;
237 		}
238 		free(name);
239 		name = NULL;
240 	}
241 	/* successfully parsed all options */
242 	ret = 0;
243 
244  out:
245 	free(name);
246 	sshbuf_free(data);
247 	sshbuf_free(c);
248 	return ret;
249 }
250 
251 struct sshauthopt *
252 sshauthopt_new(void)
253 {
254 	struct sshauthopt *ret;
255 
256 	if ((ret = calloc(1, sizeof(*ret))) == NULL)
257 		return NULL;
258 	ret->force_tun_device = -1;
259 	return ret;
260 }
261 
262 void
263 sshauthopt_free(struct sshauthopt *opts)
264 {
265 	size_t i;
266 
267 	if (opts == NULL)
268 		return;
269 
270 	free(opts->cert_principals);
271 	free(opts->force_command);
272 	free(opts->required_from_host_cert);
273 	free(opts->required_from_host_keys);
274 
275 	for (i = 0; i < opts->nenv; i++)
276 		free(opts->env[i]);
277 	free(opts->env);
278 
279 	for (i = 0; i < opts->npermitopen; i++)
280 		free(opts->permitopen[i]);
281 	free(opts->permitopen);
282 
283 	explicit_bzero(opts, sizeof(*opts));
284 	free(opts);
285 }
286 
287 struct sshauthopt *
288 sshauthopt_new_with_keys_defaults(void)
289 {
290 	struct sshauthopt *ret = NULL;
291 
292 	if ((ret = sshauthopt_new()) == NULL)
293 		return NULL;
294 
295 	/* Defaults for authorized_keys flags */
296 	ret->permit_port_forwarding_flag = 1;
297 	ret->permit_agent_forwarding_flag = 1;
298 	ret->permit_x11_forwarding_flag = 1;
299 	ret->permit_pty_flag = 1;
300 	ret->permit_user_rc = 1;
301 	return ret;
302 }
303 
304 struct sshauthopt *
305 sshauthopt_parse(const char *opts, const char **errstrp)
306 {
307 	char **oarray, *opt, *cp, *tmp, *host;
308 	int r;
309 	struct sshauthopt *ret = NULL;
310 	const char *errstr = "unknown error";
311 
312 	if (errstrp != NULL)
313 		*errstrp = NULL;
314 	if ((ret = sshauthopt_new_with_keys_defaults()) == NULL)
315 		goto alloc_fail;
316 
317 	if (opts == NULL)
318 		return ret;
319 
320 	while (*opts && *opts != ' ' && *opts != '\t') {
321 		/* flag options */
322 		if ((r = opt_flag("restrict", 0, &opts)) != -1) {
323 			ret->restricted = 1;
324 			ret->permit_port_forwarding_flag = 0;
325 			ret->permit_agent_forwarding_flag = 0;
326 			ret->permit_x11_forwarding_flag = 0;
327 			ret->permit_pty_flag = 0;
328 			ret->permit_user_rc = 0;
329 		} else if ((r = opt_flag("cert-authority", 0, &opts)) != -1) {
330 			ret->cert_authority = r;
331 		} else if ((r = opt_flag("port-forwarding", 1, &opts)) != -1) {
332 			ret->permit_port_forwarding_flag = r == 1;
333 		} else if ((r = opt_flag("agent-forwarding", 1, &opts)) != -1) {
334 			ret->permit_agent_forwarding_flag = r == 1;
335 		} else if ((r = opt_flag("x11-forwarding", 1, &opts)) != -1) {
336 			ret->permit_x11_forwarding_flag = r == 1;
337 		} else if ((r = opt_flag("pty", 1, &opts)) != -1) {
338 			ret->permit_pty_flag = r == 1;
339 		} else if ((r = opt_flag("user-rc", 1, &opts)) != -1) {
340 			ret->permit_user_rc = r == 1;
341 		} else if (opt_match(&opts, "command")) {
342 			if (ret->force_command != NULL) {
343 				errstr = "multiple \"command\" clauses";
344 				goto fail;
345 			}
346 			ret->force_command = opt_dequote(&opts, &errstr);
347 			if (ret->force_command == NULL)
348 				goto fail;
349 		} else if (opt_match(&opts, "principals")) {
350 			if (ret->cert_principals != NULL) {
351 				errstr = "multiple \"principals\" clauses";
352 				goto fail;
353 			}
354 			ret->cert_principals = opt_dequote(&opts, &errstr);
355 			if (ret->cert_principals == NULL)
356 				goto fail;
357 		} else if (opt_match(&opts, "from")) {
358 			if (ret->required_from_host_keys != NULL) {
359 				errstr = "multiple \"from\" clauses";
360 				goto fail;
361 			}
362 			ret->required_from_host_keys = opt_dequote(&opts,
363 			    &errstr);
364 			if (ret->required_from_host_keys == NULL)
365 				goto fail;
366 		} else if (opt_match(&opts, "environment")) {
367 			if (ret->nenv > INT_MAX) {
368 				errstr = "too many environment strings";
369 				goto fail;
370 			}
371 			if ((opt = opt_dequote(&opts, &errstr)) == NULL)
372 				goto fail;
373 			/* env name must be alphanumeric and followed by '=' */
374 			if ((tmp = strchr(opt, '=')) == NULL) {
375 				free(opt);
376 				errstr = "invalid environment string";
377 				goto fail;
378 			}
379 			for (cp = opt; cp < tmp; cp++) {
380 				if (!isalnum((u_char)*cp)) {
381 					free(opt);
382 					errstr = "invalid environment string";
383 					goto fail;
384 				}
385 			}
386 			/* Append it. */
387 			oarray = ret->env;
388 			if ((ret->env = recallocarray(ret->env, ret->nenv,
389 			    ret->nenv + 1, sizeof(*ret->env))) == NULL) {
390 				free(opt);
391 				ret->env = oarray; /* put it back for cleanup */
392 				goto alloc_fail;
393 			}
394 			ret->env[ret->nenv++] = opt;
395 		} else if (opt_match(&opts, "permitopen")) {
396 			if (ret->npermitopen > INT_MAX) {
397 				errstr = "too many permitopens";
398 				goto fail;
399 			}
400 			if ((opt = opt_dequote(&opts, &errstr)) == NULL)
401 				goto fail;
402 			if ((tmp = strdup(opt)) == NULL) {
403 				free(opt);
404 				goto alloc_fail;
405 			}
406 			cp = tmp;
407 			/* validate syntax of permitopen before recording it. */
408 			host = hpdelim(&cp);
409 			if (host == NULL || strlen(host) >= NI_MAXHOST) {
410 				free(tmp);
411 				free(opt);
412 				errstr = "invalid permitopen hostname";
413 				goto fail;
414 			}
415 			/*
416 			 * don't want to use permitopen_port to avoid
417 			 * dependency on channels.[ch] here.
418 			 */
419 			if (cp == NULL ||
420 			    (strcmp(cp, "*") != 0 && a2port(cp) <= 0)) {
421 				free(tmp);
422 				free(opt);
423 				errstr = "invalid permitopen port";
424 				goto fail;
425 			}
426 			/* XXX - add streamlocal support */
427 			free(tmp);
428 			/* Record it */
429 			oarray = ret->permitopen;
430 			if ((ret->permitopen = recallocarray(ret->permitopen,
431 			    ret->npermitopen, ret->npermitopen + 1,
432 			    sizeof(*ret->permitopen))) == NULL) {
433 				free(opt);
434 				ret->permitopen = oarray;
435 				goto alloc_fail;
436 			}
437 			ret->permitopen[ret->npermitopen++] = opt;
438 		} else if (opt_match(&opts, "tunnel")) {
439 			if ((opt = opt_dequote(&opts, &errstr)) == NULL)
440 				goto fail;
441 			ret->force_tun_device = a2tun(opt, NULL);
442 			free(opt);
443 			if (ret->force_tun_device == SSH_TUNID_ERR) {
444 				errstr = "invalid tun device";
445 				goto fail;
446 			}
447 		}
448 		/*
449 		 * Skip the comma, and move to the next option
450 		 * (or break out if there are no more).
451 		 */
452 		if (*opts == '\0' || *opts == ' ' || *opts == '\t')
453 			break;		/* End of options. */
454 		/* Anything other than a comma is an unknown option */
455 		if (*opts != ',') {
456 			errstr = "unknown key option";
457 			goto fail;
458 		}
459 		opts++;
460 		if (*opts == '\0') {
461 			errstr = "unexpected end-of-options";
462 			goto fail;
463 		}
464 	}
465 
466 	/* success */
467 	if (errstrp != NULL)
468 		*errstrp = NULL;
469 	return ret;
470 
471 alloc_fail:
472 	errstr = "memory allocation failed";
473 fail:
474 	sshauthopt_free(ret);
475 	if (errstrp != NULL)
476 		*errstrp = errstr;
477 	return NULL;
478 }
479 
480 struct sshauthopt *
481 sshauthopt_from_cert(struct sshkey *k)
482 {
483 	struct sshauthopt *ret;
484 
485 	if (k == NULL || !sshkey_type_is_cert(k->type) || k->cert == NULL ||
486 	    k->cert->type != SSH2_CERT_TYPE_USER)
487 		return NULL;
488 
489 	if ((ret = sshauthopt_new()) == NULL)
490 		return NULL;
491 
492 	/* Handle options and critical extensions separately */
493 	if (cert_option_list(ret, k->cert->critical,
494 	    OPTIONS_CRITICAL, 1) == -1) {
495 		sshauthopt_free(ret);
496 		return NULL;
497 	}
498 	if (cert_option_list(ret, k->cert->extensions,
499 	    OPTIONS_EXTENSIONS, 0) == -1) {
500 		sshauthopt_free(ret);
501 		return NULL;
502 	}
503 	/* success */
504 	return ret;
505 }
506 
507 /*
508  * Merges "additional" options to "primary" and returns the result.
509  * NB. Some options from primary have primacy.
510  */
511 struct sshauthopt *
512 sshauthopt_merge(const struct sshauthopt *primary,
513     const struct sshauthopt *additional, const char **errstrp)
514 {
515 	struct sshauthopt *ret;
516 	const char *errstr = "internal error";
517 	const char *tmp;
518 
519 	if (errstrp != NULL)
520 		*errstrp = NULL;
521 
522 	if ((ret = sshauthopt_new()) == NULL)
523 		goto alloc_fail;
524 
525 	/* cert_authority and cert_principals are cleared in result */
526 
527 	/* Prefer access lists from primary. */
528 	/* XXX err is both set and mismatch? */
529 	tmp = primary->required_from_host_cert;
530 	if (tmp == NULL)
531 		tmp = additional->required_from_host_cert;
532 	if (tmp != NULL && (ret->required_from_host_cert = strdup(tmp)) == NULL)
533 		goto alloc_fail;
534 	tmp = primary->required_from_host_keys;
535 	if (tmp == NULL)
536 		tmp = additional->required_from_host_keys;
537 	if (tmp != NULL && (ret->required_from_host_keys = strdup(tmp)) == NULL)
538 		goto alloc_fail;
539 
540 	/* force_tun_device, permitopen and environment prefer the primary. */
541 	ret->force_tun_device = primary->force_tun_device;
542 	if (ret->force_tun_device == -1)
543 		ret->force_tun_device = additional->force_tun_device;
544 	if (primary->nenv > 0) {
545 		if (dup_strings(&ret->env, &ret->nenv,
546 		    primary->env, primary->nenv) != 0)
547 			goto alloc_fail;
548 	} else if (additional->nenv) {
549 		if (dup_strings(&ret->env, &ret->nenv,
550 		    additional->env, additional->nenv) != 0)
551 			goto alloc_fail;
552 	}
553 	if (primary->npermitopen > 0) {
554 		if (dup_strings(&ret->permitopen, &ret->npermitopen,
555 		    primary->permitopen, primary->npermitopen) != 0)
556 			goto alloc_fail;
557 	} else if (additional->npermitopen > 0) {
558 		if (dup_strings(&ret->permitopen, &ret->npermitopen,
559 		    additional->permitopen, additional->npermitopen) != 0)
560 			goto alloc_fail;
561 	}
562 
563 	/* Flags are logical-AND (i.e. must be set in both for permission) */
564 #define OPTFLAG(x) ret->x = (primary->x == 1) && (additional->x == 1)
565 	OPTFLAG(permit_port_forwarding_flag);
566 	OPTFLAG(permit_agent_forwarding_flag);
567 	OPTFLAG(permit_x11_forwarding_flag);
568 	OPTFLAG(permit_pty_flag);
569 	OPTFLAG(permit_user_rc);
570 #undef OPTFLAG
571 
572 	/*
573 	 * When both multiple forced-command are specified, only
574 	 * proceed if they are identical, otherwise fail.
575 	 */
576 	if (primary->force_command != NULL &&
577 	    additional->force_command != NULL) {
578 		if (strcmp(primary->force_command,
579 		    additional->force_command) == 0) {
580 			/* ok */
581 			ret->force_command = strdup(primary->force_command);
582 			if (ret->force_command == NULL)
583 				goto alloc_fail;
584 		} else {
585 			errstr = "forced command options do not match";
586 			goto fail;
587 		}
588 	} else if (primary->force_command != NULL) {
589 		if ((ret->force_command = strdup(
590 		    primary->force_command)) == NULL)
591 			goto alloc_fail;
592 	} else if (additional->force_command != NULL) {
593 		if ((ret->force_command = strdup(
594 		    additional->force_command)) == NULL)
595 			goto alloc_fail;
596 	}
597 	/* success */
598 	if (errstrp != NULL)
599 		*errstrp = NULL;
600 	return ret;
601 
602  alloc_fail:
603 	errstr = "memory allocation failed";
604  fail:
605 	if (errstrp != NULL)
606 		*errstrp = errstr;
607 	sshauthopt_free(ret);
608 	return NULL;
609 }
610 
611 /*
612  * Copy options
613  */
614 struct sshauthopt *
615 sshauthopt_copy(const struct sshauthopt *orig)
616 {
617 	struct sshauthopt *ret;
618 
619 	if ((ret = sshauthopt_new()) == NULL)
620 		return NULL;
621 
622 #define OPTSCALAR(x) ret->x = orig->x
623 	OPTSCALAR(permit_port_forwarding_flag);
624 	OPTSCALAR(permit_agent_forwarding_flag);
625 	OPTSCALAR(permit_x11_forwarding_flag);
626 	OPTSCALAR(permit_pty_flag);
627 	OPTSCALAR(permit_user_rc);
628 	OPTSCALAR(restricted);
629 	OPTSCALAR(cert_authority);
630 	OPTSCALAR(force_tun_device);
631 #undef OPTSCALAR
632 #define OPTSTRING(x) \
633 	do { \
634 		if (orig->x != NULL && (ret->x = strdup(orig->x)) == NULL) { \
635 			sshauthopt_free(ret); \
636 			return NULL; \
637 		} \
638 	} while (0)
639 	OPTSTRING(cert_principals);
640 	OPTSTRING(force_command);
641 	OPTSTRING(required_from_host_cert);
642 	OPTSTRING(required_from_host_keys);
643 #undef OPTSTRING
644 
645 	if (dup_strings(&ret->env, &ret->nenv, orig->env, orig->nenv) != 0 ||
646 	    dup_strings(&ret->permitopen, &ret->npermitopen,
647 	    orig->permitopen, orig->npermitopen) != 0) {
648 		sshauthopt_free(ret);
649 		return NULL;
650 	}
651 	return ret;
652 }
653 
654 static int
655 serialise_array(struct sshbuf *m, char **a, size_t n)
656 {
657 	struct sshbuf *b;
658 	size_t i;
659 	int r;
660 
661 	if (n > INT_MAX)
662 		return SSH_ERR_INTERNAL_ERROR;
663 
664 	if ((b = sshbuf_new()) == NULL) {
665 		return SSH_ERR_ALLOC_FAIL;
666 	}
667 	for (i = 0; i < n; i++) {
668 		if ((r = sshbuf_put_cstring(b, a[i])) != 0) {
669 			sshbuf_free(b);
670 			return r;
671 		}
672 	}
673 	if ((r = sshbuf_put_u32(m, n)) != 0 ||
674 	    (r = sshbuf_put_stringb(m, b)) != 0) {
675 		sshbuf_free(b);
676 		return r;
677 	}
678 	/* success */
679 	return 0;
680 }
681 
682 static int
683 deserialise_array(struct sshbuf *m, char ***ap, size_t *np)
684 {
685 	char **a = NULL;
686 	size_t i, n = 0;
687 	struct sshbuf *b = NULL;
688 	u_int tmp;
689 	int r = SSH_ERR_INTERNAL_ERROR;
690 
691 	if ((r = sshbuf_get_u32(m, &tmp)) != 0 ||
692 	    (r = sshbuf_froms(m, &b)) != 0)
693 		goto out;
694 	if (tmp > INT_MAX) {
695 		r = SSH_ERR_INVALID_FORMAT;
696 		goto out;
697 	}
698 	n = tmp;
699 	if (n > 0 && (a = calloc(n, sizeof(*a))) == NULL) {
700 		r = SSH_ERR_ALLOC_FAIL;
701 		goto out;
702 	}
703 	for (i = 0; i < n; i++) {
704 		if ((r = sshbuf_get_cstring(b, &a[i], NULL)) != 0)
705 			goto out;
706 	}
707 	/* success */
708 	r = 0;
709 	*ap = a;
710 	a = NULL;
711 	*np = n;
712 	n = 0;
713  out:
714 	for (i = 0; i < n; i++)
715 		free(a[i]);
716 	free(a);
717 	sshbuf_free(b);
718 	return r;
719 }
720 
721 static int
722 serialise_nullable_string(struct sshbuf *m, const char *s)
723 {
724 	int r;
725 
726 	if ((r = sshbuf_put_u8(m, s == NULL)) != 0 ||
727 	    (r = sshbuf_put_cstring(m, s)) != 0)
728 		return r;
729 	return 0;
730 }
731 
732 static int
733 deserialise_nullable_string(struct sshbuf *m, char **sp)
734 {
735 	int r;
736 	u_char flag;
737 
738 	*sp = NULL;
739 	if ((r = sshbuf_get_u8(m, &flag)) != 0 ||
740 	    (r = sshbuf_get_cstring(m, flag ? NULL : sp, NULL)) != 0)
741 		return r;
742 	return 0;
743 }
744 
745 int
746 sshauthopt_serialise(const struct sshauthopt *opts, struct sshbuf *m,
747     int untrusted)
748 {
749 	int r = SSH_ERR_INTERNAL_ERROR;
750 
751 	/* Flag options */
752 	if ((r = sshbuf_put_u8(m, opts->permit_port_forwarding_flag)) != 0 ||
753 	    (r = sshbuf_put_u8(m, opts->permit_agent_forwarding_flag)) != 0 ||
754 	    (r = sshbuf_put_u8(m, opts->permit_x11_forwarding_flag)) != 0 ||
755 	    (r = sshbuf_put_u8(m, opts->permit_pty_flag)) != 0 ||
756 	    (r = sshbuf_put_u8(m, opts->permit_user_rc)) != 0 ||
757 	    (r = sshbuf_put_u8(m, opts->restricted)) != 0 ||
758 	    (r = sshbuf_put_u8(m, opts->cert_authority)) != 0)
759 		return r;
760 
761 	/* tunnel number can be negative to indicate "unset" */
762 	if ((r = sshbuf_put_u8(m, opts->force_tun_device == -1)) != 0 ||
763 	    (r = sshbuf_put_u32(m, (opts->force_tun_device < 0) ?
764 	    0 : (u_int)opts->force_tun_device)) != 0)
765 		return r;
766 
767 	/* String options; these may be NULL */
768 	if ((r = serialise_nullable_string(m,
769 	    untrusted ? "yes" : opts->cert_principals)) != 0 ||
770 	    (r = serialise_nullable_string(m,
771 	    untrusted ? "true" : opts->force_command)) != 0 ||
772 	    (r = serialise_nullable_string(m,
773 	    untrusted ? NULL : opts->required_from_host_cert)) != 0 ||
774 	    (r = serialise_nullable_string(m,
775 	     untrusted ? NULL : opts->required_from_host_keys)) != 0)
776 		return r;
777 
778 	/* Array options */
779 	if ((r = serialise_array(m, opts->env,
780 	    untrusted ? 0 : opts->nenv)) != 0 ||
781 	    (r = serialise_array(m, opts->permitopen,
782 	    untrusted ? 0 : opts->npermitopen)) != 0)
783 		return r;
784 
785 	/* success */
786 	return 0;
787 }
788 
789 int
790 sshauthopt_deserialise(struct sshbuf *m, struct sshauthopt **optsp)
791 {
792 	struct sshauthopt *opts = NULL;
793 	int r = SSH_ERR_INTERNAL_ERROR;
794 	u_char f;
795 	u_int tmp;
796 
797 	if ((opts = calloc(1, sizeof(*opts))) == NULL)
798 		return SSH_ERR_ALLOC_FAIL;
799 
800 #define OPT_FLAG(x) \
801 	do { \
802 		if ((r = sshbuf_get_u8(m, &f)) != 0) \
803 			goto out; \
804 		opts->x = f; \
805 	} while (0)
806 	OPT_FLAG(permit_port_forwarding_flag);
807 	OPT_FLAG(permit_agent_forwarding_flag);
808 	OPT_FLAG(permit_x11_forwarding_flag);
809 	OPT_FLAG(permit_pty_flag);
810 	OPT_FLAG(permit_user_rc);
811 	OPT_FLAG(restricted);
812 	OPT_FLAG(cert_authority);
813 #undef OPT_FLAG
814 
815 	/* tunnel number can be negative to indicate "unset" */
816 	if ((r = sshbuf_get_u8(m, &f)) != 0 ||
817 	    (r = sshbuf_get_u32(m, &tmp)) != 0)
818 		goto out;
819 	opts->force_tun_device = f ? -1 : (int)tmp;
820 
821 	/* String options may be NULL */
822 	if ((r = deserialise_nullable_string(m, &opts->cert_principals)) != 0 ||
823 	    (r = deserialise_nullable_string(m, &opts->force_command)) != 0 ||
824 	    (r = deserialise_nullable_string(m,
825 	    &opts->required_from_host_cert)) != 0 ||
826 	    (r = deserialise_nullable_string(m,
827 	    &opts->required_from_host_keys)) != 0)
828 		goto out;
829 
830 	/* Array options */
831 	if ((r = deserialise_array(m, &opts->env, &opts->nenv)) != 0 ||
832 	    (r = deserialise_array(m,
833 	    &opts->permitopen, &opts->npermitopen)) != 0)
834 		goto out;
835 
836 	/* success */
837 	r = 0;
838 	*optsp = opts;
839 	opts = NULL;
840  out:
841 	sshauthopt_free(opts);
842 	return r;
843 }
844