1 /* $OpenBSD: parse.y,v 1.7 2022/03/21 04:35:41 dlg Exp $ */ 2 3 /* 4 * Copyright (c) 2018 Florian Obser <florian@openbsd.org> 5 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org> 6 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org> 7 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org> 8 * Copyright (c) 2001 Markus Friedl. All rights reserved. 9 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved. 10 * Copyright (c) 2001 Theo de Raadt. All rights reserved. 11 * 12 * Permission to use, copy, modify, and distribute this software for any 13 * purpose with or without fee is hereby granted, provided that the above 14 * copyright notice and this permission notice appear in all copies. 15 * 16 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 17 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 18 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 19 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 20 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 21 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 22 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 23 */ 24 25 %{ 26 #include <sys/types.h> 27 #include <sys/queue.h> 28 #include <sys/socket.h> 29 #include <sys/stat.h> 30 31 #include <net/if.h> 32 33 #include <netinet/in.h> 34 #include <netinet/if_ether.h> 35 36 #include <arpa/inet.h> 37 38 #include <ctype.h> 39 #include <err.h> 40 #include <errno.h> 41 #include <event.h> 42 #include <imsg.h> 43 #include <limits.h> 44 #include <stdarg.h> 45 #include <stdio.h> 46 #include <string.h> 47 #include <syslog.h> 48 #include <unistd.h> 49 #include <vis.h> 50 51 #include "log.h" 52 #include "dhcpleased.h" 53 #include "frontend.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 size_t ungetpos; 61 size_t ungetsize; 62 u_char *ungetbuf; 63 int eof_reached; 64 int lineno; 65 int errors; 66 } *file, *topfile; 67 struct file *pushfile(const char *, int); 68 int popfile(void); 69 int check_file_secrecy(int, const char *); 70 int yyparse(void); 71 int yylex(void); 72 int yyerror(const char *, ...) 73 __attribute__((__format__ (printf, 1, 2))) 74 __attribute__((__nonnull__ (1))); 75 int kw_cmp(const void *, const void *); 76 int lookup(char *); 77 int igetc(void); 78 int lgetc(int); 79 void lungetc(int); 80 int findeol(void); 81 82 TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); 83 struct sym { 84 TAILQ_ENTRY(sym) entry; 85 int used; 86 int persist; 87 char *nam; 88 char *val; 89 }; 90 91 int symset(const char *, const char *, int); 92 char *symget(const char *); 93 94 static struct dhcpleased_conf *conf; 95 static int errors; 96 97 static struct iface_conf *iface_conf; 98 99 struct iface_conf *conf_get_iface(char *); 100 101 typedef struct { 102 union { 103 int64_t number; 104 char *string; 105 } v; 106 int lineno; 107 } YYSTYPE; 108 109 %} 110 111 %token DHCP_IFACE ERROR SEND VENDOR CLASS ID CLIENT IGNORE DNS ROUTES HOST NAME 112 %token NO 113 114 %token <v.string> STRING 115 %token <v.number> NUMBER 116 %type <v.string> string 117 118 %% 119 120 grammar : /* empty */ 121 | grammar '\n' 122 | grammar varset '\n' 123 | grammar dhcp_iface '\n' 124 | grammar error '\n' { file->errors++; } 125 ; 126 127 string : string STRING { 128 if (asprintf(&$$, "%s %s", $1, $2) == -1) { 129 free($1); 130 free($2); 131 yyerror("string: asprintf"); 132 YYERROR; 133 } 134 free($1); 135 free($2); 136 } 137 | STRING 138 ; 139 140 varset : STRING '=' string { 141 char *s = $1; 142 if (log_getverbose() == 1) 143 printf("%s = \"%s\"\n", $1, $3); 144 while (*s++) { 145 if (isspace((unsigned char)*s)) { 146 yyerror("macro name cannot contain " 147 "whitespace"); 148 free($1); 149 free($3); 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 optnl : '\n' optnl /* zero or more newlines */ 161 | /*empty*/ 162 ; 163 164 nl : '\n' optnl /* one or more newlines */ 165 ; 166 167 dhcp_iface : DHCP_IFACE STRING { 168 iface_conf = conf_get_iface($2); 169 } '{' iface_block '}' { 170 iface_conf = NULL; 171 } 172 ; 173 174 iface_block : optnl ifaceopts_l 175 | optnl 176 ; 177 178 ifaceopts_l : ifaceopts_l ifaceoptsl nl 179 | ifaceoptsl optnl 180 ; 181 182 ifaceoptsl : SEND VENDOR CLASS ID STRING { 183 ssize_t len; 184 char buf[256]; 185 186 if (iface_conf->vc_id != NULL) { 187 yyerror("vendor class id already set"); 188 YYERROR; 189 } 190 191 len = strnunvis(buf, $5, sizeof(buf)); 192 free($5); 193 194 if (len == -1) { 195 yyerror("invalid vendor class id"); 196 YYERROR; 197 } 198 if ((size_t)len >= sizeof(buf)) { 199 yyerror("vendor class id too long"); 200 YYERROR; 201 } 202 203 iface_conf->vc_id_len = 2 + strlen(buf); 204 iface_conf->vc_id = malloc(iface_conf->vc_id_len); 205 if (iface_conf->vc_id == NULL) { 206 yyerror("malloc"); 207 YYERROR; 208 } 209 iface_conf->vc_id[0] = DHO_DHCP_CLASS_IDENTIFIER; 210 iface_conf->vc_id[1] = iface_conf->vc_id_len - 2; 211 memcpy(&iface_conf->vc_id[2], buf, 212 iface_conf->vc_id_len - 2); 213 } 214 | SEND CLIENT ID STRING { 215 size_t i; 216 ssize_t len; 217 int not_hex = 0, val; 218 char buf[256], *hex, *p, excess; 219 220 if (iface_conf->c_id != NULL) { 221 yyerror("client-id already set"); 222 YYERROR; 223 } 224 225 /* parse as hex string including the type byte */ 226 if ((hex = strdup($4)) == NULL) { 227 free($4); 228 yyerror("malloc"); 229 YYERROR; 230 } 231 for (i = 0; (p = strsep(&hex, ":")) != NULL && i < 232 sizeof(buf); ) { 233 if (sscanf(p, "%x%c", &val, &excess) != 1 || 234 val < 0 || val > 0xff) { 235 not_hex = 1; 236 break; 237 } 238 buf[i++] = (val & 0xff); 239 } 240 if (p != NULL && i == sizeof(buf)) 241 not_hex = 1; 242 free(hex); 243 244 if (not_hex) { 245 len = strnunvis(buf, $4, sizeof(buf)); 246 free($4); 247 248 if (len == -1) { 249 yyerror("invalid client-id"); 250 YYERROR; 251 } 252 if ((size_t)len >= sizeof(buf)) { 253 yyerror("client-id too long"); 254 YYERROR; 255 } 256 iface_conf->c_id_len = 2 + len; 257 iface_conf->c_id = malloc(iface_conf->c_id_len); 258 if (iface_conf->c_id == NULL) { 259 yyerror("malloc"); 260 YYERROR; 261 } 262 memcpy(&iface_conf->c_id[2], buf, 263 iface_conf->c_id_len - 2); 264 } else { 265 free($4); 266 iface_conf->c_id_len = 2 + i; 267 iface_conf->c_id = malloc(iface_conf->c_id_len); 268 if (iface_conf->c_id == NULL) { 269 yyerror("malloc"); 270 YYERROR; 271 } 272 memcpy(&iface_conf->c_id[2], buf, 273 iface_conf->c_id_len - 2); 274 } 275 iface_conf->c_id[0] = DHO_DHCP_CLIENT_IDENTIFIER; 276 iface_conf->c_id[1] = iface_conf->c_id_len - 2; 277 } 278 | SEND HOST NAME STRING { 279 if (iface_conf->h_name != NULL) { 280 free($4); 281 yyerror("host name already set"); 282 YYERROR; 283 } 284 if (strlen($4) > 255) { 285 free($4); 286 yyerror("host name too long"); 287 YYERROR; 288 } 289 iface_conf->h_name = $4; 290 } 291 | SEND NO HOST NAME { 292 if (iface_conf->h_name != NULL) { 293 yyerror("host name already set"); 294 YYERROR; 295 } 296 297 if ((iface_conf->h_name = strdup("")) == NULL) { 298 yyerror("malloc"); 299 YYERROR; 300 } 301 } 302 | IGNORE ROUTES { 303 iface_conf->ignore |= IGN_ROUTES; 304 } 305 | IGNORE DNS { 306 iface_conf->ignore |= IGN_DNS; 307 } 308 | IGNORE STRING { 309 int res; 310 311 if (iface_conf->ignore_servers_len >= MAX_SERVERS) { 312 yyerror("too many servers to ignore"); 313 free($2); 314 YYERROR; 315 } 316 res = inet_pton(AF_INET, $2, 317 &iface_conf->ignore_servers[ 318 iface_conf->ignore_servers_len++]); 319 320 if (res != 1) { 321 yyerror("Invalid server IP %s", $2); 322 free($2); 323 YYERROR; 324 } 325 free($2); 326 } 327 ; 328 %% 329 330 struct keywords { 331 const char *k_name; 332 int k_val; 333 }; 334 335 int 336 yyerror(const char *fmt, ...) 337 { 338 va_list ap; 339 char *msg; 340 341 file->errors++; 342 va_start(ap, fmt); 343 if (vasprintf(&msg, fmt, ap) == -1) 344 fatalx("yyerror vasprintf"); 345 va_end(ap); 346 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); 347 free(msg); 348 return (0); 349 } 350 351 int 352 kw_cmp(const void *k, const void *e) 353 { 354 return (strcmp(k, ((const struct keywords *)e)->k_name)); 355 } 356 357 int 358 lookup(char *s) 359 { 360 /* This has to be sorted always. */ 361 static const struct keywords keywords[] = { 362 {"class", CLASS}, 363 {"client", CLIENT}, 364 {"dns", DNS}, 365 {"host", HOST}, 366 {"id", ID}, 367 {"ignore", IGNORE}, 368 {"interface", DHCP_IFACE}, 369 {"name", NAME}, 370 {"no", NO}, 371 {"routes", ROUTES}, 372 {"send", SEND}, 373 {"vendor", VENDOR}, 374 }; 375 const struct keywords *p; 376 377 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), 378 sizeof(keywords[0]), kw_cmp); 379 380 if (p) 381 return (p->k_val); 382 else 383 return (STRING); 384 } 385 386 #define START_EXPAND 1 387 #define DONE_EXPAND 2 388 389 static int expanding; 390 391 int 392 igetc(void) 393 { 394 int c; 395 396 while (1) { 397 if (file->ungetpos > 0) 398 c = file->ungetbuf[--file->ungetpos]; 399 else 400 c = getc(file->stream); 401 402 if (c == START_EXPAND) 403 expanding = 1; 404 else if (c == DONE_EXPAND) 405 expanding = 0; 406 else 407 break; 408 } 409 return (c); 410 } 411 412 int 413 lgetc(int quotec) 414 { 415 int c, next; 416 417 if (quotec) { 418 if ((c = igetc()) == EOF) { 419 yyerror("reached end of file while parsing " 420 "quoted string"); 421 if (file == topfile || popfile() == EOF) 422 return (EOF); 423 return (quotec); 424 } 425 return (c); 426 } 427 428 while ((c = igetc()) == '\\') { 429 next = igetc(); 430 if (next != '\n') { 431 c = next; 432 break; 433 } 434 yylval.lineno = file->lineno; 435 file->lineno++; 436 } 437 438 if (c == EOF) { 439 /* 440 * Fake EOL when hit EOF for the first time. This gets line 441 * count right if last line in included file is syntactically 442 * invalid and has no newline. 443 */ 444 if (file->eof_reached == 0) { 445 file->eof_reached = 1; 446 return ('\n'); 447 } 448 while (c == EOF) { 449 if (file == topfile || popfile() == EOF) 450 return (EOF); 451 c = igetc(); 452 } 453 } 454 return (c); 455 } 456 457 void 458 lungetc(int c) 459 { 460 if (c == EOF) 461 return; 462 463 if (file->ungetpos >= file->ungetsize) { 464 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); 465 if (p == NULL) 466 err(1, "lungetc"); 467 file->ungetbuf = p; 468 file->ungetsize *= 2; 469 } 470 file->ungetbuf[file->ungetpos++] = c; 471 } 472 473 int 474 findeol(void) 475 { 476 int c; 477 478 /* Skip to either EOF or the first real EOL. */ 479 while (1) { 480 c = lgetc(0); 481 if (c == '\n') { 482 file->lineno++; 483 break; 484 } 485 if (c == EOF) 486 break; 487 } 488 return (ERROR); 489 } 490 491 int 492 yylex(void) 493 { 494 char buf[8096]; 495 char *p, *val; 496 int quotec, next, c; 497 int token; 498 499 top: 500 p = buf; 501 while ((c = lgetc(0)) == ' ' || c == '\t') 502 ; /* nothing */ 503 504 yylval.lineno = file->lineno; 505 if (c == '#') 506 while ((c = lgetc(0)) != '\n' && c != EOF) 507 ; /* nothing */ 508 if (c == '$' && !expanding) { 509 while (1) { 510 if ((c = lgetc(0)) == EOF) 511 return (0); 512 513 if (p + 1 >= buf + sizeof(buf) - 1) { 514 yyerror("string too long"); 515 return (findeol()); 516 } 517 if (isalnum(c) || c == '_') { 518 *p++ = c; 519 continue; 520 } 521 *p = '\0'; 522 lungetc(c); 523 break; 524 } 525 val = symget(buf); 526 if (val == NULL) { 527 yyerror("macro '%s' not defined", buf); 528 return (findeol()); 529 } 530 p = val + strlen(val) - 1; 531 lungetc(DONE_EXPAND); 532 while (p >= val) { 533 lungetc((unsigned char)*p); 534 p--; 535 } 536 lungetc(START_EXPAND); 537 goto top; 538 } 539 540 switch (c) { 541 case '\'': 542 case '"': 543 quotec = c; 544 while (1) { 545 if ((c = lgetc(quotec)) == EOF) 546 return (0); 547 if (c == '\n') { 548 file->lineno++; 549 continue; 550 } else if (c == '\\') { 551 if ((next = lgetc(quotec)) == EOF) 552 return (0); 553 if (next == quotec || next == ' ' || 554 next == '\t') 555 c = next; 556 else if (next == '\n') { 557 file->lineno++; 558 continue; 559 } else 560 lungetc(next); 561 } else if (c == quotec) { 562 *p = '\0'; 563 break; 564 } else if (c == '\0') { 565 yyerror("syntax error"); 566 return (findeol()); 567 } 568 if (p + 1 >= buf + sizeof(buf) - 1) { 569 yyerror("string too long"); 570 return (findeol()); 571 } 572 *p++ = c; 573 } 574 yylval.v.string = strdup(buf); 575 if (yylval.v.string == NULL) 576 err(1, "yylex: strdup"); 577 return (STRING); 578 } 579 580 #define allowed_to_end_number(x) \ 581 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') 582 583 if (c == '-' || isdigit(c)) { 584 do { 585 *p++ = c; 586 if ((size_t)(p-buf) >= sizeof(buf)) { 587 yyerror("string too long"); 588 return (findeol()); 589 } 590 } while ((c = lgetc(0)) != EOF && isdigit(c)); 591 lungetc(c); 592 if (p == buf + 1 && buf[0] == '-') 593 goto nodigits; 594 if (c == EOF || allowed_to_end_number(c)) { 595 const char *errstr = NULL; 596 597 *p = '\0'; 598 yylval.v.number = strtonum(buf, LLONG_MIN, 599 LLONG_MAX, &errstr); 600 if (errstr) { 601 yyerror("\"%s\" invalid number: %s", 602 buf, errstr); 603 return (findeol()); 604 } 605 return (NUMBER); 606 } else { 607 nodigits: 608 while (p > buf + 1) 609 lungetc((unsigned char)*--p); 610 c = (unsigned char)*--p; 611 if (c == '-') 612 return (c); 613 } 614 } 615 616 #define allowed_in_string(x) \ 617 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ 618 x != '{' && x != '}' && \ 619 x != '!' && x != '=' && x != '#' && \ 620 x != ',')) 621 622 if (isalnum(c) || c == ':' || c == '_') { 623 do { 624 *p++ = c; 625 if ((size_t)(p-buf) >= sizeof(buf)) { 626 yyerror("string too long"); 627 return (findeol()); 628 } 629 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); 630 lungetc(c); 631 *p = '\0'; 632 if ((token = lookup(buf)) == STRING) 633 if ((yylval.v.string = strdup(buf)) == NULL) 634 err(1, "yylex: strdup"); 635 return (token); 636 } 637 if (c == '\n') { 638 yylval.lineno = file->lineno; 639 file->lineno++; 640 } 641 if (c == EOF) 642 return (0); 643 return (c); 644 } 645 646 int 647 check_file_secrecy(int fd, const char *fname) 648 { 649 struct stat st; 650 651 if (fstat(fd, &st)) { 652 log_warn("cannot stat %s", fname); 653 return (-1); 654 } 655 if (st.st_uid != 0 && st.st_uid != getuid()) { 656 log_warnx("%s: owner not root or current user", fname); 657 return (-1); 658 } 659 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { 660 log_warnx("%s: group writable or world read/writable", fname); 661 return (-1); 662 } 663 return (0); 664 } 665 666 struct file * 667 pushfile(const char *name, int secret) 668 { 669 struct file *nfile; 670 671 if ((nfile = calloc(1, sizeof(struct file))) == NULL) { 672 log_warn("calloc"); 673 return (NULL); 674 } 675 if ((nfile->name = strdup(name)) == NULL) { 676 log_warn("strdup"); 677 free(nfile); 678 return (NULL); 679 } 680 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { 681 free(nfile->name); 682 free(nfile); 683 return (NULL); 684 } else if (secret && 685 check_file_secrecy(fileno(nfile->stream), nfile->name)) { 686 fclose(nfile->stream); 687 free(nfile->name); 688 free(nfile); 689 return (NULL); 690 } 691 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; 692 nfile->ungetsize = 16; 693 nfile->ungetbuf = malloc(nfile->ungetsize); 694 if (nfile->ungetbuf == NULL) { 695 log_warn("malloc"); 696 fclose(nfile->stream); 697 free(nfile->name); 698 free(nfile); 699 return (NULL); 700 } 701 TAILQ_INSERT_TAIL(&files, nfile, entry); 702 return (nfile); 703 } 704 705 int 706 popfile(void) 707 { 708 struct file *prev; 709 710 if ((prev = TAILQ_PREV(file, files, entry)) != NULL) 711 prev->errors += file->errors; 712 713 TAILQ_REMOVE(&files, file, entry); 714 fclose(file->stream); 715 free(file->name); 716 free(file->ungetbuf); 717 free(file); 718 file = prev; 719 return (file ? 0 : EOF); 720 } 721 722 struct dhcpleased_conf * 723 parse_config(const char *filename) 724 { 725 extern const char default_conffile[]; 726 struct sym *sym, *next; 727 728 conf = config_new_empty(); 729 730 file = pushfile(filename, 0); 731 if (file == NULL) { 732 /* no default config file is fine */ 733 if (errno == ENOENT && filename == default_conffile) 734 return (conf); 735 log_warn("%s", filename); 736 free(conf); 737 return (NULL); 738 } 739 topfile = file; 740 741 yyparse(); 742 errors = file->errors; 743 popfile(); 744 745 /* Free macros and check which have not been used. */ 746 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { 747 if ((log_getverbose() == 2) && !sym->used) 748 fprintf(stderr, "warning: macro '%s' not used\n", 749 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 config_clear(conf); 760 return (NULL); 761 } 762 763 return (conf); 764 } 765 766 int 767 symset(const char *nam, const char *val, int persist) 768 { 769 struct sym *sym; 770 771 TAILQ_FOREACH(sym, &symhead, entry) { 772 if (strcmp(nam, sym->nam) == 0) 773 break; 774 } 775 776 if (sym != NULL) { 777 if (sym->persist == 1) 778 return (0); 779 else { 780 free(sym->nam); 781 free(sym->val); 782 TAILQ_REMOVE(&symhead, sym, entry); 783 free(sym); 784 } 785 } 786 if ((sym = calloc(1, sizeof(*sym))) == NULL) 787 return (-1); 788 789 sym->nam = strdup(nam); 790 if (sym->nam == NULL) { 791 free(sym); 792 return (-1); 793 } 794 sym->val = strdup(val); 795 if (sym->val == NULL) { 796 free(sym->nam); 797 free(sym); 798 return (-1); 799 } 800 sym->used = 0; 801 sym->persist = persist; 802 TAILQ_INSERT_TAIL(&symhead, sym, entry); 803 return (0); 804 } 805 806 int 807 cmdline_symset(char *s) 808 { 809 char *sym, *val; 810 int ret; 811 812 if ((val = strrchr(s, '=')) == NULL) 813 return (-1); 814 sym = strndup(s, val - s); 815 if (sym == NULL) 816 errx(1, "%s: strndup", __func__); 817 ret = symset(sym, val + 1, 1); 818 free(sym); 819 820 return (ret); 821 } 822 823 char * 824 symget(const char *nam) 825 { 826 struct sym *sym; 827 828 TAILQ_FOREACH(sym, &symhead, entry) { 829 if (strcmp(nam, sym->nam) == 0) { 830 sym->used = 1; 831 return (sym->val); 832 } 833 } 834 return (NULL); 835 } 836 837 struct iface_conf * 838 conf_get_iface(char *name) 839 { 840 struct iface_conf *iface; 841 size_t n; 842 843 SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) { 844 if (strcmp(name, iface->name) == 0) 845 return (iface); 846 } 847 848 iface = calloc(1, sizeof(*iface)); 849 if (iface == NULL) 850 errx(1, "%s: calloc", __func__); 851 n = strlcpy(iface->name, name, sizeof(iface->name)); 852 if (n >= sizeof(iface->name)) 853 errx(1, "%s: name too long", __func__); 854 855 856 SIMPLEQ_INSERT_TAIL(&conf->iface_list, iface, entry); 857 858 return (iface); 859 } 860