1 /* $OpenBSD: src/sbin/dhclient/clparse.c,v 1.41 2012/10/27 23:08:53 krw Exp $ */ 2 3 /* Parser for dhclient config and lease files... */ 4 5 /* 6 * Copyright (c) 1997 The Internet Software Consortium. 7 * All rights reserved. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 3. Neither the name of The Internet Software Consortium nor the names 19 * of its contributors may be used to endorse or promote products derived 20 * from this software without specific prior written permission. 21 * 22 * THIS SOFTWARE IS PROVIDED BY THE INTERNET SOFTWARE CONSORTIUM AND 23 * CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 24 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 25 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 * DISCLAIMED. IN NO EVENT SHALL THE INTERNET SOFTWARE CONSORTIUM OR 27 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 28 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 29 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 30 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 32 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 33 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 34 * SUCH DAMAGE. 35 * 36 * This software has been written for the Internet Software Consortium 37 * by Ted Lemon <mellon@fugue.com> in cooperation with Vixie 38 * Enterprises. To learn more about the Internet Software Consortium, 39 * see ``http://www.vix.com/isc''. To learn more about Vixie 40 * Enterprises, see ``http://www.vix.com''. 41 */ 42 43 #include "dhcpd.h" 44 #include "dhctoken.h" 45 46 /* 47 * client-conf-file :== client-declarations EOF 48 * client-declarations :== <nil> 49 * | client-declaration 50 * | client-declarations client-declaration 51 */ 52 int 53 read_client_conf(void) 54 { 55 FILE *cfile; 56 int token; 57 58 new_parse(path_dhclient_conf); 59 60 /* Set some defaults... */ 61 config->link_timeout = 30; 62 config->timeout = 60; 63 config->select_interval = 0; 64 config->reboot_timeout = 10; 65 config->retry_interval = 300; 66 config->backoff_cutoff = 15; 67 config->initial_interval = 3; 68 config->bootp_policy = ACCEPT; 69 config->script_name = _PATH_DHCLIENT_SCRIPT; 70 config->requested_options 71 [config->requested_option_count++] = DHO_SUBNET_MASK; 72 config->requested_options 73 [config->requested_option_count++] = DHO_BROADCAST_ADDRESS; 74 config->requested_options 75 [config->requested_option_count++] = DHO_TIME_OFFSET; 76 config->requested_options 77 [config->requested_option_count++] = DHO_ROUTERS; 78 config->requested_options 79 [config->requested_option_count++] = DHO_DOMAIN_NAME; 80 config->requested_options 81 [config->requested_option_count++] = DHO_DOMAIN_NAME_SERVERS; 82 config->requested_options 83 [config->requested_option_count++] = DHO_HOST_NAME; 84 85 if ((cfile = fopen(path_dhclient_conf, "r")) != NULL) { 86 do { 87 token = peek_token(NULL, cfile); 88 if (token == EOF) 89 break; 90 parse_client_statement(cfile); 91 } while (1); 92 token = next_token(NULL, cfile); /* Clear the peek buffer */ 93 fclose(cfile); 94 } 95 96 return (!warnings_occurred); 97 } 98 99 /* 100 * lease-file :== client-lease-statements EOF 101 * client-lease-statements :== <nil> 102 * | client-lease-statements LEASE client-lease-statement 103 */ 104 void 105 read_client_leases(void) 106 { 107 FILE *cfile; 108 int token; 109 110 new_parse(path_dhclient_db); 111 112 /* Open the lease file. If we can't open it, just return - 113 we can safely trust the server to remember our state. */ 114 if ((cfile = fopen(path_dhclient_db, "r")) == NULL) 115 return; 116 do { 117 token = next_token(NULL, cfile); 118 if (token == EOF) 119 break; 120 if (token != TOK_LEASE) { 121 warning("Corrupt lease file - possible data loss!"); 122 skip_to_semi(cfile); 123 break; 124 } else 125 parse_client_lease_statement(cfile, 0); 126 127 } while (1); 128 fclose(cfile); 129 } 130 131 /* 132 * client-declaration :== 133 * TOK_SEND option-decl | 134 * TOK_DEFAULT option-decl | 135 * TOK_SUPERSEDE option-decl | 136 * TOK_APPEND option-decl | 137 * TOK_PREPEND option-decl | 138 * TOK_MEDIA string-list | 139 * hardware-declaration | 140 * TOK_REQUEST option-list | 141 * TOK_REQUIRE option-list | 142 * TOK_TIMEOUT number | 143 * TOK_RETRY number | 144 * TOK_SELECT_TIMEOUT number | 145 * TOK_REBOOT number | 146 * TOK_BACKOFF_CUTOFF number | 147 * TOK_INITIAL_INTERVAL number | 148 * TOK_SCRIPT string | 149 * interface-declaration | 150 * TOK_LEASE client-lease-statement | 151 * TOK_ALIAS client-lease-statement | 152 * TOK_REJECT reject-statement 153 */ 154 void 155 parse_client_statement(FILE *cfile) 156 { 157 u_int8_t ignorelist[256]; 158 int token, code, count, i; 159 160 switch (next_token(NULL, cfile)) { 161 case TOK_SEND: 162 parse_option_decl(cfile, &config->send_options[0]); 163 return; 164 case TOK_DEFAULT: 165 code = parse_option_decl(cfile, &config->defaults[0]); 166 if (code != -1) 167 config->default_actions[code] = ACTION_DEFAULT; 168 return; 169 case TOK_SUPERSEDE: 170 code = parse_option_decl(cfile, &config->defaults[0]); 171 if (code != -1) 172 config->default_actions[code] = ACTION_SUPERSEDE; 173 return; 174 case TOK_IGNORE: 175 count = parse_option_list(cfile, ignorelist); 176 for (i = 0; i < count; i++) 177 config->default_actions[ignorelist[i]] = ACTION_IGNORE; 178 return; 179 case TOK_APPEND: 180 code = parse_option_decl(cfile, &config->defaults[0]); 181 if (code != -1) 182 config->default_actions[code] = ACTION_APPEND; 183 return; 184 case TOK_PREPEND: 185 code = parse_option_decl(cfile, &config->defaults[0]); 186 if (code != -1) 187 config->default_actions[code] = ACTION_PREPEND; 188 return; 189 case TOK_MEDIA: 190 skip_to_semi(cfile); 191 return; 192 case TOK_HARDWARE: 193 parse_hardware_param(cfile, &ifi->hw_address); 194 return; 195 case TOK_REQUEST: 196 config->requested_option_count = 197 parse_option_list(cfile, config->requested_options); 198 return; 199 case TOK_REQUIRE: 200 memset(config->required_options, 0, 201 sizeof(config->required_options)); 202 parse_option_list(cfile, config->required_options); 203 return; 204 case TOK_LINK_TIMEOUT: 205 parse_lease_time(cfile, &config->link_timeout); 206 return; 207 case TOK_TIMEOUT: 208 parse_lease_time(cfile, &config->timeout); 209 return; 210 case TOK_RETRY: 211 parse_lease_time(cfile, &config->retry_interval); 212 return; 213 case TOK_SELECT_TIMEOUT: 214 parse_lease_time(cfile, &config->select_interval); 215 return; 216 case TOK_REBOOT: 217 parse_lease_time(cfile, &config->reboot_timeout); 218 return; 219 case TOK_BACKOFF_CUTOFF: 220 parse_lease_time(cfile, &config->backoff_cutoff); 221 return; 222 case TOK_INITIAL_INTERVAL: 223 parse_lease_time(cfile, &config->initial_interval); 224 return; 225 case TOK_SCRIPT: 226 config->script_name = parse_string(cfile); 227 return; 228 case TOK_INTERFACE: 229 parse_interface_declaration(cfile); 230 return; 231 case TOK_LEASE: 232 parse_client_lease_statement(cfile, 1); 233 return; 234 case TOK_ALIAS: 235 skip_to_semi(cfile); 236 return; 237 case TOK_REJECT: 238 parse_reject_statement(cfile); 239 return; 240 default: 241 parse_warn("expecting a statement."); 242 skip_to_semi(cfile); 243 break; 244 } 245 token = next_token(NULL, cfile); 246 if (token != ';') { 247 parse_warn("semicolon expected."); 248 skip_to_semi(cfile); 249 } 250 } 251 252 int 253 parse_X(FILE *cfile, u_int8_t *buf, int max) 254 { 255 int token; 256 char *val; 257 int len; 258 259 token = peek_token(&val, cfile); 260 if (token == TOK_NUMBER_OR_NAME || token == TOK_NUMBER) { 261 len = 0; 262 do { 263 token = next_token(&val, cfile); 264 if (token != TOK_NUMBER && token != TOK_NUMBER_OR_NAME) { 265 parse_warn("expecting hexadecimal constant."); 266 skip_to_semi(cfile); 267 return (0); 268 } 269 convert_num(&buf[len], val, 16, 8); 270 if (len++ > max) { 271 parse_warn("hexadecimal constant too long."); 272 skip_to_semi(cfile); 273 return (0); 274 } 275 token = peek_token(&val, cfile); 276 if (token == ':') 277 token = next_token(&val, cfile); 278 } while (token == ':'); 279 val = (char *)buf; 280 } else if (token == TOK_STRING) { 281 token = next_token(&val, cfile); 282 len = strlen(val); 283 if (len + 1 > max) { 284 parse_warn("string constant too long."); 285 skip_to_semi(cfile); 286 return (0); 287 } 288 memcpy(buf, val, len + 1); 289 } else { 290 parse_warn("expecting string or hexadecimal data"); 291 skip_to_semi(cfile); 292 return (0); 293 } 294 return (len); 295 } 296 297 /* 298 * option-list :== option_name | 299 * option_list COMMA option_name 300 */ 301 int 302 parse_option_list(FILE *cfile, u_int8_t *list) 303 { 304 int ix, i; 305 int token; 306 char *val; 307 308 ix = 0; 309 do { 310 token = next_token(&val, cfile); 311 if (!is_identifier(token)) { 312 parse_warn("expected option name."); 313 skip_to_semi(cfile); 314 return (0); 315 } 316 for (i = 0; i < 256; i++) 317 if (!strcasecmp(dhcp_options[i].name, val)) 318 break; 319 320 if (i == 256) { 321 parse_warn("%s: unexpected option name.", val); 322 skip_to_semi(cfile); 323 return (0); 324 } 325 list[ix++] = i; 326 if (ix == 256) { 327 parse_warn("%s: too many options.", val); 328 skip_to_semi(cfile); 329 return (0); 330 } 331 token = next_token(&val, cfile); 332 } while (token == ','); 333 if (token != ';') { 334 parse_warn("expecting semicolon."); 335 skip_to_semi(cfile); 336 return (0); 337 } 338 return (ix); 339 } 340 341 /* 342 * interface-declaration :== 343 * INTERFACE string LBRACE client-declarations RBRACE 344 */ 345 void 346 parse_interface_declaration(FILE *cfile) 347 { 348 char *val; 349 int token; 350 351 token = next_token(&val, cfile); 352 if (token != TOK_STRING) { 353 parse_warn("expecting interface name (in quotes)."); 354 skip_to_semi(cfile); 355 return; 356 } 357 358 if (strcmp(ifi->name, val) != 0) { 359 skip_to_semi(cfile); 360 return; 361 } 362 363 token = next_token(&val, cfile); 364 if (token != '{') { 365 parse_warn("expecting left brace."); 366 skip_to_semi(cfile); 367 return; 368 } 369 370 do { 371 token = peek_token(&val, cfile); 372 if (token == EOF) { 373 parse_warn("unterminated interface declaration."); 374 return; 375 } 376 if (token == '}') 377 break; 378 parse_client_statement(cfile); 379 } while (1); 380 token = next_token(&val, cfile); 381 } 382 383 /* 384 * client-lease-statement :== 385 * RBRACE client-lease-declarations LBRACE 386 * 387 * client-lease-declarations :== 388 * <nil> | 389 * client-lease-declaration | 390 * client-lease-declarations client-lease-declaration 391 */ 392 void 393 parse_client_lease_statement(FILE *cfile, int is_static) 394 { 395 struct client_lease *lease, *lp, *pl; 396 int token; 397 398 token = next_token(NULL, cfile); 399 if (token != '{') { 400 parse_warn("expecting left brace."); 401 skip_to_semi(cfile); 402 return; 403 } 404 405 lease = malloc(sizeof(struct client_lease)); 406 if (!lease) 407 error("no memory for lease."); 408 memset(lease, 0, sizeof(*lease)); 409 lease->is_static = is_static; 410 411 do { 412 token = peek_token(NULL, cfile); 413 if (token == EOF) { 414 parse_warn("unterminated lease declaration."); 415 return; 416 } 417 if (token == '}') 418 break; 419 parse_client_lease_declaration(cfile, lease); 420 } while (1); 421 token = next_token(NULL, cfile); 422 423 /* If the lease declaration didn't include an interface 424 * declaration that we recognized, it's of no use to us. 425 */ 426 if (!ifi) { 427 free_client_lease(lease); 428 return; 429 } 430 431 /* 432 * The new lease may supersede a lease that's not the active 433 * lease but is still on the lease list, so scan the lease list 434 * looking for a lease with the same address, and if we find it, 435 * toss it. 436 */ 437 pl = NULL; 438 for (lp = client->leases; lp; lp = lp->next) { 439 if (addr_eq(lp->address, lease->address)) { 440 if (pl) 441 pl->next = lp->next; 442 else 443 client->leases = lp->next; 444 free_client_lease(lp); 445 break; 446 } else 447 pl = lp; 448 } 449 450 /* 451 * If this is a preloaded lease, just put it on the list of 452 * recorded leases - don't make it the active lease. 453 */ 454 if (is_static) { 455 lease->next = client->leases; 456 client->leases = lease; 457 return; 458 } 459 460 /* 461 * The last lease in the lease file on a particular interface is 462 * the active lease for that interface. Of course, we don't 463 * know what the last lease in the file is until we've parsed 464 * the whole file, so at this point, we assume that the lease we 465 * just parsed is the active lease for its interface. If 466 * there's already an active lease for the interface, and this 467 * lease is for the same ip address, then we just toss the old 468 * active lease and replace it with this one. If this lease is 469 * for a different address, then if the old active lease has 470 * expired, we dump it; if not, we put it on the list of leases 471 * for this interface which are still valid but no longer 472 * active. 473 */ 474 if (client->active) { 475 if (client->active->expiry < time(NULL)) 476 free_client_lease(client->active); 477 else if (addr_eq(client->active->address, lease->address)) 478 free_client_lease(client->active); 479 else { 480 client->active->next = client->leases; 481 client->leases = client->active; 482 } 483 } 484 client->active = lease; 485 486 /* Phew. */ 487 } 488 489 /* 490 * client-lease-declaration :== 491 * BOOTP | 492 * INTERFACE string | 493 * FIXED_ADDR ip_address | 494 * FILENAME string | 495 * SERVER_NAME string | 496 * OPTION option-decl | 497 * RENEW time-decl | 498 * REBIND time-decl | 499 * EXPIRE time-decl 500 */ 501 void 502 parse_client_lease_declaration(FILE *cfile, struct client_lease *lease) 503 { 504 char *val; 505 int token; 506 507 switch (next_token(&val, cfile)) { 508 case TOK_BOOTP: 509 lease->is_bootp = 1; 510 break; 511 case TOK_INTERFACE: 512 token = next_token(&val, cfile); 513 if (token != TOK_STRING) { 514 parse_warn("expecting interface name (in quotes)."); 515 skip_to_semi(cfile); 516 break; 517 } 518 if (strcmp(ifi->name, val) != 0) { 519 parse_warn("wrong interface name. Expecting '%s'.", 520 ifi->name); 521 skip_to_semi(cfile); 522 break; 523 } 524 break; 525 case TOK_FIXED_ADDR: 526 if (!parse_ip_addr(cfile, &lease->address)) 527 return; 528 break; 529 case TOK_MEDIUM: 530 skip_to_semi(cfile); 531 return; 532 case TOK_FILENAME: 533 lease->filename = parse_string(cfile); 534 return; 535 case TOK_SERVER_NAME: 536 lease->server_name = parse_string(cfile); 537 return; 538 case TOK_RENEW: 539 lease->renewal = parse_date(cfile); 540 return; 541 case TOK_REBIND: 542 lease->rebind = parse_date(cfile); 543 return; 544 case TOK_EXPIRE: 545 lease->expiry = parse_date(cfile); 546 return; 547 case TOK_OPTION: 548 parse_option_decl(cfile, lease->options); 549 return; 550 default: 551 parse_warn("expecting lease declaration."); 552 skip_to_semi(cfile); 553 break; 554 } 555 token = next_token(&val, cfile); 556 if (token != ';') { 557 parse_warn("expecting semicolon."); 558 skip_to_semi(cfile); 559 } 560 } 561 562 int 563 parse_option_decl(FILE *cfile, struct option_data *options) 564 { 565 char *val; 566 int token; 567 u_int8_t buf[4]; 568 u_int8_t hunkbuf[1024]; 569 int hunkix = 0; 570 char *fmt; 571 struct iaddr ip_addr; 572 u_int8_t *dp; 573 int len, code; 574 int nul_term = 0; 575 576 token = next_token(&val, cfile); 577 if (!is_identifier(token)) { 578 parse_warn("expecting identifier after option keyword."); 579 if (token != ';') 580 skip_to_semi(cfile); 581 return (-1); 582 } 583 584 /* Look up the actual option info. */ 585 fmt = NULL; 586 for (code = 0; code < 256; code++) 587 if (strcmp(dhcp_options[code].name, val) == 0) 588 break; 589 590 if (code > 255) { 591 parse_warn("no option named %s", val); 592 skip_to_semi(cfile); 593 return (-1); 594 } 595 596 /* Parse the option data... */ 597 do { 598 for (fmt = dhcp_options[code].format; *fmt; fmt++) { 599 if (*fmt == 'A') 600 break; 601 switch (*fmt) { 602 case 'X': 603 len = parse_X(cfile, &hunkbuf[hunkix], 604 sizeof(hunkbuf) - hunkix); 605 hunkix += len; 606 break; 607 case 't': /* Text string... */ 608 token = next_token(&val, cfile); 609 if (token != TOK_STRING) { 610 parse_warn("expecting string."); 611 skip_to_semi(cfile); 612 return (-1); 613 } 614 len = strlen(val); 615 if (hunkix + len + 1 > sizeof(hunkbuf)) { 616 parse_warn("option data buffer %s", 617 "overflow"); 618 skip_to_semi(cfile); 619 return (-1); 620 } 621 memcpy(&hunkbuf[hunkix], val, len + 1); 622 nul_term = 1; 623 hunkix += len; 624 break; 625 case 'I': /* IP address. */ 626 if (!parse_ip_addr(cfile, &ip_addr)) 627 return (-1); 628 len = ip_addr.len; 629 dp = ip_addr.iabuf; 630 alloc: 631 if (hunkix + len > sizeof(hunkbuf)) { 632 parse_warn("option data buffer " 633 "overflow"); 634 skip_to_semi(cfile); 635 return (-1); 636 } 637 memcpy(&hunkbuf[hunkix], dp, len); 638 hunkix += len; 639 break; 640 case 'L': /* Unsigned 32-bit integer... */ 641 case 'l': /* Signed 32-bit integer... */ 642 token = next_token(&val, cfile); 643 if (token != TOK_NUMBER) { 644 need_number: 645 parse_warn("expecting number."); 646 if (token != ';') 647 skip_to_semi(cfile); 648 return (-1); 649 } 650 convert_num(buf, val, 0, 32); 651 len = 4; 652 dp = buf; 653 goto alloc; 654 case 's': /* Signed 16-bit integer. */ 655 case 'S': /* Unsigned 16-bit integer. */ 656 token = next_token(&val, cfile); 657 if (token != TOK_NUMBER) 658 goto need_number; 659 convert_num(buf, val, 0, 16); 660 len = 2; 661 dp = buf; 662 goto alloc; 663 case 'b': /* Signed 8-bit integer. */ 664 case 'B': /* Unsigned 8-bit integer. */ 665 token = next_token(&val, cfile); 666 if (token != TOK_NUMBER) 667 goto need_number; 668 convert_num(buf, val, 0, 8); 669 len = 1; 670 dp = buf; 671 goto alloc; 672 case 'f': /* Boolean flag. */ 673 token = next_token(&val, cfile); 674 if (!is_identifier(token)) { 675 parse_warn("expecting identifier."); 676 bad_flag: 677 if (token != ';') 678 skip_to_semi(cfile); 679 return (-1); 680 } 681 if (!strcasecmp(val, "true") || 682 !strcasecmp(val, "on")) 683 buf[0] = 1; 684 else if (!strcasecmp(val, "false") || 685 !strcasecmp(val, "off")) 686 buf[0] = 0; 687 else { 688 parse_warn("expecting boolean."); 689 goto bad_flag; 690 } 691 len = 1; 692 dp = buf; 693 goto alloc; 694 default: 695 warning("Bad format %c in parse_option_param.", 696 *fmt); 697 skip_to_semi(cfile); 698 return (-1); 699 } 700 } 701 token = next_token(&val, cfile); 702 } while (*fmt == 'A' && token == ','); 703 704 if (token != ';') { 705 parse_warn("semicolon expected."); 706 skip_to_semi(cfile); 707 return (-1); 708 } 709 710 options[code].data = malloc(hunkix + nul_term); 711 if (!options[code].data) 712 error("out of memory allocating option data."); 713 memcpy(options[code].data, hunkbuf, hunkix + nul_term); 714 options[code].len = hunkix; 715 return (code); 716 } 717 718 void 719 parse_reject_statement(FILE *cfile) 720 { 721 struct iaddrlist *list; 722 struct iaddr addr; 723 int token; 724 725 do { 726 if (!parse_ip_addr(cfile, &addr)) { 727 parse_warn("expecting IP address."); 728 skip_to_semi(cfile); 729 return; 730 } 731 732 list = malloc(sizeof(struct iaddrlist)); 733 if (!list) 734 error("no memory for reject list!"); 735 736 list->addr = addr; 737 list->next = config->reject_list; 738 config->reject_list = list; 739 740 token = next_token(NULL, cfile); 741 } while (token == ','); 742 743 if (token != ';') { 744 parse_warn("expecting semicolon."); 745 skip_to_semi(cfile); 746 } 747 } 748