1 /* $OpenBSD: parse.y,v 1.4 2021/09/20 11:46:22 florian 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 112 113 %token <v.string> STRING 114 %token <v.number> NUMBER 115 %type <v.string> string 116 117 %% 118 119 grammar : /* empty */ 120 | grammar '\n' 121 | grammar varset '\n' 122 | grammar dhcp_iface '\n' 123 | grammar error '\n' { file->errors++; } 124 ; 125 126 string : string STRING { 127 if (asprintf(&$$, "%s %s", $1, $2) == -1) { 128 free($1); 129 free($2); 130 yyerror("string: asprintf"); 131 YYERROR; 132 } 133 free($1); 134 free($2); 135 } 136 | STRING 137 ; 138 139 varset : STRING '=' string { 140 char *s = $1; 141 if (log_getverbose() == 1) 142 printf("%s = \"%s\"\n", $1, $3); 143 while (*s++) { 144 if (isspace((unsigned char)*s)) { 145 yyerror("macro name cannot contain " 146 "whitespace"); 147 free($1); 148 free($3); 149 YYERROR; 150 } 151 } 152 if (symset($1, $3, 0) == -1) 153 fatal("cannot store variable"); 154 free($1); 155 free($3); 156 } 157 ; 158 159 optnl : '\n' optnl /* zero or more newlines */ 160 | /*empty*/ 161 ; 162 163 nl : '\n' optnl /* one or more newlines */ 164 ; 165 166 dhcp_iface : DHCP_IFACE STRING { 167 iface_conf = conf_get_iface($2); 168 } '{' iface_block '}' { 169 iface_conf = NULL; 170 } 171 ; 172 173 iface_block : optnl ifaceopts_l 174 | optnl 175 ; 176 177 ifaceopts_l : ifaceopts_l ifaceoptsl nl 178 | ifaceoptsl optnl 179 ; 180 181 ifaceoptsl : SEND VENDOR CLASS ID STRING { 182 ssize_t len; 183 char buf[256]; 184 185 if (iface_conf->vc_id != NULL) { 186 yyerror("vendor class id already set"); 187 YYERROR; 188 } 189 190 len = strnunvis(buf, $5, sizeof(buf)); 191 free($5); 192 193 if (len == -1) { 194 yyerror("invalid vendor class id"); 195 YYERROR; 196 } 197 if ((size_t)len >= sizeof(buf)) { 198 yyerror("vendor class id too long"); 199 YYERROR; 200 } 201 202 iface_conf->vc_id_len = 2 + strlen(buf); 203 iface_conf->vc_id = malloc(iface_conf->vc_id_len); 204 if (iface_conf->vc_id == NULL) { 205 yyerror("malloc"); 206 YYERROR; 207 } 208 iface_conf->vc_id[0] = DHO_DHCP_CLASS_IDENTIFIER; 209 iface_conf->vc_id[1] = iface_conf->vc_id_len - 2; 210 memcpy(&iface_conf->vc_id[2], buf, 211 iface_conf->vc_id_len - 2); 212 } 213 | SEND CLIENT ID STRING { 214 size_t i; 215 ssize_t len; 216 int not_hex = 0, val; 217 char buf[256], *hex, *p, excess; 218 219 if (iface_conf->c_id != NULL) { 220 yyerror("client-id already set"); 221 YYERROR; 222 } 223 224 /* parse as hex string including the type byte */ 225 if ((hex = strdup($4)) == NULL) { 226 free($4); 227 yyerror("malloc"); 228 YYERROR; 229 } 230 for (i = 0; (p = strsep(&hex, ":")) != NULL && i < 231 sizeof(buf); ) { 232 if (sscanf(p, "%x%c", &val, &excess) != 1 || 233 val < 0 || val > 0xff) { 234 not_hex = 1; 235 break; 236 } 237 buf[i++] = (val & 0xff); 238 } 239 if (p != NULL && i == sizeof(buf)) 240 not_hex = 1; 241 free(hex); 242 243 if (not_hex) { 244 len = strnunvis(buf, $4, sizeof(buf)); 245 free($4); 246 247 if (len == -1) { 248 yyerror("invalid client-id"); 249 YYERROR; 250 } 251 if ((size_t)len >= sizeof(buf)) { 252 yyerror("client-id too long"); 253 YYERROR; 254 } 255 iface_conf->c_id_len = 2 + len; 256 iface_conf->c_id = malloc(iface_conf->c_id_len); 257 if (iface_conf->c_id == NULL) { 258 yyerror("malloc"); 259 YYERROR; 260 } 261 memcpy(&iface_conf->c_id[2], buf, 262 iface_conf->c_id_len - 2); 263 } else { 264 free($4); 265 iface_conf->c_id_len = 2 + i; 266 iface_conf->c_id = malloc(iface_conf->c_id_len); 267 if (iface_conf->c_id == NULL) { 268 yyerror("malloc"); 269 YYERROR; 270 } 271 memcpy(&iface_conf->c_id[2], buf, 272 iface_conf->c_id_len - 2); 273 } 274 iface_conf->c_id[0] = DHO_DHCP_CLIENT_IDENTIFIER; 275 iface_conf->c_id[1] = iface_conf->c_id_len - 2; 276 } 277 | IGNORE ROUTES { 278 iface_conf->ignore |= IGN_ROUTES; 279 } 280 | IGNORE DNS { 281 iface_conf->ignore |= IGN_DNS; 282 } 283 | IGNORE STRING { 284 int res; 285 286 if (iface_conf->ignore_servers_len >= MAX_SERVERS) { 287 yyerror("too many servers to ignore"); 288 free($2); 289 YYERROR; 290 } 291 res = inet_pton(AF_INET, $2, 292 &iface_conf->ignore_servers[ 293 iface_conf->ignore_servers_len++]); 294 295 if (res != 1) { 296 yyerror("Invalid server IP %s", $2); 297 free($2); 298 YYERROR; 299 } 300 free($2); 301 } 302 ; 303 %% 304 305 struct keywords { 306 const char *k_name; 307 int k_val; 308 }; 309 310 int 311 yyerror(const char *fmt, ...) 312 { 313 va_list ap; 314 char *msg; 315 316 file->errors++; 317 va_start(ap, fmt); 318 if (vasprintf(&msg, fmt, ap) == -1) 319 fatalx("yyerror vasprintf"); 320 va_end(ap); 321 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); 322 free(msg); 323 return (0); 324 } 325 326 int 327 kw_cmp(const void *k, const void *e) 328 { 329 return (strcmp(k, ((const struct keywords *)e)->k_name)); 330 } 331 332 int 333 lookup(char *s) 334 { 335 /* This has to be sorted always. */ 336 static const struct keywords keywords[] = { 337 {"class", CLASS}, 338 {"client", CLIENT}, 339 {"dns", DNS}, 340 {"id", ID}, 341 {"ignore", IGNORE}, 342 {"interface", DHCP_IFACE}, 343 {"routes", ROUTES}, 344 {"send", SEND}, 345 {"vendor", VENDOR}, 346 }; 347 const struct keywords *p; 348 349 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), 350 sizeof(keywords[0]), kw_cmp); 351 352 if (p) 353 return (p->k_val); 354 else 355 return (STRING); 356 } 357 358 #define START_EXPAND 1 359 #define DONE_EXPAND 2 360 361 static int expanding; 362 363 int 364 igetc(void) 365 { 366 int c; 367 368 while (1) { 369 if (file->ungetpos > 0) 370 c = file->ungetbuf[--file->ungetpos]; 371 else 372 c = getc(file->stream); 373 374 if (c == START_EXPAND) 375 expanding = 1; 376 else if (c == DONE_EXPAND) 377 expanding = 0; 378 else 379 break; 380 } 381 return (c); 382 } 383 384 int 385 lgetc(int quotec) 386 { 387 int c, next; 388 389 if (quotec) { 390 if ((c = igetc()) == EOF) { 391 yyerror("reached end of file while parsing " 392 "quoted string"); 393 if (file == topfile || popfile() == EOF) 394 return (EOF); 395 return (quotec); 396 } 397 return (c); 398 } 399 400 while ((c = igetc()) == '\\') { 401 next = igetc(); 402 if (next != '\n') { 403 c = next; 404 break; 405 } 406 yylval.lineno = file->lineno; 407 file->lineno++; 408 } 409 410 if (c == EOF) { 411 /* 412 * Fake EOL when hit EOF for the first time. This gets line 413 * count right if last line in included file is syntactically 414 * invalid and has no newline. 415 */ 416 if (file->eof_reached == 0) { 417 file->eof_reached = 1; 418 return ('\n'); 419 } 420 while (c == EOF) { 421 if (file == topfile || popfile() == EOF) 422 return (EOF); 423 c = igetc(); 424 } 425 } 426 return (c); 427 } 428 429 void 430 lungetc(int c) 431 { 432 if (c == EOF) 433 return; 434 435 if (file->ungetpos >= file->ungetsize) { 436 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); 437 if (p == NULL) 438 err(1, "lungetc"); 439 file->ungetbuf = p; 440 file->ungetsize *= 2; 441 } 442 file->ungetbuf[file->ungetpos++] = c; 443 } 444 445 int 446 findeol(void) 447 { 448 int c; 449 450 /* Skip to either EOF or the first real EOL. */ 451 while (1) { 452 c = lgetc(0); 453 if (c == '\n') { 454 file->lineno++; 455 break; 456 } 457 if (c == EOF) 458 break; 459 } 460 return (ERROR); 461 } 462 463 int 464 yylex(void) 465 { 466 unsigned char buf[8096]; 467 unsigned char *p, *val; 468 int quotec, next, c; 469 int token; 470 471 top: 472 p = buf; 473 while ((c = lgetc(0)) == ' ' || c == '\t') 474 ; /* nothing */ 475 476 yylval.lineno = file->lineno; 477 if (c == '#') 478 while ((c = lgetc(0)) != '\n' && c != EOF) 479 ; /* nothing */ 480 if (c == '$' && !expanding) { 481 while (1) { 482 if ((c = lgetc(0)) == EOF) 483 return (0); 484 485 if (p + 1 >= buf + sizeof(buf) - 1) { 486 yyerror("string too long"); 487 return (findeol()); 488 } 489 if (isalnum(c) || c == '_') { 490 *p++ = c; 491 continue; 492 } 493 *p = '\0'; 494 lungetc(c); 495 break; 496 } 497 val = symget(buf); 498 if (val == NULL) { 499 yyerror("macro '%s' not defined", buf); 500 return (findeol()); 501 } 502 p = val + strlen(val) - 1; 503 lungetc(DONE_EXPAND); 504 while (p >= val) { 505 lungetc(*p); 506 p--; 507 } 508 lungetc(START_EXPAND); 509 goto top; 510 } 511 512 switch (c) { 513 case '\'': 514 case '"': 515 quotec = c; 516 while (1) { 517 if ((c = lgetc(quotec)) == EOF) 518 return (0); 519 if (c == '\n') { 520 file->lineno++; 521 continue; 522 } else if (c == '\\') { 523 if ((next = lgetc(quotec)) == EOF) 524 return (0); 525 if (next == quotec || next == ' ' || 526 next == '\t') 527 c = next; 528 else if (next == '\n') { 529 file->lineno++; 530 continue; 531 } else 532 lungetc(next); 533 } else if (c == quotec) { 534 *p = '\0'; 535 break; 536 } else if (c == '\0') { 537 yyerror("syntax error"); 538 return (findeol()); 539 } 540 if (p + 1 >= buf + sizeof(buf) - 1) { 541 yyerror("string too long"); 542 return (findeol()); 543 } 544 *p++ = c; 545 } 546 yylval.v.string = strdup(buf); 547 if (yylval.v.string == NULL) 548 err(1, "yylex: strdup"); 549 return (STRING); 550 } 551 552 #define allowed_to_end_number(x) \ 553 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') 554 555 if (c == '-' || isdigit(c)) { 556 do { 557 *p++ = c; 558 if ((size_t)(p-buf) >= sizeof(buf)) { 559 yyerror("string too long"); 560 return (findeol()); 561 } 562 } while ((c = lgetc(0)) != EOF && isdigit(c)); 563 lungetc(c); 564 if (p == buf + 1 && buf[0] == '-') 565 goto nodigits; 566 if (c == EOF || allowed_to_end_number(c)) { 567 const char *errstr = NULL; 568 569 *p = '\0'; 570 yylval.v.number = strtonum(buf, LLONG_MIN, 571 LLONG_MAX, &errstr); 572 if (errstr) { 573 yyerror("\"%s\" invalid number: %s", 574 buf, errstr); 575 return (findeol()); 576 } 577 return (NUMBER); 578 } else { 579 nodigits: 580 while (p > buf + 1) 581 lungetc(*--p); 582 c = *--p; 583 if (c == '-') 584 return (c); 585 } 586 } 587 588 #define allowed_in_string(x) \ 589 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ 590 x != '{' && x != '}' && \ 591 x != '!' && x != '=' && x != '#' && \ 592 x != ',')) 593 594 if (isalnum(c) || c == ':' || c == '_') { 595 do { 596 *p++ = c; 597 if ((size_t)(p-buf) >= sizeof(buf)) { 598 yyerror("string too long"); 599 return (findeol()); 600 } 601 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); 602 lungetc(c); 603 *p = '\0'; 604 if ((token = lookup(buf)) == STRING) 605 if ((yylval.v.string = strdup(buf)) == NULL) 606 err(1, "yylex: strdup"); 607 return (token); 608 } 609 if (c == '\n') { 610 yylval.lineno = file->lineno; 611 file->lineno++; 612 } 613 if (c == EOF) 614 return (0); 615 return (c); 616 } 617 618 int 619 check_file_secrecy(int fd, const char *fname) 620 { 621 struct stat st; 622 623 if (fstat(fd, &st)) { 624 log_warn("cannot stat %s", fname); 625 return (-1); 626 } 627 if (st.st_uid != 0 && st.st_uid != getuid()) { 628 log_warnx("%s: owner not root or current user", fname); 629 return (-1); 630 } 631 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { 632 log_warnx("%s: group writable or world read/writable", fname); 633 return (-1); 634 } 635 return (0); 636 } 637 638 struct file * 639 pushfile(const char *name, int secret) 640 { 641 struct file *nfile; 642 643 if ((nfile = calloc(1, sizeof(struct file))) == NULL) { 644 log_warn("calloc"); 645 return (NULL); 646 } 647 if ((nfile->name = strdup(name)) == NULL) { 648 log_warn("strdup"); 649 free(nfile); 650 return (NULL); 651 } 652 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { 653 free(nfile->name); 654 free(nfile); 655 return (NULL); 656 } else if (secret && 657 check_file_secrecy(fileno(nfile->stream), nfile->name)) { 658 fclose(nfile->stream); 659 free(nfile->name); 660 free(nfile); 661 return (NULL); 662 } 663 nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0; 664 nfile->ungetsize = 16; 665 nfile->ungetbuf = malloc(nfile->ungetsize); 666 if (nfile->ungetbuf == NULL) { 667 log_warn("malloc"); 668 fclose(nfile->stream); 669 free(nfile->name); 670 free(nfile); 671 return (NULL); 672 } 673 TAILQ_INSERT_TAIL(&files, nfile, entry); 674 return (nfile); 675 } 676 677 int 678 popfile(void) 679 { 680 struct file *prev; 681 682 if ((prev = TAILQ_PREV(file, files, entry)) != NULL) 683 prev->errors += file->errors; 684 685 TAILQ_REMOVE(&files, file, entry); 686 fclose(file->stream); 687 free(file->name); 688 free(file->ungetbuf); 689 free(file); 690 file = prev; 691 return (file ? 0 : EOF); 692 } 693 694 struct dhcpleased_conf * 695 parse_config(char *filename) 696 { 697 struct sym *sym, *next; 698 699 conf = config_new_empty(); 700 701 file = pushfile(filename != NULL ? filename : _PATH_CONF_FILE, 0); 702 if (file == NULL) { 703 /* no default config file is fine */ 704 if (errno == ENOENT && filename == NULL) 705 return (conf); 706 log_warn("%s", filename); 707 free(conf); 708 return (NULL); 709 } 710 topfile = file; 711 712 yyparse(); 713 errors = file->errors; 714 popfile(); 715 716 /* Free macros and check which have not been used. */ 717 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { 718 if ((log_getverbose() == 2) && !sym->used) 719 fprintf(stderr, "warning: macro '%s' not used\n", 720 sym->nam); 721 if (!sym->persist) { 722 free(sym->nam); 723 free(sym->val); 724 TAILQ_REMOVE(&symhead, sym, entry); 725 free(sym); 726 } 727 } 728 729 if (errors) { 730 config_clear(conf); 731 return (NULL); 732 } 733 734 return (conf); 735 } 736 737 int 738 symset(const char *nam, const char *val, int persist) 739 { 740 struct sym *sym; 741 742 TAILQ_FOREACH(sym, &symhead, entry) { 743 if (strcmp(nam, sym->nam) == 0) 744 break; 745 } 746 747 if (sym != NULL) { 748 if (sym->persist == 1) 749 return (0); 750 else { 751 free(sym->nam); 752 free(sym->val); 753 TAILQ_REMOVE(&symhead, sym, entry); 754 free(sym); 755 } 756 } 757 if ((sym = calloc(1, sizeof(*sym))) == NULL) 758 return (-1); 759 760 sym->nam = strdup(nam); 761 if (sym->nam == NULL) { 762 free(sym); 763 return (-1); 764 } 765 sym->val = strdup(val); 766 if (sym->val == NULL) { 767 free(sym->nam); 768 free(sym); 769 return (-1); 770 } 771 sym->used = 0; 772 sym->persist = persist; 773 TAILQ_INSERT_TAIL(&symhead, sym, entry); 774 return (0); 775 } 776 777 int 778 cmdline_symset(char *s) 779 { 780 char *sym, *val; 781 int ret; 782 783 if ((val = strrchr(s, '=')) == NULL) 784 return (-1); 785 sym = strndup(s, val - s); 786 if (sym == NULL) 787 errx(1, "%s: strndup", __func__); 788 ret = symset(sym, val + 1, 1); 789 free(sym); 790 791 return (ret); 792 } 793 794 char * 795 symget(const char *nam) 796 { 797 struct sym *sym; 798 799 TAILQ_FOREACH(sym, &symhead, entry) { 800 if (strcmp(nam, sym->nam) == 0) { 801 sym->used = 1; 802 return (sym->val); 803 } 804 } 805 return (NULL); 806 } 807 808 struct iface_conf * 809 conf_get_iface(char *name) 810 { 811 struct iface_conf *iface; 812 size_t n; 813 814 SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) { 815 if (strcmp(name, iface->name) == 0) 816 return (iface); 817 } 818 819 iface = calloc(1, sizeof(*iface)); 820 if (iface == NULL) 821 errx(1, "%s: calloc", __func__); 822 n = strlcpy(iface->name, name, sizeof(iface->name)); 823 if (n >= sizeof(iface->name)) 824 errx(1, "%s: name too long", __func__); 825 826 827 SIMPLEQ_INSERT_TAIL(&conf->iface_list, iface, entry); 828 829 return (iface); 830 } 831