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