102 [KP_ENFORCE_STATFS] = {"enforce_statfs", 0}, 103 [KP_HOST_HOSTNAME] = {"host.hostname", 0}, 104#ifdef INET 105 [KP_IP4_ADDR] = {"ip4.addr", 0}, 106#endif 107#ifdef INET6 108 [KP_IP6_ADDR] = {"ip6.addr", 0}, 109#endif 110 [KP_JID] = {"jid", 0}, 111 [KP_NAME] = {"name", 0}, 112 [KP_PATH] = {"path", 0}, 113 [KP_PERSIST] = {"persist", 0}, 114 [KP_SECURELEVEL] = {"securelevel", 0}, 115 [KP_VNET] = {"vnet", 0}, 116}; 117 118/* 119 * Parse the jail configuration file. 120 */ 121void 122load_config(void) 123{ 124 struct cfjails wild; 125 struct cfparams opp; 126 struct cfjail *j, *tj, *wj; 127 struct cfparam *p, *vp, *tp; 128 struct cfstring *s, *vs, *ns; 129 struct cfvar *v; 130 char *ep; 131 size_t varoff; 132 int did_self, jseq, pgen; 133 134 if (!strcmp(cfname, "-")) { 135 cfname = "STDIN"; 136 yyin = stdin; 137 } else { 138 yyin = fopen(cfname, "r"); 139 if (!yyin) 140 err(1, "%s", cfname); 141 } 142 if (yyparse() || yynerrs) 143 exit(1); 144 145 /* Separate the wildcard jails out from the actual jails. */ 146 jseq = 0; 147 TAILQ_INIT(&wild); 148 TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) { 149 j->seq = ++jseq; 150 if (wild_jail_name(j->name)) 151 requeue(j, &wild); 152 } 153 154 TAILQ_FOREACH(j, &cfjails, tq) { 155 /* Set aside the jail's parameters. */ 156 TAILQ_INIT(&opp); 157 TAILQ_CONCAT(&opp, &j->params, tq); 158 /* 159 * The jail name implies its "name" or "jid" parameter, 160 * though they may also be explicitly set later on. 161 */ 162 add_param(j, NULL, 163 strtol(j->name, &ep, 10) && !*ep ? KP_JID : KP_NAME, 164 j->name); 165 /* 166 * Collect parameters for the jail, global parameters/variables, 167 * and any matching wildcard jails. 168 */ 169 did_self = 0; 170 TAILQ_FOREACH(wj, &wild, tq) { 171 if (j->seq < wj->seq && !did_self) { 172 TAILQ_FOREACH(p, &opp, tq) 173 add_param(j, p, 0, NULL); 174 did_self = 1; 175 } 176 if (wild_jail_match(j->name, wj->name)) 177 TAILQ_FOREACH(p, &wj->params, tq) 178 add_param(j, p, 0, NULL); 179 } 180 if (!did_self) 181 TAILQ_FOREACH(p, &opp, tq) 182 add_param(j, p, 0, NULL); 183 184 /* Resolve any variable substitutions. */ 185 pgen = 0; 186 TAILQ_FOREACH(p, &j->params, tq) { 187 p->gen = ++pgen; 188 find_vars: 189 TAILQ_FOREACH(s, &p->val, tq) { 190 varoff = 0; 191 while ((v = STAILQ_FIRST(&s->vars))) { 192 TAILQ_FOREACH(vp, &j->params, tq) 193 if (!strcmp(vp->name, v->name)) 194 break; 195 if (!vp) { 196 jail_warnx(j, 197 "%s: variable \"%s\" not found", 198 p->name, v->name); 199 bad_var: 200 j->flags |= JF_FAILED; 201 TAILQ_FOREACH(vp, &j->params, tq) 202 if (vp->gen == pgen) 203 vp->flags |= PF_BAD; 204 goto free_var; 205 } 206 if (vp->flags & PF_BAD) 207 goto bad_var; 208 if (vp->gen == pgen) { 209 jail_warnx(j, "%s: variable loop", 210 v->name); 211 goto bad_var; 212 } 213 TAILQ_FOREACH(vs, &vp->val, tq) 214 if (!STAILQ_EMPTY(&vs->vars)) { 215 vp->gen = pgen; 216 TAILQ_REMOVE(&j->params, vp, 217 tq); 218 TAILQ_INSERT_BEFORE(p, vp, tq); 219 p = vp; 220 goto find_vars; 221 } 222 vs = TAILQ_FIRST(&vp->val); 223 if (TAILQ_NEXT(vs, tq) != NULL && 224 (s->s[0] != '\0' || 225 STAILQ_NEXT(v, tq))) { 226 jail_warnx(j, "%s: array cannot be " 227 "substituted inline", 228 p->name); 229 goto bad_var; 230 } 231 s->s = erealloc(s->s, s->len + vs->len + 1); 232 memmove(s->s + v->pos + varoff + vs->len, 233 s->s + v->pos + varoff, 234 s->len - (v->pos + varoff) + 1); 235 memcpy(s->s + v->pos + varoff, vs->s, vs->len); 236 varoff += vs->len; 237 s->len += vs->len; 238 while ((vs = TAILQ_NEXT(vs, tq))) { 239 ns = emalloc(sizeof(struct cfstring)); 240 ns->s = estrdup(vs->s); 241 ns->len = vs->len; 242 STAILQ_INIT(&ns->vars); 243 TAILQ_INSERT_AFTER(&p->val, s, ns, tq); 244 s = ns; 245 } 246 free_var: 247 free(v->name); 248 STAILQ_REMOVE_HEAD(&s->vars, tq); 249 free(v); 250 } 251 } 252 } 253 254 /* Free the jail's original parameter list and any variables. */ 255 while ((p = TAILQ_FIRST(&opp))) 256 free_param(&opp, p); 257 TAILQ_FOREACH_SAFE(p, &j->params, tq, tp) 258 if (p->flags & PF_VAR) 259 free_param(&j->params, p); 260 } 261 while ((wj = TAILQ_FIRST(&wild))) { 262 free(wj->name); 263 while ((p = TAILQ_FIRST(&wj->params))) 264 free_param(&wj->params, p); 265 TAILQ_REMOVE(&wild, wj, tq); 266 } 267} 268 269/* 270 * Create a new jail record. 271 */ 272struct cfjail * 273add_jail(void) 274{ 275 struct cfjail *j; 276 277 j = emalloc(sizeof(struct cfjail)); 278 memset(j, 0, sizeof(struct cfjail)); 279 TAILQ_INIT(&j->params); 280 STAILQ_INIT(&j->dep[DEP_FROM]); 281 STAILQ_INIT(&j->dep[DEP_TO]); 282 j->queue = &cfjails; 283 TAILQ_INSERT_TAIL(&cfjails, j, tq); 284 return j; 285} 286 287/* 288 * Add a parameter to a jail. 289 */ 290void 291add_param(struct cfjail *j, const struct cfparam *p, enum intparam ipnum, 292 const char *value) 293{ 294 struct cfstrings nss; 295 struct cfparam *dp, *np; 296 struct cfstring *s, *ns; 297 struct cfvar *v, *nv; 298 const char *name; 299 char *cs, *tname; 300 unsigned flags; 301 302 if (j == NULL) { 303 /* Create a single anonymous jail if one doesn't yet exist. */ 304 j = TAILQ_LAST(&cfjails, cfjails); 305 if (j == NULL) 306 j = add_jail(); 307 } 308 TAILQ_INIT(&nss); 309 if (p != NULL) { 310 name = p->name; 311 flags = p->flags; 312 /* 313 * Make a copy of the parameter's string list, 314 * which may be freed if it's overridden later. 315 */ 316 TAILQ_FOREACH(s, &p->val, tq) { 317 ns = emalloc(sizeof(struct cfstring)); 318 ns->s = estrdup(s->s); 319 ns->len = s->len; 320 STAILQ_INIT(&ns->vars); 321 STAILQ_FOREACH(v, &s->vars, tq) { 322 nv = emalloc(sizeof(struct cfvar)); 323 nv->name = strdup(v->name); 324 nv->pos = v->pos; 325 STAILQ_INSERT_TAIL(&ns->vars, nv, tq); 326 } 327 TAILQ_INSERT_TAIL(&nss, ns, tq); 328 } 329 } else { 330 flags = PF_APPEND; 331 if (ipnum != 0) { 332 name = intparams[ipnum].name; 333 flags |= intparams[ipnum].flags; 334 } else if ((cs = strchr(value, '='))) { 335 tname = alloca(cs - value + 1); 336 strlcpy(tname, value, cs - value + 1); 337 name = tname; 338 value = cs + 1; 339 } else { 340 name = value; 341 value = NULL; 342 } 343 if (value != NULL) { 344 ns = emalloc(sizeof(struct cfstring)); 345 ns->s = estrdup(value); 346 ns->len = strlen(value); 347 STAILQ_INIT(&ns->vars); 348 TAILQ_INSERT_TAIL(&nss, ns, tq); 349 } 350 } 351 352 /* See if this parameter has already been added. */ 353 if (ipnum != 0) 354 dp = j->intparams[ipnum]; 355 else 356 TAILQ_FOREACH(dp, &j->params, tq) 357 if (!(dp->flags & PF_CONV) && equalopts(dp->name, name)) 358 break; 359 if (dp != NULL) { 360 /* Found it - append or replace. */ 361 if (strcmp(dp->name, name)) { 362 free(dp->name); 363 dp->name = estrdup(name); 364 } 365 if (!(flags & PF_APPEND) || TAILQ_EMPTY(&nss)) 366 free_param_strings(dp); 367 TAILQ_CONCAT(&dp->val, &nss, tq); 368 dp->flags |= flags; 369 } else { 370 /* Not found - add it. */ 371 np = emalloc(sizeof(struct cfparam)); 372 np->name = estrdup(name); 373 TAILQ_INIT(&np->val); 374 TAILQ_CONCAT(&np->val, &nss, tq); 375 np->flags = flags; 376 np->gen = 0; 377 TAILQ_INSERT_TAIL(&j->params, np, tq); 378 if (ipnum != 0) 379 j->intparams[ipnum] = np; 380 else 381 for (ipnum = 1; ipnum < IP_NPARAM; ipnum++) 382 if (!(intparams[ipnum].flags & PF_CONV) && 383 equalopts(name, intparams[ipnum].name)) { 384 j->intparams[ipnum] = np; 385 np->flags |= intparams[ipnum].flags; 386 break; 387 } 388 } 389} 390 391/* 392 * Return if a boolean parameter exists and is true. 393 */ 394int 395bool_param(const struct cfparam *p) 396{ 397 const char *cs; 398 399 if (p == NULL) 400 return 0; 401 cs = strrchr(p->name, '.'); 402 return !strncmp(cs ? cs + 1 : p->name, "no", 2) ^ 403 (TAILQ_EMPTY(&p->val) || 404 !strcasecmp(TAILQ_LAST(&p->val, cfstrings)->s, "true") || 405 (strtol(TAILQ_LAST(&p->val, cfstrings)->s, NULL, 10))); 406} 407 408/* 409 * Set an integer if a parameter if it exists. 410 */ 411int 412int_param(const struct cfparam *p, int *ip) 413{ 414 if (p == NULL || TAILQ_EMPTY(&p->val)) 415 return 0; 416 *ip = strtol(TAILQ_LAST(&p->val, cfstrings)->s, NULL, 10); 417 return 1; 418} 419 420/* 421 * Return the string value of a scalar parameter if it exists. 422 */ 423const char * 424string_param(const struct cfparam *p) 425{ 426 return (p && !TAILQ_EMPTY(&p->val) 427 ? TAILQ_LAST(&p->val, cfstrings)->s : NULL); 428} 429 430/* 431 * Check syntax and values of internal parameters. Set some internal 432 * parameters based on the values of others. 433 */ 434int 435check_intparams(struct cfjail *j) 436{ 437 struct cfparam *p; 438 struct cfstring *s; 439 FILE *f; 440 const char *val; 441 char *cs, *ep, *ln; 442 size_t lnlen; 443 int error; 444#if defined(INET) || defined(INET6) 445 struct addrinfo hints; 446 struct addrinfo *ai0, *ai; 447 const char *hostname; 448 int gicode, defif, prefix; 449#endif 450#ifdef INET 451 struct in_addr addr4; 452 int ip4ok; 453 char avalue4[INET_ADDRSTRLEN]; 454#endif 455#ifdef INET6 456 struct in6_addr addr6; 457 int ip6ok; 458 char avalue6[INET6_ADDRSTRLEN]; 459#endif 460 461 error = 0; 462 /* Check format of boolan and integer values. */ 463 TAILQ_FOREACH(p, &j->params, tq) { 464 if (!TAILQ_EMPTY(&p->val) && (p->flags & (PF_BOOL | PF_INT))) { 465 val = TAILQ_LAST(&p->val, cfstrings)->s; 466 if (p->flags & PF_BOOL) { 467 if (strcasecmp(val, "false") && 468 strcasecmp(val, "true") && 469 ((void)strtol(val, &ep, 10), *ep)) { 470 jail_warnx(j, 471 "%s: unknown boolean value \"%s\"", 472 p->name, val); 473 error = -1; 474 } 475 } else { 476 (void)strtol(val, &ep, 10); 477 if (ep == val || *ep) { 478 jail_warnx(j, 479 "%s: non-integer value \"%s\"", 480 p->name, val); 481 error = -1; 482 } 483 } 484 } 485 } 486 487#if defined(INET) || defined(INET6) 488 /* 489 * The ip_hostname parameter looks up the hostname, and adds parameters 490 * for any IP addresses it finds. 491 */ 492 if (((j->flags & JF_OP_MASK) != JF_STOP || 493 j->intparams[IP_INTERFACE] != NULL) && 494 bool_param(j->intparams[IP_IP_HOSTNAME]) && 495 (hostname = string_param(j->intparams[KP_HOST_HOSTNAME]))) { 496 j->intparams[IP_IP_HOSTNAME] = NULL; 497 /* 498 * Silently ignore unsupported address families from 499 * DNS lookups. 500 */ 501#ifdef INET 502 ip4ok = feature_present("inet"); 503#endif 504#ifdef INET6 505 ip6ok = feature_present("inet6"); 506#endif 507 if ( 508#if defined(INET) && defined(INET6) 509 ip4ok || ip6ok 510#elif defined(INET) 511 ip4ok 512#elif defined(INET6) 513 ip6ok 514#endif 515 ) { 516 /* Look up the hostname (or get the address) */ 517 memset(&hints, 0, sizeof(hints)); 518 hints.ai_socktype = SOCK_STREAM; 519 hints.ai_family = 520#if defined(INET) && defined(INET6) 521 ip4ok ? (ip6ok ? PF_UNSPEC : PF_INET) : PF_INET6; 522#elif defined(INET) 523 PF_INET; 524#elif defined(INET6) 525 PF_INET6; 526#endif 527 gicode = getaddrinfo(hostname, NULL, &hints, &ai0); 528 if (gicode != 0) { 529 jail_warnx(j, "host.hostname %s: %s", hostname, 530 gai_strerror(gicode)); 531 error = -1; 532 } else { 533 /* 534 * Convert the addresses to ASCII so jailparam 535 * can convert them back. Errors are not 536 * expected here. 537 */ 538 for (ai = ai0; ai; ai = ai->ai_next) 539 switch (ai->ai_family) { 540#ifdef INET 541 case AF_INET: 542 memcpy(&addr4, 543 &((struct sockaddr_in *) 544 (void *)ai->ai_addr)-> 545 sin_addr, sizeof(addr4)); 546 if (inet_ntop(AF_INET, 547 &addr4, avalue4, 548 INET_ADDRSTRLEN) == NULL) 549 err(1, "inet_ntop"); 550 add_param(j, NULL, KP_IP4_ADDR, 551 avalue4); 552 break; 553#endif 554#ifdef INET6 555 case AF_INET6: 556 memcpy(&addr6, 557 &((struct sockaddr_in6 *) 558 (void *)ai->ai_addr)-> 559 sin6_addr, sizeof(addr6)); 560 if (inet_ntop(AF_INET6, 561 &addr6, avalue6, 562 INET6_ADDRSTRLEN) == NULL) 563 err(1, "inet_ntop"); 564 add_param(j, NULL, KP_IP6_ADDR, 565 avalue6); 566 break; 567#endif 568 } 569 freeaddrinfo(ai0); 570 } 571 } 572 } 573 574 /* 575 * IP addresses may include an interface to set that address on, 576 * and a netmask/suffix for that address. 577 */ 578 defif = string_param(j->intparams[IP_INTERFACE]) != NULL; 579#ifdef INET 580 if (j->intparams[KP_IP4_ADDR] != NULL) { 581 TAILQ_FOREACH(s, &j->intparams[KP_IP4_ADDR]->val, tq) { 582 cs = strchr(s->s, '|'); 583 if (cs || defif) 584 add_param(j, NULL, IP__IP4_IFADDR, s->s); 585 if (cs) { 586 strcpy(s->s, cs + 1); 587 s->len -= cs + 1 - s->s; 588 } 589 if ((cs = strchr(s->s, '/'))) { 590 prefix = strtol(cs + 1, &ep, 10); 591 if (*ep == '.' 592 ? inet_pton(AF_INET, cs + 1, &addr4) != 1 593 : *ep || prefix < 0 || prefix > 32) { 594 jail_warnx(j, 595 "ip4.addr: bad netmask \"%s\"", cs); 596 error = -1; 597 } 598 *cs = '\0'; 599 s->len = cs - s->s + 1; 600 } 601 } 602 } 603#endif 604#ifdef INET6 605 if (j->intparams[KP_IP6_ADDR] != NULL) { 606 TAILQ_FOREACH(s, &j->intparams[KP_IP6_ADDR]->val, tq) { 607 cs = strchr(s->s, '|'); 608 if (cs || defif) 609 add_param(j, NULL, IP__IP6_IFADDR, s->s); 610 if (cs) { 611 strcpy(s->s, cs + 1); 612 s->len -= cs + 1 - s->s; 613 } 614 if ((cs = strchr(s->s, '/'))) { 615 prefix = strtol(cs + 1, &ep, 10); 616 if (*ep || prefix < 0 || prefix > 128) { 617 jail_warnx(j, 618 "ip6.addr: bad prefixlen \"%s\"", 619 cs); 620 error = -1; 621 } 622 *cs = '\0'; 623 s->len = cs - s->s + 1; 624 } 625 } 626 } 627#endif 628#endif 629 630 /* 631 * Read mount.fstab file(s), and treat each line as its own mount 632 * parameter. 633 */ 634 if (j->intparams[IP_MOUNT_FSTAB] != NULL) { 635 TAILQ_FOREACH(s, &j->intparams[IP_MOUNT_FSTAB]->val, tq) { 636 if (s->len == 0) 637 continue; 638 f = fopen(s->s, "r"); 639 if (f == NULL) { 640 jail_warnx(j, "mount.fstab: %s: %s", 641 s->s, strerror(errno)); 642 error = -1; 643 continue; 644 } 645 while ((ln = fgetln(f, &lnlen))) { 646 if ((cs = memchr(ln, '#', lnlen - 1))) 647 lnlen = cs - ln + 1; 648 if (ln[lnlen - 1] == '\n' || 649 ln[lnlen - 1] == '#') 650 ln[lnlen - 1] = '\0'; 651 else { 652 cs = alloca(lnlen + 1); 653 strlcpy(cs, ln, lnlen + 1); 654 ln = cs; 655 } 656 add_param(j, NULL, IP__MOUNT_FROM_FSTAB, ln); 657 } 658 fclose(f); 659 } 660 } 661 if (error) 662 failed(j); 663 return error; 664} 665 666/* 667 * Import parameters into libjail's binary jailparam format. 668 */ 669int 670import_params(struct cfjail *j) 671{ 672 struct cfparam *p; 673 struct cfstring *s, *ts; 674 struct jailparam *jp; 675 char *value, *cs; 676 size_t vallen; 677 int error; 678 679 error = 0; 680 j->njp = 0; 681 TAILQ_FOREACH(p, &j->params, tq) 682 if (!(p->flags & PF_INTERNAL)) 683 j->njp++; 684 j->jp = jp = emalloc(j->njp * sizeof(struct jailparam)); 685 TAILQ_FOREACH(p, &j->params, tq) { 686 if (p->flags & PF_INTERNAL) 687 continue; 688 if (jailparam_init(jp, p->name) < 0) { 689 error = -1; 690 jail_warnx(j, "%s", jail_errmsg); 691 continue; 692 } 693 if (TAILQ_EMPTY(&p->val)) 694 value = NULL; 695 else if (!jp->jp_elemlen || 696 !TAILQ_NEXT(TAILQ_FIRST(&p->val), tq)) { 697 /* 698 * Scalar parameters silently discard multiple (array) 699 * values, keeping only the last value added. This 700 * lets values added from the command line append to 701 * arrays wthout pre-checking the type. 702 */ 703 value = TAILQ_LAST(&p->val, cfstrings)->s; 704 } else { 705 /* 706 * Convert arrays into comma-separated strings, which 707 * jailparam_import will then convert back into arrays. 708 */ 709 vallen = 0; 710 TAILQ_FOREACH(s, &p->val, tq) 711 vallen += s->len + 1; 712 value = alloca(vallen); 713 cs = value; 714 TAILQ_FOREACH_SAFE(s, &p->val, tq, ts) { 715 strcpy(cs, s->s); 716 if (ts != NULL) { 717 cs += s->len + 1; 718 cs[-1] = ','; 719 } 720 } 721 } 722 if (jailparam_import(jp, value) < 0) { 723 error = -1; 724 jail_warnx(j, "%s", jail_errmsg); 725 } 726 jp++; 727 } 728 if (error) { 729 jailparam_free(j->jp, j->njp); 730 free(j->jp); 731 j->jp = NULL; 732 failed(j); 733 } 734 return error; 735} 736 737/* 738 * Check if options are equal (with or without the "no" prefix). 739 */ 740int 741equalopts(const char *opt1, const char *opt2) 742{ 743 char *p; 744 745 /* "opt" vs. "opt" or "noopt" vs. "noopt" */ 746 if (strcmp(opt1, opt2) == 0) 747 return (1); 748 /* "noopt" vs. "opt" */ 749 if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0) 750 return (1); 751 /* "opt" vs. "noopt" */ 752 if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0) 753 return (1); 754 while ((p = strchr(opt1, '.')) != NULL && 755 !strncmp(opt1, opt2, ++p - opt1)) { 756 opt2 += p - opt1; 757 opt1 = p; 758 /* "foo.noopt" vs. "foo.opt" */ 759 if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0) 760 return (1); 761 /* "foo.opt" vs. "foo.noopt" */ 762 if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0) 763 return (1); 764 } 765 return (0); 766} 767 768/* 769 * See if a jail name matches a wildcard. 770 */ 771int 772wild_jail_match(const char *jname, const char *wname) 773{ 774 const char *jc, *jd, *wc, *wd; 775 776 /* 777 * A non-final "*" component in the wild name matches a single jail 778 * component, and a final "*" matches one or more jail components. 779 */ 780 for (jc = jname, wc = wname; 781 (jd = strchr(jc, '.')) && (wd = strchr(wc, '.')); 782 jc = jd + 1, wc = wd + 1) 783 if (strncmp(jc, wc, jd - jc + 1) && strncmp(wc, "*.", 2)) 784 return 0; 785 return (!strcmp(jc, wc) || !strcmp(wc, "*")); 786} 787 788/* 789 * Return if a jail name is a wildcard. 790 */ 791int 792wild_jail_name(const char *wname) 793{ 794 const char *wc; 795 796 for (wc = strchr(wname, '*'); wc; wc = strchr(wc + 1, '*')) 797 if ((wc == wname || wc[-1] == '.') && 798 (wc[1] == '\0' || wc[1] == '.')) 799 return 1; 800 return 0; 801} 802 803/* 804 * Free a parameter record and all its strings and variables. 805 */ 806static void 807free_param(struct cfparams *pp, struct cfparam *p) 808{ 809 free(p->name); 810 free_param_strings(p); 811 TAILQ_REMOVE(pp, p, tq); 812 free(p); 813} 814 815static void 816free_param_strings(struct cfparam *p) 817{ 818 struct cfstring *s; 819 struct cfvar *v; 820 821 while ((s = TAILQ_FIRST(&p->val))) { 822 free(s->s); 823 while ((v = STAILQ_FIRST(&s->vars))) { 824 free(v->name); 825 STAILQ_REMOVE_HEAD(&s->vars, tq); 826 free(v); 827 } 828 TAILQ_REMOVE(&p->val, s, tq); 829 free(s); 830 } 831}
| 102 [KP_ENFORCE_STATFS] = {"enforce_statfs", 0}, 103 [KP_HOST_HOSTNAME] = {"host.hostname", 0}, 104#ifdef INET 105 [KP_IP4_ADDR] = {"ip4.addr", 0}, 106#endif 107#ifdef INET6 108 [KP_IP6_ADDR] = {"ip6.addr", 0}, 109#endif 110 [KP_JID] = {"jid", 0}, 111 [KP_NAME] = {"name", 0}, 112 [KP_PATH] = {"path", 0}, 113 [KP_PERSIST] = {"persist", 0}, 114 [KP_SECURELEVEL] = {"securelevel", 0}, 115 [KP_VNET] = {"vnet", 0}, 116}; 117 118/* 119 * Parse the jail configuration file. 120 */ 121void 122load_config(void) 123{ 124 struct cfjails wild; 125 struct cfparams opp; 126 struct cfjail *j, *tj, *wj; 127 struct cfparam *p, *vp, *tp; 128 struct cfstring *s, *vs, *ns; 129 struct cfvar *v; 130 char *ep; 131 size_t varoff; 132 int did_self, jseq, pgen; 133 134 if (!strcmp(cfname, "-")) { 135 cfname = "STDIN"; 136 yyin = stdin; 137 } else { 138 yyin = fopen(cfname, "r"); 139 if (!yyin) 140 err(1, "%s", cfname); 141 } 142 if (yyparse() || yynerrs) 143 exit(1); 144 145 /* Separate the wildcard jails out from the actual jails. */ 146 jseq = 0; 147 TAILQ_INIT(&wild); 148 TAILQ_FOREACH_SAFE(j, &cfjails, tq, tj) { 149 j->seq = ++jseq; 150 if (wild_jail_name(j->name)) 151 requeue(j, &wild); 152 } 153 154 TAILQ_FOREACH(j, &cfjails, tq) { 155 /* Set aside the jail's parameters. */ 156 TAILQ_INIT(&opp); 157 TAILQ_CONCAT(&opp, &j->params, tq); 158 /* 159 * The jail name implies its "name" or "jid" parameter, 160 * though they may also be explicitly set later on. 161 */ 162 add_param(j, NULL, 163 strtol(j->name, &ep, 10) && !*ep ? KP_JID : KP_NAME, 164 j->name); 165 /* 166 * Collect parameters for the jail, global parameters/variables, 167 * and any matching wildcard jails. 168 */ 169 did_self = 0; 170 TAILQ_FOREACH(wj, &wild, tq) { 171 if (j->seq < wj->seq && !did_self) { 172 TAILQ_FOREACH(p, &opp, tq) 173 add_param(j, p, 0, NULL); 174 did_self = 1; 175 } 176 if (wild_jail_match(j->name, wj->name)) 177 TAILQ_FOREACH(p, &wj->params, tq) 178 add_param(j, p, 0, NULL); 179 } 180 if (!did_self) 181 TAILQ_FOREACH(p, &opp, tq) 182 add_param(j, p, 0, NULL); 183 184 /* Resolve any variable substitutions. */ 185 pgen = 0; 186 TAILQ_FOREACH(p, &j->params, tq) { 187 p->gen = ++pgen; 188 find_vars: 189 TAILQ_FOREACH(s, &p->val, tq) { 190 varoff = 0; 191 while ((v = STAILQ_FIRST(&s->vars))) { 192 TAILQ_FOREACH(vp, &j->params, tq) 193 if (!strcmp(vp->name, v->name)) 194 break; 195 if (!vp) { 196 jail_warnx(j, 197 "%s: variable \"%s\" not found", 198 p->name, v->name); 199 bad_var: 200 j->flags |= JF_FAILED; 201 TAILQ_FOREACH(vp, &j->params, tq) 202 if (vp->gen == pgen) 203 vp->flags |= PF_BAD; 204 goto free_var; 205 } 206 if (vp->flags & PF_BAD) 207 goto bad_var; 208 if (vp->gen == pgen) { 209 jail_warnx(j, "%s: variable loop", 210 v->name); 211 goto bad_var; 212 } 213 TAILQ_FOREACH(vs, &vp->val, tq) 214 if (!STAILQ_EMPTY(&vs->vars)) { 215 vp->gen = pgen; 216 TAILQ_REMOVE(&j->params, vp, 217 tq); 218 TAILQ_INSERT_BEFORE(p, vp, tq); 219 p = vp; 220 goto find_vars; 221 } 222 vs = TAILQ_FIRST(&vp->val); 223 if (TAILQ_NEXT(vs, tq) != NULL && 224 (s->s[0] != '\0' || 225 STAILQ_NEXT(v, tq))) { 226 jail_warnx(j, "%s: array cannot be " 227 "substituted inline", 228 p->name); 229 goto bad_var; 230 } 231 s->s = erealloc(s->s, s->len + vs->len + 1); 232 memmove(s->s + v->pos + varoff + vs->len, 233 s->s + v->pos + varoff, 234 s->len - (v->pos + varoff) + 1); 235 memcpy(s->s + v->pos + varoff, vs->s, vs->len); 236 varoff += vs->len; 237 s->len += vs->len; 238 while ((vs = TAILQ_NEXT(vs, tq))) { 239 ns = emalloc(sizeof(struct cfstring)); 240 ns->s = estrdup(vs->s); 241 ns->len = vs->len; 242 STAILQ_INIT(&ns->vars); 243 TAILQ_INSERT_AFTER(&p->val, s, ns, tq); 244 s = ns; 245 } 246 free_var: 247 free(v->name); 248 STAILQ_REMOVE_HEAD(&s->vars, tq); 249 free(v); 250 } 251 } 252 } 253 254 /* Free the jail's original parameter list and any variables. */ 255 while ((p = TAILQ_FIRST(&opp))) 256 free_param(&opp, p); 257 TAILQ_FOREACH_SAFE(p, &j->params, tq, tp) 258 if (p->flags & PF_VAR) 259 free_param(&j->params, p); 260 } 261 while ((wj = TAILQ_FIRST(&wild))) { 262 free(wj->name); 263 while ((p = TAILQ_FIRST(&wj->params))) 264 free_param(&wj->params, p); 265 TAILQ_REMOVE(&wild, wj, tq); 266 } 267} 268 269/* 270 * Create a new jail record. 271 */ 272struct cfjail * 273add_jail(void) 274{ 275 struct cfjail *j; 276 277 j = emalloc(sizeof(struct cfjail)); 278 memset(j, 0, sizeof(struct cfjail)); 279 TAILQ_INIT(&j->params); 280 STAILQ_INIT(&j->dep[DEP_FROM]); 281 STAILQ_INIT(&j->dep[DEP_TO]); 282 j->queue = &cfjails; 283 TAILQ_INSERT_TAIL(&cfjails, j, tq); 284 return j; 285} 286 287/* 288 * Add a parameter to a jail. 289 */ 290void 291add_param(struct cfjail *j, const struct cfparam *p, enum intparam ipnum, 292 const char *value) 293{ 294 struct cfstrings nss; 295 struct cfparam *dp, *np; 296 struct cfstring *s, *ns; 297 struct cfvar *v, *nv; 298 const char *name; 299 char *cs, *tname; 300 unsigned flags; 301 302 if (j == NULL) { 303 /* Create a single anonymous jail if one doesn't yet exist. */ 304 j = TAILQ_LAST(&cfjails, cfjails); 305 if (j == NULL) 306 j = add_jail(); 307 } 308 TAILQ_INIT(&nss); 309 if (p != NULL) { 310 name = p->name; 311 flags = p->flags; 312 /* 313 * Make a copy of the parameter's string list, 314 * which may be freed if it's overridden later. 315 */ 316 TAILQ_FOREACH(s, &p->val, tq) { 317 ns = emalloc(sizeof(struct cfstring)); 318 ns->s = estrdup(s->s); 319 ns->len = s->len; 320 STAILQ_INIT(&ns->vars); 321 STAILQ_FOREACH(v, &s->vars, tq) { 322 nv = emalloc(sizeof(struct cfvar)); 323 nv->name = strdup(v->name); 324 nv->pos = v->pos; 325 STAILQ_INSERT_TAIL(&ns->vars, nv, tq); 326 } 327 TAILQ_INSERT_TAIL(&nss, ns, tq); 328 } 329 } else { 330 flags = PF_APPEND; 331 if (ipnum != 0) { 332 name = intparams[ipnum].name; 333 flags |= intparams[ipnum].flags; 334 } else if ((cs = strchr(value, '='))) { 335 tname = alloca(cs - value + 1); 336 strlcpy(tname, value, cs - value + 1); 337 name = tname; 338 value = cs + 1; 339 } else { 340 name = value; 341 value = NULL; 342 } 343 if (value != NULL) { 344 ns = emalloc(sizeof(struct cfstring)); 345 ns->s = estrdup(value); 346 ns->len = strlen(value); 347 STAILQ_INIT(&ns->vars); 348 TAILQ_INSERT_TAIL(&nss, ns, tq); 349 } 350 } 351 352 /* See if this parameter has already been added. */ 353 if (ipnum != 0) 354 dp = j->intparams[ipnum]; 355 else 356 TAILQ_FOREACH(dp, &j->params, tq) 357 if (!(dp->flags & PF_CONV) && equalopts(dp->name, name)) 358 break; 359 if (dp != NULL) { 360 /* Found it - append or replace. */ 361 if (strcmp(dp->name, name)) { 362 free(dp->name); 363 dp->name = estrdup(name); 364 } 365 if (!(flags & PF_APPEND) || TAILQ_EMPTY(&nss)) 366 free_param_strings(dp); 367 TAILQ_CONCAT(&dp->val, &nss, tq); 368 dp->flags |= flags; 369 } else { 370 /* Not found - add it. */ 371 np = emalloc(sizeof(struct cfparam)); 372 np->name = estrdup(name); 373 TAILQ_INIT(&np->val); 374 TAILQ_CONCAT(&np->val, &nss, tq); 375 np->flags = flags; 376 np->gen = 0; 377 TAILQ_INSERT_TAIL(&j->params, np, tq); 378 if (ipnum != 0) 379 j->intparams[ipnum] = np; 380 else 381 for (ipnum = 1; ipnum < IP_NPARAM; ipnum++) 382 if (!(intparams[ipnum].flags & PF_CONV) && 383 equalopts(name, intparams[ipnum].name)) { 384 j->intparams[ipnum] = np; 385 np->flags |= intparams[ipnum].flags; 386 break; 387 } 388 } 389} 390 391/* 392 * Return if a boolean parameter exists and is true. 393 */ 394int 395bool_param(const struct cfparam *p) 396{ 397 const char *cs; 398 399 if (p == NULL) 400 return 0; 401 cs = strrchr(p->name, '.'); 402 return !strncmp(cs ? cs + 1 : p->name, "no", 2) ^ 403 (TAILQ_EMPTY(&p->val) || 404 !strcasecmp(TAILQ_LAST(&p->val, cfstrings)->s, "true") || 405 (strtol(TAILQ_LAST(&p->val, cfstrings)->s, NULL, 10))); 406} 407 408/* 409 * Set an integer if a parameter if it exists. 410 */ 411int 412int_param(const struct cfparam *p, int *ip) 413{ 414 if (p == NULL || TAILQ_EMPTY(&p->val)) 415 return 0; 416 *ip = strtol(TAILQ_LAST(&p->val, cfstrings)->s, NULL, 10); 417 return 1; 418} 419 420/* 421 * Return the string value of a scalar parameter if it exists. 422 */ 423const char * 424string_param(const struct cfparam *p) 425{ 426 return (p && !TAILQ_EMPTY(&p->val) 427 ? TAILQ_LAST(&p->val, cfstrings)->s : NULL); 428} 429 430/* 431 * Check syntax and values of internal parameters. Set some internal 432 * parameters based on the values of others. 433 */ 434int 435check_intparams(struct cfjail *j) 436{ 437 struct cfparam *p; 438 struct cfstring *s; 439 FILE *f; 440 const char *val; 441 char *cs, *ep, *ln; 442 size_t lnlen; 443 int error; 444#if defined(INET) || defined(INET6) 445 struct addrinfo hints; 446 struct addrinfo *ai0, *ai; 447 const char *hostname; 448 int gicode, defif, prefix; 449#endif 450#ifdef INET 451 struct in_addr addr4; 452 int ip4ok; 453 char avalue4[INET_ADDRSTRLEN]; 454#endif 455#ifdef INET6 456 struct in6_addr addr6; 457 int ip6ok; 458 char avalue6[INET6_ADDRSTRLEN]; 459#endif 460 461 error = 0; 462 /* Check format of boolan and integer values. */ 463 TAILQ_FOREACH(p, &j->params, tq) { 464 if (!TAILQ_EMPTY(&p->val) && (p->flags & (PF_BOOL | PF_INT))) { 465 val = TAILQ_LAST(&p->val, cfstrings)->s; 466 if (p->flags & PF_BOOL) { 467 if (strcasecmp(val, "false") && 468 strcasecmp(val, "true") && 469 ((void)strtol(val, &ep, 10), *ep)) { 470 jail_warnx(j, 471 "%s: unknown boolean value \"%s\"", 472 p->name, val); 473 error = -1; 474 } 475 } else { 476 (void)strtol(val, &ep, 10); 477 if (ep == val || *ep) { 478 jail_warnx(j, 479 "%s: non-integer value \"%s\"", 480 p->name, val); 481 error = -1; 482 } 483 } 484 } 485 } 486 487#if defined(INET) || defined(INET6) 488 /* 489 * The ip_hostname parameter looks up the hostname, and adds parameters 490 * for any IP addresses it finds. 491 */ 492 if (((j->flags & JF_OP_MASK) != JF_STOP || 493 j->intparams[IP_INTERFACE] != NULL) && 494 bool_param(j->intparams[IP_IP_HOSTNAME]) && 495 (hostname = string_param(j->intparams[KP_HOST_HOSTNAME]))) { 496 j->intparams[IP_IP_HOSTNAME] = NULL; 497 /* 498 * Silently ignore unsupported address families from 499 * DNS lookups. 500 */ 501#ifdef INET 502 ip4ok = feature_present("inet"); 503#endif 504#ifdef INET6 505 ip6ok = feature_present("inet6"); 506#endif 507 if ( 508#if defined(INET) && defined(INET6) 509 ip4ok || ip6ok 510#elif defined(INET) 511 ip4ok 512#elif defined(INET6) 513 ip6ok 514#endif 515 ) { 516 /* Look up the hostname (or get the address) */ 517 memset(&hints, 0, sizeof(hints)); 518 hints.ai_socktype = SOCK_STREAM; 519 hints.ai_family = 520#if defined(INET) && defined(INET6) 521 ip4ok ? (ip6ok ? PF_UNSPEC : PF_INET) : PF_INET6; 522#elif defined(INET) 523 PF_INET; 524#elif defined(INET6) 525 PF_INET6; 526#endif 527 gicode = getaddrinfo(hostname, NULL, &hints, &ai0); 528 if (gicode != 0) { 529 jail_warnx(j, "host.hostname %s: %s", hostname, 530 gai_strerror(gicode)); 531 error = -1; 532 } else { 533 /* 534 * Convert the addresses to ASCII so jailparam 535 * can convert them back. Errors are not 536 * expected here. 537 */ 538 for (ai = ai0; ai; ai = ai->ai_next) 539 switch (ai->ai_family) { 540#ifdef INET 541 case AF_INET: 542 memcpy(&addr4, 543 &((struct sockaddr_in *) 544 (void *)ai->ai_addr)-> 545 sin_addr, sizeof(addr4)); 546 if (inet_ntop(AF_INET, 547 &addr4, avalue4, 548 INET_ADDRSTRLEN) == NULL) 549 err(1, "inet_ntop"); 550 add_param(j, NULL, KP_IP4_ADDR, 551 avalue4); 552 break; 553#endif 554#ifdef INET6 555 case AF_INET6: 556 memcpy(&addr6, 557 &((struct sockaddr_in6 *) 558 (void *)ai->ai_addr)-> 559 sin6_addr, sizeof(addr6)); 560 if (inet_ntop(AF_INET6, 561 &addr6, avalue6, 562 INET6_ADDRSTRLEN) == NULL) 563 err(1, "inet_ntop"); 564 add_param(j, NULL, KP_IP6_ADDR, 565 avalue6); 566 break; 567#endif 568 } 569 freeaddrinfo(ai0); 570 } 571 } 572 } 573 574 /* 575 * IP addresses may include an interface to set that address on, 576 * and a netmask/suffix for that address. 577 */ 578 defif = string_param(j->intparams[IP_INTERFACE]) != NULL; 579#ifdef INET 580 if (j->intparams[KP_IP4_ADDR] != NULL) { 581 TAILQ_FOREACH(s, &j->intparams[KP_IP4_ADDR]->val, tq) { 582 cs = strchr(s->s, '|'); 583 if (cs || defif) 584 add_param(j, NULL, IP__IP4_IFADDR, s->s); 585 if (cs) { 586 strcpy(s->s, cs + 1); 587 s->len -= cs + 1 - s->s; 588 } 589 if ((cs = strchr(s->s, '/'))) { 590 prefix = strtol(cs + 1, &ep, 10); 591 if (*ep == '.' 592 ? inet_pton(AF_INET, cs + 1, &addr4) != 1 593 : *ep || prefix < 0 || prefix > 32) { 594 jail_warnx(j, 595 "ip4.addr: bad netmask \"%s\"", cs); 596 error = -1; 597 } 598 *cs = '\0'; 599 s->len = cs - s->s + 1; 600 } 601 } 602 } 603#endif 604#ifdef INET6 605 if (j->intparams[KP_IP6_ADDR] != NULL) { 606 TAILQ_FOREACH(s, &j->intparams[KP_IP6_ADDR]->val, tq) { 607 cs = strchr(s->s, '|'); 608 if (cs || defif) 609 add_param(j, NULL, IP__IP6_IFADDR, s->s); 610 if (cs) { 611 strcpy(s->s, cs + 1); 612 s->len -= cs + 1 - s->s; 613 } 614 if ((cs = strchr(s->s, '/'))) { 615 prefix = strtol(cs + 1, &ep, 10); 616 if (*ep || prefix < 0 || prefix > 128) { 617 jail_warnx(j, 618 "ip6.addr: bad prefixlen \"%s\"", 619 cs); 620 error = -1; 621 } 622 *cs = '\0'; 623 s->len = cs - s->s + 1; 624 } 625 } 626 } 627#endif 628#endif 629 630 /* 631 * Read mount.fstab file(s), and treat each line as its own mount 632 * parameter. 633 */ 634 if (j->intparams[IP_MOUNT_FSTAB] != NULL) { 635 TAILQ_FOREACH(s, &j->intparams[IP_MOUNT_FSTAB]->val, tq) { 636 if (s->len == 0) 637 continue; 638 f = fopen(s->s, "r"); 639 if (f == NULL) { 640 jail_warnx(j, "mount.fstab: %s: %s", 641 s->s, strerror(errno)); 642 error = -1; 643 continue; 644 } 645 while ((ln = fgetln(f, &lnlen))) { 646 if ((cs = memchr(ln, '#', lnlen - 1))) 647 lnlen = cs - ln + 1; 648 if (ln[lnlen - 1] == '\n' || 649 ln[lnlen - 1] == '#') 650 ln[lnlen - 1] = '\0'; 651 else { 652 cs = alloca(lnlen + 1); 653 strlcpy(cs, ln, lnlen + 1); 654 ln = cs; 655 } 656 add_param(j, NULL, IP__MOUNT_FROM_FSTAB, ln); 657 } 658 fclose(f); 659 } 660 } 661 if (error) 662 failed(j); 663 return error; 664} 665 666/* 667 * Import parameters into libjail's binary jailparam format. 668 */ 669int 670import_params(struct cfjail *j) 671{ 672 struct cfparam *p; 673 struct cfstring *s, *ts; 674 struct jailparam *jp; 675 char *value, *cs; 676 size_t vallen; 677 int error; 678 679 error = 0; 680 j->njp = 0; 681 TAILQ_FOREACH(p, &j->params, tq) 682 if (!(p->flags & PF_INTERNAL)) 683 j->njp++; 684 j->jp = jp = emalloc(j->njp * sizeof(struct jailparam)); 685 TAILQ_FOREACH(p, &j->params, tq) { 686 if (p->flags & PF_INTERNAL) 687 continue; 688 if (jailparam_init(jp, p->name) < 0) { 689 error = -1; 690 jail_warnx(j, "%s", jail_errmsg); 691 continue; 692 } 693 if (TAILQ_EMPTY(&p->val)) 694 value = NULL; 695 else if (!jp->jp_elemlen || 696 !TAILQ_NEXT(TAILQ_FIRST(&p->val), tq)) { 697 /* 698 * Scalar parameters silently discard multiple (array) 699 * values, keeping only the last value added. This 700 * lets values added from the command line append to 701 * arrays wthout pre-checking the type. 702 */ 703 value = TAILQ_LAST(&p->val, cfstrings)->s; 704 } else { 705 /* 706 * Convert arrays into comma-separated strings, which 707 * jailparam_import will then convert back into arrays. 708 */ 709 vallen = 0; 710 TAILQ_FOREACH(s, &p->val, tq) 711 vallen += s->len + 1; 712 value = alloca(vallen); 713 cs = value; 714 TAILQ_FOREACH_SAFE(s, &p->val, tq, ts) { 715 strcpy(cs, s->s); 716 if (ts != NULL) { 717 cs += s->len + 1; 718 cs[-1] = ','; 719 } 720 } 721 } 722 if (jailparam_import(jp, value) < 0) { 723 error = -1; 724 jail_warnx(j, "%s", jail_errmsg); 725 } 726 jp++; 727 } 728 if (error) { 729 jailparam_free(j->jp, j->njp); 730 free(j->jp); 731 j->jp = NULL; 732 failed(j); 733 } 734 return error; 735} 736 737/* 738 * Check if options are equal (with or without the "no" prefix). 739 */ 740int 741equalopts(const char *opt1, const char *opt2) 742{ 743 char *p; 744 745 /* "opt" vs. "opt" or "noopt" vs. "noopt" */ 746 if (strcmp(opt1, opt2) == 0) 747 return (1); 748 /* "noopt" vs. "opt" */ 749 if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0) 750 return (1); 751 /* "opt" vs. "noopt" */ 752 if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0) 753 return (1); 754 while ((p = strchr(opt1, '.')) != NULL && 755 !strncmp(opt1, opt2, ++p - opt1)) { 756 opt2 += p - opt1; 757 opt1 = p; 758 /* "foo.noopt" vs. "foo.opt" */ 759 if (strncmp(opt1, "no", 2) == 0 && strcmp(opt1 + 2, opt2) == 0) 760 return (1); 761 /* "foo.opt" vs. "foo.noopt" */ 762 if (strncmp(opt2, "no", 2) == 0 && strcmp(opt1, opt2 + 2) == 0) 763 return (1); 764 } 765 return (0); 766} 767 768/* 769 * See if a jail name matches a wildcard. 770 */ 771int 772wild_jail_match(const char *jname, const char *wname) 773{ 774 const char *jc, *jd, *wc, *wd; 775 776 /* 777 * A non-final "*" component in the wild name matches a single jail 778 * component, and a final "*" matches one or more jail components. 779 */ 780 for (jc = jname, wc = wname; 781 (jd = strchr(jc, '.')) && (wd = strchr(wc, '.')); 782 jc = jd + 1, wc = wd + 1) 783 if (strncmp(jc, wc, jd - jc + 1) && strncmp(wc, "*.", 2)) 784 return 0; 785 return (!strcmp(jc, wc) || !strcmp(wc, "*")); 786} 787 788/* 789 * Return if a jail name is a wildcard. 790 */ 791int 792wild_jail_name(const char *wname) 793{ 794 const char *wc; 795 796 for (wc = strchr(wname, '*'); wc; wc = strchr(wc + 1, '*')) 797 if ((wc == wname || wc[-1] == '.') && 798 (wc[1] == '\0' || wc[1] == '.')) 799 return 1; 800 return 0; 801} 802 803/* 804 * Free a parameter record and all its strings and variables. 805 */ 806static void 807free_param(struct cfparams *pp, struct cfparam *p) 808{ 809 free(p->name); 810 free_param_strings(p); 811 TAILQ_REMOVE(pp, p, tq); 812 free(p); 813} 814 815static void 816free_param_strings(struct cfparam *p) 817{ 818 struct cfstring *s; 819 struct cfvar *v; 820 821 while ((s = TAILQ_FIRST(&p->val))) { 822 free(s->s); 823 while ((v = STAILQ_FIRST(&s->vars))) { 824 free(v->name); 825 STAILQ_REMOVE_HEAD(&s->vars, tq); 826 free(v); 827 } 828 TAILQ_REMOVE(&p->val, s, tq); 829 free(s); 830 } 831}
|