parse.y revision 290931
1/* $OpenBSD: parse.y,v 1.18 2015/01/16 06:40:22 deraadt Exp $ */ 2/* $FreeBSD: head/usr.sbin/ypldap/parse.y 290931 2015-11-16 16:48:43Z rodrigc $ */ 3 4/* 5 * Copyright (c) 2008 Pierre-Yves Ritschard <pyr@openbsd.org> 6 * Copyright (c) 2007, 2008 Reyk Floeter <reyk@openbsd.org> 7 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> 8 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org> 9 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> 10 * Copyright (c) 2001 Markus Friedl. All rights reserved. 11 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. 12 * Copyright (c) 2001 Theo de Raadt. All rights reserved. 13 * 14 * Permission to use, copy, modify, and distribute this software for any 15 * purpose with or without fee is hereby granted, provided that the above 16 * copyright notice and this permission notice appear in all copies. 17 * 18 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 19 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 20 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 21 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 22 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 23 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 24 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 25 */ 26 27%{ 28#include <sys/types.h> 29#include <sys/time.h> 30#include <sys/queue.h> 31#include <sys/tree.h> 32#include <sys/socket.h> 33#include <sys/stat.h> 34 35#include <netinet/in.h> 36#include <arpa/inet.h> 37 38#include <ctype.h> 39#include <err.h> 40#include <errno.h> 41#include <event.h> 42#include <fcntl.h> 43#include <limits.h> 44#include <netdb.h> 45#include <pwd.h> 46#include <stdarg.h> 47#include <stdio.h> 48#include <stdlib.h> 49#include <string.h> 50#include <syslog.h> 51#include <unistd.h> 52 53#include "ypldap.h" 54 55TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); 56static struct file { 57 TAILQ_ENTRY(file) entry; 58 FILE *stream; 59 char *name; 60 int lineno; 61 int errors; 62} *file, *topfile; 63struct file *pushfile(const char *, int); 64int popfile(void); 65int check_file_secrecy(int, const char *); 66int yyparse(void); 67int yylex(void); 68int yyerror(const char *, ...) 69 __attribute__((__format__ (printf, 1, 2))) 70 __attribute__((__nonnull__ (1))); 71int kw_cmp(const void *, const void *); 72int lookup(char *); 73int lgetc(int); 74int lungetc(int); 75int findeol(void); 76 77TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); 78struct sym { 79 TAILQ_ENTRY(sym) entry; 80 int used; 81 int persist; 82 char *nam; 83 char *val; 84}; 85int symset(const char *, const char *, int); 86char *symget(const char *); 87 88struct env *conf = NULL; 89struct idm *idm = NULL; 90static int errors = 0; 91 92typedef struct { 93 union { 94 int64_t number; 95 char *string; 96 } v; 97 int lineno; 98} YYSTYPE; 99 100%} 101 102%token SERVER FILTER ATTRIBUTE BASEDN BINDDN GROUPDN BINDCRED MAPS CHANGE DOMAIN PROVIDE 103%token USER GROUP TO EXPIRE HOME SHELL GECOS UID GID INTERVAL 104%token PASSWD NAME FIXED LIST GROUPNAME GROUPPASSWD GROUPGID MAP 105%token INCLUDE DIRECTORY CLASS PORT ERROR GROUPMEMBERS 106%token <v.string> STRING 107%token <v.number> NUMBER 108%type <v.number> opcode attribute 109%type <v.string> port 110 111%% 112 113grammar : /* empty */ 114 | grammar '\n' 115 | grammar include '\n' 116 | grammar varset '\n' 117 | grammar directory '\n' 118 | grammar main '\n' 119 | grammar error '\n' { file->errors++; } 120 ; 121 122nl : '\n' optnl 123 ; 124 125optnl : '\n' optnl 126 | /* empty */ 127 ; 128 129 130include : INCLUDE STRING { 131 struct file *nfile; 132 133 if ((nfile = pushfile($2, 0)) == NULL) { 134 yyerror("failed to include file %s", $2); 135 free($2); 136 YYERROR; 137 } 138 free($2); 139 140 file = nfile; 141 lungetc('\n'); 142 } 143 ; 144 145varset : STRING '=' STRING { 146 if (symset($1, $3, 0) == -1) 147 fatal("cannot store variable"); 148 free($1); 149 free($3); 150 } 151 ; 152 153port : /* empty */ { $$ = NULL; } 154 | PORT STRING { $$ = $2; } 155 ; 156 157opcode : GROUP { $$ = 0; } 158 | PASSWD { $$ = 1; } 159 ; 160 161 162attribute : NAME { $$ = 0; } 163 | PASSWD { $$ = 1; } 164 | UID { $$ = 2; } 165 | GID { $$ = 3; } 166 | CLASS { $$ = 4; } 167 | CHANGE { $$ = 5; } 168 | EXPIRE { $$ = 6; } 169 | GECOS { $$ = 7; } 170 | HOME { $$ = 8; } 171 | SHELL { $$ = 9; } 172 | GROUPNAME { $$ = 10; } 173 | GROUPPASSWD { $$ = 11; } 174 | GROUPGID { $$ = 12; } 175 | GROUPMEMBERS { $$ = 13; } 176 ; 177 178diropt : BINDDN STRING { 179 idm->idm_flags |= F_NEEDAUTH; 180 if (strlcpy(idm->idm_binddn, $2, 181 sizeof(idm->idm_binddn)) >= 182 sizeof(idm->idm_binddn)) { 183 yyerror("directory binddn truncated"); 184 free($2); 185 YYERROR; 186 } 187 free($2); 188 } 189 | BINDCRED STRING { 190 idm->idm_flags |= F_NEEDAUTH; 191 if (strlcpy(idm->idm_bindcred, $2, 192 sizeof(idm->idm_bindcred)) >= 193 sizeof(idm->idm_bindcred)) { 194 yyerror("directory bindcred truncated"); 195 free($2); 196 YYERROR; 197 } 198 free($2); 199 } 200 | BASEDN STRING { 201 if (strlcpy(idm->idm_basedn, $2, 202 sizeof(idm->idm_basedn)) >= 203 sizeof(idm->idm_basedn)) { 204 yyerror("directory basedn truncated"); 205 free($2); 206 YYERROR; 207 } 208 free($2); 209 } 210 | GROUPDN STRING { 211 if(strlcpy(idm->idm_groupdn, $2, 212 sizeof(idm->idm_groupdn)) >= 213 sizeof(idm->idm_groupdn)) { 214 yyerror("directory groupdn truncated"); 215 free($2); 216 YYERROR; 217 } 218 free($2); 219 } 220 | opcode FILTER STRING { 221 if (strlcpy(idm->idm_filters[$1], $3, 222 sizeof(idm->idm_filters[$1])) >= 223 sizeof(idm->idm_filters[$1])) { 224 yyerror("filter truncated"); 225 free($3); 226 YYERROR; 227 } 228 free($3); 229 } 230 | ATTRIBUTE attribute MAPS TO STRING { 231 if (strlcpy(idm->idm_attrs[$2], $5, 232 sizeof(idm->idm_attrs[$2])) >= 233 sizeof(idm->idm_attrs[$2])) { 234 yyerror("attribute truncated"); 235 free($5); 236 YYERROR; 237 } 238 free($5); 239 } 240 | FIXED ATTRIBUTE attribute STRING { 241 if (strlcpy(idm->idm_attrs[$3], $4, 242 sizeof(idm->idm_attrs[$3])) >= 243 sizeof(idm->idm_attrs[$3])) { 244 yyerror("attribute truncated"); 245 free($4); 246 YYERROR; 247 } 248 idm->idm_flags |= F_FIXED_ATTR($3); 249 free($4); 250 } 251 | LIST attribute MAPS TO STRING { 252 if (strlcpy(idm->idm_attrs[$2], $5, 253 sizeof(idm->idm_attrs[$2])) >= 254 sizeof(idm->idm_attrs[$2])) { 255 yyerror("attribute truncated"); 256 free($5); 257 YYERROR; 258 } 259 idm->idm_list |= F_LIST($2); 260 free($5); 261 } 262 ; 263 264directory : DIRECTORY STRING port { 265 if ((idm = calloc(1, sizeof(*idm))) == NULL) 266 fatal(NULL); 267 idm->idm_id = conf->sc_maxid++; 268 269 if (strlcpy(idm->idm_name, $2, 270 sizeof(idm->idm_name)) >= 271 sizeof(idm->idm_name)) { 272 yyerror("attribute truncated"); 273 free($2); 274 YYERROR; 275 } 276 277 free($2); 278 } '{' optnl diropts '}' { 279 TAILQ_INSERT_TAIL(&conf->sc_idms, idm, idm_entry); 280 idm = NULL; 281 } 282 ; 283 284main : INTERVAL NUMBER { 285 conf->sc_conf_tv.tv_sec = $2; 286 conf->sc_conf_tv.tv_usec = 0; 287 } 288 | DOMAIN STRING { 289 if (strlcpy(conf->sc_domainname, $2, 290 sizeof(conf->sc_domainname)) >= 291 sizeof(conf->sc_domainname)) { 292 yyerror("domainname truncated"); 293 free($2); 294 YYERROR; 295 } 296 free($2); 297 } 298 | PROVIDE MAP STRING { 299 if (strcmp($3, "passwd.byname") == 0) 300 conf->sc_flags |= YPMAP_PASSWD_BYNAME; 301 else if (strcmp($3, "passwd.byuid") == 0) 302 conf->sc_flags |= YPMAP_PASSWD_BYUID; 303 else if (strcmp($3, "master.passwd.byname") == 0) 304 conf->sc_flags |= YPMAP_MASTER_PASSWD_BYNAME; 305 else if (strcmp($3, "master.passwd.byuid") == 0) 306 conf->sc_flags |= YPMAP_MASTER_PASSWD_BYUID; 307 else if (strcmp($3, "group.byname") == 0) 308 conf->sc_flags |= YPMAP_GROUP_BYNAME; 309 else if (strcmp($3, "group.bygid") == 0) 310 conf->sc_flags |= YPMAP_GROUP_BYGID; 311 else if (strcmp($3, "netid.byname") == 0) 312 conf->sc_flags |= YPMAP_NETID_BYNAME; 313 else { 314 yyerror("unsupported map type: %s", $3); 315 free($3); 316 YYERROR; 317 } 318 free($3); 319 } 320 ; 321 322diropts : diropts diropt nl 323 | diropt optnl 324 ; 325 326%% 327 328struct keywords { 329 const char *k_name; 330 int k_val; 331}; 332 333int 334yyerror(const char *fmt, ...) 335{ 336 va_list ap; 337 char *msg; 338 339 file->errors++; 340 va_start(ap, fmt); 341 if (vasprintf(&msg, fmt, ap) == -1) 342 fatalx("yyerror vasprintf"); 343 va_end(ap); 344 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); 345 free(msg); 346 return (0); 347} 348 349int 350kw_cmp(const void *k, const void *e) 351{ 352 return (strcmp(k, ((const struct keywords *)e)->k_name)); 353} 354 355int 356lookup(char *s) 357{ 358 /* this has to be sorted always */ 359 static const struct keywords keywords[] = { 360 { "attribute", ATTRIBUTE }, 361 { "basedn", BASEDN }, 362 { "bindcred", BINDCRED }, 363 { "binddn", BINDDN }, 364 { "change", CHANGE }, 365 { "class", CLASS }, 366 { "directory", DIRECTORY }, 367 { "domain", DOMAIN }, 368 { "expire", EXPIRE }, 369 { "filter", FILTER }, 370 { "fixed", FIXED }, 371 { "gecos", GECOS }, 372 { "gid", GID }, 373 { "group", GROUP }, 374 { "groupdn", GROUPDN }, 375 { "groupgid", GROUPGID }, 376 { "groupmembers", GROUPMEMBERS }, 377 { "groupname", GROUPNAME }, 378 { "grouppasswd", GROUPPASSWD }, 379 { "home", HOME }, 380 { "include", INCLUDE }, 381 { "interval", INTERVAL }, 382 { "list", LIST }, 383 { "map", MAP }, 384 { "maps", MAPS }, 385 { "name", NAME }, 386 { "passwd", PASSWD }, 387 { "port", PORT }, 388 { "provide", PROVIDE }, 389 { "server", SERVER }, 390 { "shell", SHELL }, 391 { "to", TO }, 392 { "uid", UID }, 393 { "user", USER }, 394 }; 395 const struct keywords *p; 396 397 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), 398 sizeof(keywords[0]), kw_cmp); 399 400 if (p) 401 return (p->k_val); 402 else 403 return (STRING); 404} 405 406#define MAXPUSHBACK 128 407 408u_char *parsebuf; 409int parseindex; 410u_char pushback_buffer[MAXPUSHBACK]; 411int pushback_index = 0; 412 413int 414lgetc(int quotec) 415{ 416 int c, next; 417 418 if (parsebuf) { 419 /* Read character from the parsebuffer instead of input. */ 420 if (parseindex >= 0) { 421 c = parsebuf[parseindex++]; 422 if (c != '\0') 423 return (c); 424 parsebuf = NULL; 425 } else 426 parseindex++; 427 } 428 429 if (pushback_index) 430 return (pushback_buffer[--pushback_index]); 431 432 if (quotec) { 433 if ((c = getc(file->stream)) == EOF) { 434 yyerror("reached end of file while parsing " 435 "quoted string"); 436 if (file == topfile || popfile() == EOF) 437 return (EOF); 438 return (quotec); 439 } 440 return (c); 441 } 442 443 while ((c = getc(file->stream)) == '\\') { 444 next = getc(file->stream); 445 if (next != '\n') { 446 c = next; 447 break; 448 } 449 yylval.lineno = file->lineno; 450 file->lineno++; 451 } 452 453 while (c == EOF) { 454 if (file == topfile || popfile() == EOF) 455 return (EOF); 456 c = getc(file->stream); 457 } 458 return (c); 459} 460 461int 462lungetc(int c) 463{ 464 if (c == EOF) 465 return (EOF); 466 if (parsebuf) { 467 parseindex--; 468 if (parseindex >= 0) 469 return (c); 470 } 471 if (pushback_index < MAXPUSHBACK-1) 472 return (pushback_buffer[pushback_index++] = c); 473 else 474 return (EOF); 475} 476 477int 478findeol(void) 479{ 480 int c; 481 482 parsebuf = NULL; 483 484 /* skip to either EOF or the first real EOL */ 485 while (1) { 486 if (pushback_index) 487 c = pushback_buffer[--pushback_index]; 488 else 489 c = lgetc(0); 490 if (c == '\n') { 491 file->lineno++; 492 break; 493 } 494 if (c == EOF) 495 break; 496 } 497 return (ERROR); 498} 499 500int 501yylex(void) 502{ 503 u_char buf[8096]; 504 u_char *p, *val; 505 int quotec, next, c; 506 int token; 507 508top: 509 p = buf; 510 while ((c = lgetc(0)) == ' ' || c == '\t') 511 ; /* nothing */ 512 513 yylval.lineno = file->lineno; 514 if (c == '#') 515 while ((c = lgetc(0)) != '\n' && c != EOF) 516 ; /* nothing */ 517 if (c == '$' && parsebuf == NULL) { 518 while (1) { 519 if ((c = lgetc(0)) == EOF) 520 return (0); 521 522 if (p + 1 >= buf + sizeof(buf) - 1) { 523 yyerror("string too long"); 524 return (findeol()); 525 } 526 if (isalnum(c) || c == '_') { 527 *p++ = c; 528 continue; 529 } 530 *p = '\0'; 531 lungetc(c); 532 break; 533 } 534 val = symget(buf); 535 if (val == NULL) { 536 yyerror("macro '%s' not defined", buf); 537 return (findeol()); 538 } 539 parsebuf = val; 540 parseindex = 0; 541 goto top; 542 } 543 544 switch (c) { 545 case '\'': 546 case '"': 547 quotec = c; 548 while (1) { 549 if ((c = lgetc(quotec)) == EOF) 550 return (0); 551 if (c == '\n') { 552 file->lineno++; 553 continue; 554 } else if (c == '\\') { 555 if ((next = lgetc(quotec)) == EOF) 556 return (0); 557 if (next == quotec || c == ' ' || c == '\t') 558 c = next; 559 else if (next == '\n') { 560 file->lineno++; 561 continue; 562 } else 563 lungetc(next); 564 } else if (c == quotec) { 565 *p = '\0'; 566 break; 567 } else if (c == '\0') { 568 yyerror("syntax error"); 569 return (findeol()); 570 } 571 if (p + 1 >= buf + sizeof(buf) - 1) { 572 yyerror("string too long"); 573 return (findeol()); 574 } 575 *p++ = c; 576 } 577 yylval.v.string = strdup(buf); 578 if (yylval.v.string == NULL) 579 err(1, "yylex: strdup"); 580 return (STRING); 581 } 582 583#define allowed_to_end_number(x) \ 584 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') 585 586 if (c == '-' || isdigit(c)) { 587 do { 588 *p++ = c; 589 if ((unsigned)(p-buf) >= sizeof(buf)) { 590 yyerror("string too long"); 591 return (findeol()); 592 } 593 } while ((c = lgetc(0)) != EOF && isdigit(c)); 594 lungetc(c); 595 if (p == buf + 1 && buf[0] == '-') 596 goto nodigits; 597 if (c == EOF || allowed_to_end_number(c)) { 598 const char *errstr = NULL; 599 600 *p = '\0'; 601 yylval.v.number = strtonum(buf, LLONG_MIN, 602 LLONG_MAX, &errstr); 603 if (errstr) { 604 yyerror("\"%s\" invalid number: %s", 605 buf, errstr); 606 return (findeol()); 607 } 608 return (NUMBER); 609 } else { 610nodigits: 611 while (p > buf + 1) 612 lungetc(*--p); 613 c = *--p; 614 if (c == '-') 615 return (c); 616 } 617 } 618 619#define allowed_in_string(x) \ 620 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ 621 x != '{' && x != '}' && x != '<' && x != '>' && \ 622 x != '!' && x != '=' && x != '#' && \ 623 x != ',')) 624 625 if (isalnum(c) || c == ':' || c == '_') { 626 do { 627 *p++ = c; 628 if ((unsigned)(p-buf) >= sizeof(buf)) { 629 yyerror("string too long"); 630 return (findeol()); 631 } 632 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); 633 lungetc(c); 634 *p = '\0'; 635 if ((token = lookup(buf)) == STRING) 636 if ((yylval.v.string = strdup(buf)) == NULL) 637 err(1, "yylex: strdup"); 638 return (token); 639 } 640 if (c == '\n') { 641 yylval.lineno = file->lineno; 642 file->lineno++; 643 } 644 if (c == EOF) 645 return (0); 646 return (c); 647} 648 649int 650check_file_secrecy(int fd, const char *fname) 651{ 652 struct stat st; 653 654 if (fstat(fd, &st)) { 655 log_warn("cannot stat %s", fname); 656 return (-1); 657 } 658 if (st.st_uid != 0 && st.st_uid != getuid()) { 659 log_warnx("%s: owner not root or current user", fname); 660 return (-1); 661 } 662 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { 663 log_warnx("%s: group writable or world read/writable", fname); 664 return (-1); 665 } 666 return (0); 667} 668 669struct file * 670pushfile(const char *name, int secret) 671{ 672 struct file *nfile; 673 674 if ((nfile = calloc(1, sizeof(struct file))) == NULL) { 675 log_warn("malloc"); 676 return (NULL); 677 } 678 if ((nfile->name = strdup(name)) == NULL) { 679 log_warn("malloc"); 680 free(nfile); 681 return (NULL); 682 } 683 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { 684 log_warn("%s", nfile->name); 685 free(nfile->name); 686 free(nfile); 687 return (NULL); 688 } else if (secret && 689 check_file_secrecy(fileno(nfile->stream), nfile->name)) { 690 fclose(nfile->stream); 691 free(nfile->name); 692 free(nfile); 693 return (NULL); 694 } 695 nfile->lineno = 1; 696 TAILQ_INSERT_TAIL(&files, nfile, entry); 697 return (nfile); 698} 699 700int 701popfile(void) 702{ 703 struct file *prev; 704 705 if ((prev = TAILQ_PREV(file, files, entry)) != NULL) 706 prev->errors += file->errors; 707 708 TAILQ_REMOVE(&files, file, entry); 709 fclose(file->stream); 710 free(file->name); 711 free(file); 712 file = prev; 713 return (file ? 0 : EOF); 714} 715 716int 717parse_config(struct env *x_conf, const char *filename, int opts) 718{ 719 struct sym *sym, *next; 720 721 conf = x_conf; 722 bzero(conf, sizeof(*conf)); 723 724 TAILQ_INIT(&conf->sc_idms); 725 conf->sc_conf_tv.tv_sec = DEFAULT_INTERVAL; 726 conf->sc_conf_tv.tv_usec = 0; 727 728 errors = 0; 729 730 if ((file = pushfile(filename, 1)) == NULL) { 731 return (-1); 732 } 733 topfile = file; 734 735 /* 736 * parse configuration 737 */ 738 setservent(1); 739 yyparse(); 740 endservent(); 741 errors = file->errors; 742 popfile(); 743 744 /* Free macros and check which have not been used. */ 745 for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { 746 next = TAILQ_NEXT(sym, entry); 747 if ((opts & YPLDAP_OPT_VERBOSE) && !sym->used) 748 fprintf(stderr, "warning: macro '%s' not " 749 "used\n", sym->nam); 750 if (!sym->persist) { 751 free(sym->nam); 752 free(sym->val); 753 TAILQ_REMOVE(&symhead, sym, entry); 754 free(sym); 755 } 756 } 757 758 if (errors) { 759 return (-1); 760 } 761 762 return (0); 763} 764 765int 766symset(const char *nam, const char *val, int persist) 767{ 768 struct sym *sym; 769 770 for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); 771 sym = TAILQ_NEXT(sym, entry)) 772 ; /* nothing */ 773 774 if (sym != NULL) { 775 if (sym->persist == 1) 776 return (0); 777 else { 778 free(sym->nam); 779 free(sym->val); 780 TAILQ_REMOVE(&symhead, sym, entry); 781 free(sym); 782 } 783 } 784 if ((sym = calloc(1, sizeof(*sym))) == NULL) 785 return (-1); 786 787 sym->nam = strdup(nam); 788 if (sym->nam == NULL) { 789 free(sym); 790 return (-1); 791 } 792 sym->val = strdup(val); 793 if (sym->val == NULL) { 794 free(sym->nam); 795 free(sym); 796 return (-1); 797 } 798 sym->used = 0; 799 sym->persist = persist; 800 TAILQ_INSERT_TAIL(&symhead, sym, entry); 801 return (0); 802} 803 804int 805cmdline_symset(char *s) 806{ 807 char *sym, *val; 808 int ret; 809 size_t len; 810 811 if ((val = strrchr(s, '=')) == NULL) 812 return (-1); 813 814 len = strlen(s) - strlen(val) + 1; 815 if ((sym = malloc(len)) == NULL) 816 errx(1, "cmdline_symset: malloc"); 817 818 (void)strlcpy(sym, s, len); 819 820 ret = symset(sym, val + 1, 1); 821 free(sym); 822 823 return (ret); 824} 825 826char * 827symget(const char *nam) 828{ 829 struct sym *sym; 830 831 TAILQ_FOREACH(sym, &symhead, entry) 832 if (strcmp(nam, sym->nam) == 0) { 833 sym->used = 1; 834 return (sym->val); 835 } 836 return (NULL); 837} 838