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