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