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