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