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