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