xref: /dragonfly/sbin/dhclient/clparse.c (revision 25a2db75)
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