1 /* $OpenBSD: parse.y,v 1.17 2014/11/20 05:51:21 jsg 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/param.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 if (symset($1, $3, 0) == -1) 147 fatal("cannot store variable"); 148 free($1); 149 free($3); 150 } 151 ; 152 153 port : /* empty */ { $$ = NULL; } 154 | PORT STRING { $$ = $2; } 155 ; 156 157 opcode : GROUP { $$ = 0; } 158 | PASSWD { $$ = 1; } 159 ; 160 161 162 attribute : 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 178 diropt : 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 264 directory : 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 284 main : 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 322 diropts : diropts diropt nl 323 | diropt optnl 324 ; 325 326 %% 327 328 struct keywords { 329 const char *k_name; 330 int k_val; 331 }; 332 333 int 334 yyerror(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 349 int 350 kw_cmp(const void *k, const void *e) 351 { 352 return (strcmp(k, ((const struct keywords *)e)->k_name)); 353 } 354 355 int 356 lookup(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 408 u_char *parsebuf; 409 int parseindex; 410 u_char pushback_buffer[MAXPUSHBACK]; 411 int pushback_index = 0; 412 413 int 414 lgetc(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 461 int 462 lungetc(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 477 int 478 findeol(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 500 int 501 yylex(void) 502 { 503 u_char buf[8096]; 504 u_char *p, *val; 505 int quotec, next, c; 506 int token; 507 508 top: 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 { 610 nodigits: 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 649 int 650 check_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 669 struct file * 670 pushfile(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 700 int 701 popfile(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 716 int 717 parse_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 765 int 766 symset(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 804 int 805 cmdline_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 826 char * 827 symget(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