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