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