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