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