1/* $OpenBSD: parse.y,v 1.8 2023/11/25 12:00:39 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 55TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files); 56static 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; 67struct file *pushfile(const char *, int); 68int popfile(void); 69int check_file_secrecy(int, const char *); 70int yyparse(void); 71int yylex(void); 72int yyerror(const char *, ...) 73 __attribute__((__format__ (printf, 1, 2))) 74 __attribute__((__nonnull__ (1))); 75int kw_cmp(const void *, const void *); 76int lookup(char *); 77int igetc(void); 78int lgetc(int); 79void lungetc(int); 80int findeol(void); 81 82TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); 83struct sym { 84 TAILQ_ENTRY(sym) entry; 85 int used; 86 int persist; 87 char *nam; 88 char *val; 89}; 90 91int symset(const char *, const char *, int); 92char *symget(const char *); 93 94static struct dhcpleased_conf *conf; 95static int errors; 96 97static struct iface_conf *iface_conf; 98 99struct iface_conf *conf_get_iface(char *); 100 101typedef 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 PREFER IPV6 113 114%token <v.string> STRING 115%token <v.number> NUMBER 116%type <v.string> string 117 118%% 119 120grammar : /* empty */ 121 | grammar '\n' 122 | grammar varset '\n' 123 | grammar dhcp_iface '\n' 124 | grammar error '\n' { file->errors++; } 125 ; 126 127string : 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 140varset : 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 160optnl : '\n' optnl /* zero or more newlines */ 161 | /*empty*/ 162 ; 163 164nl : '\n' optnl /* one or more newlines */ 165 ; 166 167dhcp_iface : DHCP_IFACE STRING { 168 iface_conf = conf_get_iface($2); 169 } '{' iface_block '}' { 170 iface_conf = NULL; 171 } 172 ; 173 174iface_block : optnl ifaceopts_l 175 | optnl 176 ; 177 178ifaceopts_l : ifaceopts_l ifaceoptsl nl 179 | ifaceoptsl optnl 180 ; 181 182ifaceoptsl : 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 | PREFER IPV6 { 328 iface_conf->prefer_ipv6 = 1; 329 } 330 ; 331%% 332 333struct keywords { 334 const char *k_name; 335 int k_val; 336}; 337 338int 339yyerror(const char *fmt, ...) 340{ 341 va_list ap; 342 char *msg; 343 344 file->errors++; 345 va_start(ap, fmt); 346 if (vasprintf(&msg, fmt, ap) == -1) 347 fatalx("yyerror vasprintf"); 348 va_end(ap); 349 logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg); 350 free(msg); 351 return (0); 352} 353 354int 355kw_cmp(const void *k, const void *e) 356{ 357 return (strcmp(k, ((const struct keywords *)e)->k_name)); 358} 359 360int 361lookup(char *s) 362{ 363 /* This has to be sorted always. */ 364 static const struct keywords keywords[] = { 365 {"class", CLASS}, 366 {"client", CLIENT}, 367 {"dns", DNS}, 368 {"host", HOST}, 369 {"id", ID}, 370 {"ignore", IGNORE}, 371 {"interface", DHCP_IFACE}, 372 {"ipv6", IPV6}, 373 {"name", NAME}, 374 {"no", NO}, 375 {"prefer", PREFER}, 376 {"routes", ROUTES}, 377 {"send", SEND}, 378 {"vendor", VENDOR}, 379 }; 380 const struct keywords *p; 381 382 p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]), 383 sizeof(keywords[0]), kw_cmp); 384 385 if (p) 386 return (p->k_val); 387 else 388 return (STRING); 389} 390 391#define START_EXPAND 1 392#define DONE_EXPAND 2 393 394static int expanding; 395 396int 397igetc(void) 398{ 399 int c; 400 401 while (1) { 402 if (file->ungetpos > 0) 403 c = file->ungetbuf[--file->ungetpos]; 404 else 405 c = getc(file->stream); 406 407 if (c == START_EXPAND) 408 expanding = 1; 409 else if (c == DONE_EXPAND) 410 expanding = 0; 411 else 412 break; 413 } 414 return (c); 415} 416 417int 418lgetc(int quotec) 419{ 420 int c, next; 421 422 if (quotec) { 423 if ((c = igetc()) == EOF) { 424 yyerror("reached end of file while parsing " 425 "quoted string"); 426 if (file == topfile || popfile() == EOF) 427 return (EOF); 428 return (quotec); 429 } 430 return (c); 431 } 432 433 while ((c = igetc()) == '\\') { 434 next = igetc(); 435 if (next != '\n') { 436 c = next; 437 break; 438 } 439 yylval.lineno = file->lineno; 440 file->lineno++; 441 } 442 443 if (c == EOF) { 444 /* 445 * Fake EOL when hit EOF for the first time. This gets line 446 * count right if last line in included file is syntactically 447 * invalid and has no newline. 448 */ 449 if (file->eof_reached == 0) { 450 file->eof_reached = 1; 451 return ('\n'); 452 } 453 while (c == EOF) { 454 if (file == topfile || popfile() == EOF) 455 return (EOF); 456 c = igetc(); 457 } 458 } 459 return (c); 460} 461 462void 463lungetc(int c) 464{ 465 if (c == EOF) 466 return; 467 468 if (file->ungetpos >= file->ungetsize) { 469 void *p = reallocarray(file->ungetbuf, file->ungetsize, 2); 470 if (p == NULL) 471 err(1, "lungetc"); 472 file->ungetbuf = p; 473 file->ungetsize *= 2; 474 } 475 file->ungetbuf[file->ungetpos++] = c; 476} 477 478int 479findeol(void) 480{ 481 int c; 482 483 /* Skip to either EOF or the first real EOL. */ 484 while (1) { 485 c = lgetc(0); 486 if (c == '\n') { 487 file->lineno++; 488 break; 489 } 490 if (c == EOF) 491 break; 492 } 493 return (ERROR); 494} 495 496int 497yylex(void) 498{ 499 char buf[8096]; 500 char *p, *val; 501 int quotec, next, c; 502 int token; 503 504top: 505 p = buf; 506 while ((c = lgetc(0)) == ' ' || c == '\t') 507 ; /* nothing */ 508 509 yylval.lineno = file->lineno; 510 if (c == '#') 511 while ((c = lgetc(0)) != '\n' && c != EOF) 512 ; /* nothing */ 513 if (c == '$' && !expanding) { 514 while (1) { 515 if ((c = lgetc(0)) == EOF) 516 return (0); 517 518 if (p + 1 >= buf + sizeof(buf) - 1) { 519 yyerror("string too long"); 520 return (findeol()); 521 } 522 if (isalnum(c) || c == '_') { 523 *p++ = c; 524 continue; 525 } 526 *p = '\0'; 527 lungetc(c); 528 break; 529 } 530 val = symget(buf); 531 if (val == NULL) { 532 yyerror("macro '%s' not defined", buf); 533 return (findeol()); 534 } 535 p = val + strlen(val) - 1; 536 lungetc(DONE_EXPAND); 537 while (p >= val) { 538 lungetc((unsigned char)*p); 539 p--; 540 } 541 lungetc(START_EXPAND); 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 || next == ' ' || 559 next == '\t') 560 c = next; 561 else if (next == '\n') { 562 file->lineno++; 563 continue; 564 } else 565 lungetc(next); 566 } else if (c == quotec) { 567 *p = '\0'; 568 break; 569 } else if (c == '\0') { 570 yyerror("syntax error"); 571 return (findeol()); 572 } 573 if (p + 1 >= buf + sizeof(buf) - 1) { 574 yyerror("string too long"); 575 return (findeol()); 576 } 577 *p++ = c; 578 } 579 yylval.v.string = strdup(buf); 580 if (yylval.v.string == NULL) 581 err(1, "yylex: strdup"); 582 return (STRING); 583 } 584 585#define allowed_to_end_number(x) \ 586 (isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=') 587 588 if (c == '-' || isdigit(c)) { 589 do { 590 *p++ = c; 591 if ((size_t)(p-buf) >= sizeof(buf)) { 592 yyerror("string too long"); 593 return (findeol()); 594 } 595 } while ((c = lgetc(0)) != EOF && isdigit(c)); 596 lungetc(c); 597 if (p == buf + 1 && buf[0] == '-') 598 goto nodigits; 599 if (c == EOF || allowed_to_end_number(c)) { 600 const char *errstr = NULL; 601 602 *p = '\0'; 603 yylval.v.number = strtonum(buf, LLONG_MIN, 604 LLONG_MAX, &errstr); 605 if (errstr) { 606 yyerror("\"%s\" invalid number: %s", 607 buf, errstr); 608 return (findeol()); 609 } 610 return (NUMBER); 611 } else { 612nodigits: 613 while (p > buf + 1) 614 lungetc((unsigned char)*--p); 615 c = (unsigned char)*--p; 616 if (c == '-') 617 return (c); 618 } 619 } 620 621#define allowed_in_string(x) \ 622 (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ 623 x != '{' && x != '}' && \ 624 x != '!' && x != '=' && x != '#' && \ 625 x != ',')) 626 627 if (isalnum(c) || c == ':' || c == '_') { 628 do { 629 *p++ = c; 630 if ((size_t)(p-buf) >= sizeof(buf)) { 631 yyerror("string too long"); 632 return (findeol()); 633 } 634 } while ((c = lgetc(0)) != EOF && (allowed_in_string(c))); 635 lungetc(c); 636 *p = '\0'; 637 if ((token = lookup(buf)) == STRING) 638 if ((yylval.v.string = strdup(buf)) == NULL) 639 err(1, "yylex: strdup"); 640 return (token); 641 } 642 if (c == '\n') { 643 yylval.lineno = file->lineno; 644 file->lineno++; 645 } 646 if (c == EOF) 647 return (0); 648 return (c); 649} 650 651int 652check_file_secrecy(int fd, const char *fname) 653{ 654 struct stat st; 655 656 if (fstat(fd, &st)) { 657 log_warn("cannot stat %s", fname); 658 return (-1); 659 } 660 if (st.st_uid != 0 && st.st_uid != getuid()) { 661 log_warnx("%s: owner not root or current user", fname); 662 return (-1); 663 } 664 if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) { 665 log_warnx("%s: group writable or world read/writable", fname); 666 return (-1); 667 } 668 return (0); 669} 670 671struct file * 672pushfile(const char *name, int secret) 673{ 674 struct file *nfile; 675 676 if ((nfile = calloc(1, sizeof(struct file))) == NULL) { 677 log_warn("calloc"); 678 return (NULL); 679 } 680 if ((nfile->name = strdup(name)) == NULL) { 681 log_warn("strdup"); 682 free(nfile); 683 return (NULL); 684 } 685 if ((nfile->stream = fopen(nfile->name, "r")) == NULL) { 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 = TAILQ_EMPTY(&files) ? 1 : 0; 697 nfile->ungetsize = 16; 698 nfile->ungetbuf = malloc(nfile->ungetsize); 699 if (nfile->ungetbuf == NULL) { 700 log_warn("malloc"); 701 fclose(nfile->stream); 702 free(nfile->name); 703 free(nfile); 704 return (NULL); 705 } 706 TAILQ_INSERT_TAIL(&files, nfile, entry); 707 return (nfile); 708} 709 710int 711popfile(void) 712{ 713 struct file *prev; 714 715 if ((prev = TAILQ_PREV(file, files, entry)) != NULL) 716 prev->errors += file->errors; 717 718 TAILQ_REMOVE(&files, file, entry); 719 fclose(file->stream); 720 free(file->name); 721 free(file->ungetbuf); 722 free(file); 723 file = prev; 724 return (file ? 0 : EOF); 725} 726 727struct dhcpleased_conf * 728parse_config(const char *filename) 729{ 730 extern const char default_conffile[]; 731 struct sym *sym, *next; 732 733 conf = config_new_empty(); 734 735 file = pushfile(filename, 0); 736 if (file == NULL) { 737 /* no default config file is fine */ 738 if (errno == ENOENT && filename == default_conffile) 739 return (conf); 740 log_warn("%s", filename); 741 free(conf); 742 return (NULL); 743 } 744 topfile = file; 745 746 yyparse(); 747 errors = file->errors; 748 popfile(); 749 750 /* Free macros and check which have not been used. */ 751 TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) { 752 if ((log_getverbose() == 2) && !sym->used) 753 fprintf(stderr, "warning: macro '%s' not used\n", 754 sym->nam); 755 if (!sym->persist) { 756 free(sym->nam); 757 free(sym->val); 758 TAILQ_REMOVE(&symhead, sym, entry); 759 free(sym); 760 } 761 } 762 763 if (errors) { 764 config_clear(conf); 765 return (NULL); 766 } 767 768 return (conf); 769} 770 771int 772symset(const char *nam, const char *val, int persist) 773{ 774 struct sym *sym; 775 776 TAILQ_FOREACH(sym, &symhead, entry) { 777 if (strcmp(nam, sym->nam) == 0) 778 break; 779 } 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 811int 812cmdline_symset(char *s) 813{ 814 char *sym, *val; 815 int ret; 816 817 if ((val = strrchr(s, '=')) == NULL) 818 return (-1); 819 sym = strndup(s, val - s); 820 if (sym == NULL) 821 errx(1, "%s: strndup", __func__); 822 ret = symset(sym, val + 1, 1); 823 free(sym); 824 825 return (ret); 826} 827 828char * 829symget(const char *nam) 830{ 831 struct sym *sym; 832 833 TAILQ_FOREACH(sym, &symhead, entry) { 834 if (strcmp(nam, sym->nam) == 0) { 835 sym->used = 1; 836 return (sym->val); 837 } 838 } 839 return (NULL); 840} 841 842struct iface_conf * 843conf_get_iface(char *name) 844{ 845 struct iface_conf *iface; 846 size_t n; 847 848 SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) { 849 if (strcmp(name, iface->name) == 0) 850 return (iface); 851 } 852 853 iface = calloc(1, sizeof(*iface)); 854 if (iface == NULL) 855 errx(1, "%s: calloc", __func__); 856 n = strlcpy(iface->name, name, sizeof(iface->name)); 857 if (n >= sizeof(iface->name)) 858 errx(1, "%s: name too long", __func__); 859 860 861 SIMPLEQ_INSERT_TAIL(&conf->iface_list, iface, entry); 862 863 return (iface); 864} 865