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