xref: /openbsd/sbin/dhcpleased/parse.y (revision 8a60c40e)
1 /*	$OpenBSD: parse.y,v 1.4 2021/09/20 11:46:22 florian Exp $	*/
2 
3 /*
4  * Copyright (c) 2018 Florian Obser <florian@openbsd.org>
5  * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
6  * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
7  * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
8  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
9  * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
10  * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
11  *
12  * Permission to use, copy, modify, and distribute this software for any
13  * purpose with or without fee is hereby granted, provided that the above
14  * copyright notice and this permission notice appear in all copies.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
17  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
18  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
19  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
20  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
21  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
22  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
23  */
24 
25 %{
26 #include <sys/types.h>
27 #include <sys/queue.h>
28 #include <sys/socket.h>
29 #include <sys/stat.h>
30 
31 #include <net/if.h>
32 
33 #include <netinet/in.h>
34 #include <netinet/if_ether.h>
35 
36 #include <arpa/inet.h>
37 
38 #include <ctype.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <event.h>
42 #include <imsg.h>
43 #include <limits.h>
44 #include <stdarg.h>
45 #include <stdio.h>
46 #include <string.h>
47 #include <syslog.h>
48 #include <unistd.h>
49 #include <vis.h>
50 
51 #include "log.h"
52 #include "dhcpleased.h"
53 #include "frontend.h"
54 
55 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
56 static struct file {
57 	TAILQ_ENTRY(file)	 entry;
58 	FILE			*stream;
59 	char			*name;
60 	size_t	 		 ungetpos;
61 	size_t			 ungetsize;
62 	u_char			*ungetbuf;
63 	int			 eof_reached;
64 	int			 lineno;
65 	int			 errors;
66 } *file, *topfile;
67 struct file	*pushfile(const char *, int);
68 int		 popfile(void);
69 int		 check_file_secrecy(int, const char *);
70 int		 yyparse(void);
71 int		 yylex(void);
72 int		 yyerror(const char *, ...)
73     __attribute__((__format__ (printf, 1, 2)))
74     __attribute__((__nonnull__ (1)));
75 int		 kw_cmp(const void *, const void *);
76 int		 lookup(char *);
77 int		 igetc(void);
78 int		 lgetc(int);
79 void		 lungetc(int);
80 int		 findeol(void);
81 
82 TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
83 struct sym {
84 	TAILQ_ENTRY(sym)	 entry;
85 	int			 used;
86 	int			 persist;
87 	char			*nam;
88 	char			*val;
89 };
90 
91 int	 symset(const char *, const char *, int);
92 char	*symget(const char *);
93 
94 static struct dhcpleased_conf	*conf;
95 static int			 errors;
96 
97 static struct iface_conf	*iface_conf;
98 
99 struct iface_conf	*conf_get_iface(char *);
100 
101 typedef struct {
102 	union {
103 		int64_t		 number;
104 		char		*string;
105 	} v;
106 	int lineno;
107 } YYSTYPE;
108 
109 %}
110 
111 %token	DHCP_IFACE ERROR SEND VENDOR CLASS ID CLIENT IGNORE DNS ROUTES
112 
113 %token	<v.string>	STRING
114 %token	<v.number>	NUMBER
115 %type	<v.string>	string
116 
117 %%
118 
119 grammar		: /* empty */
120 		| grammar '\n'
121 		| grammar varset '\n'
122 		| grammar dhcp_iface '\n'
123 		| grammar error '\n'		{ file->errors++; }
124 		;
125 
126 string		: string STRING	{
127 			if (asprintf(&$$, "%s %s", $1, $2) == -1) {
128 				free($1);
129 				free($2);
130 				yyerror("string: asprintf");
131 				YYERROR;
132 			}
133 			free($1);
134 			free($2);
135 		}
136 		| STRING
137 		;
138 
139 varset		: STRING '=' string		{
140 			char *s = $1;
141 			if (log_getverbose() == 1)
142 				printf("%s = \"%s\"\n", $1, $3);
143 			while (*s++) {
144 				if (isspace((unsigned char)*s)) {
145 					yyerror("macro name cannot contain "
146 					    "whitespace");
147 					free($1);
148 					free($3);
149 					YYERROR;
150 				}
151 			}
152 			if (symset($1, $3, 0) == -1)
153 				fatal("cannot store variable");
154 			free($1);
155 			free($3);
156 		}
157 		;
158 
159 optnl		: '\n' optnl		/* zero or more newlines */
160 		| /*empty*/
161 		;
162 
163 nl		: '\n' optnl		/* one or more newlines */
164 		;
165 
166 dhcp_iface	: DHCP_IFACE STRING {
167 			iface_conf = conf_get_iface($2);
168 		} '{' iface_block '}' {
169 			iface_conf = NULL;
170 		}
171 		;
172 
173 iface_block	: optnl ifaceopts_l
174 		| optnl
175 		;
176 
177 ifaceopts_l	: ifaceopts_l ifaceoptsl nl
178 		| ifaceoptsl optnl
179 		;
180 
181 ifaceoptsl	: SEND VENDOR CLASS ID STRING {
182 			ssize_t len;
183 			char	buf[256];
184 
185 			if (iface_conf->vc_id != NULL) {
186 				yyerror("vendor class id already set");
187 				YYERROR;
188 			}
189 
190 			len = strnunvis(buf, $5, sizeof(buf));
191 			free($5);
192 
193 			if (len == -1) {
194 				yyerror("invalid vendor class id");
195 				YYERROR;
196 			}
197 			if ((size_t)len >= sizeof(buf)) {
198 				yyerror("vendor class id too long");
199 				YYERROR;
200 			}
201 
202 			iface_conf->vc_id_len = 2 + strlen(buf);
203 			iface_conf->vc_id = malloc(iface_conf->vc_id_len);
204 			if (iface_conf->vc_id == NULL) {
205 				yyerror("malloc");
206 				YYERROR;
207 			}
208 			iface_conf->vc_id[0] = DHO_DHCP_CLASS_IDENTIFIER;
209 			iface_conf->vc_id[1] = iface_conf->vc_id_len - 2;
210 			memcpy(&iface_conf->vc_id[2], buf,
211 			    iface_conf->vc_id_len - 2);
212 		}
213 		| SEND CLIENT ID STRING {
214 			size_t			 i;
215 			ssize_t			 len;
216 			int			 not_hex = 0, val;
217 			char			 buf[256], *hex, *p, excess;
218 
219 			if (iface_conf->c_id != NULL) {
220 				yyerror("client-id already set");
221 				YYERROR;
222 			}
223 
224 			/* parse as hex string including the type byte */
225 			if ((hex = strdup($4)) == NULL) {
226 				free($4);
227 				yyerror("malloc");
228 				YYERROR;
229 			}
230 			for (i = 0; (p = strsep(&hex, ":")) != NULL && i <
231 				 sizeof(buf); ) {
232 				if (sscanf(p, "%x%c", &val, &excess) != 1 ||
233 				    val < 0 || val > 0xff) {
234 					not_hex = 1;
235 					break;
236 				}
237 				buf[i++] = (val & 0xff);
238 			}
239 			if (p != NULL && i == sizeof(buf))
240 				not_hex = 1;
241 			free(hex);
242 
243 			if (not_hex) {
244 				len = strnunvis(buf, $4, sizeof(buf));
245 				free($4);
246 
247 				if (len == -1) {
248 					yyerror("invalid client-id");
249 					YYERROR;
250 				}
251 				if ((size_t)len >= sizeof(buf)) {
252 					yyerror("client-id too long");
253 					YYERROR;
254 				}
255 				iface_conf->c_id_len = 2 + len;
256 				iface_conf->c_id = malloc(iface_conf->c_id_len);
257 				if (iface_conf->c_id == NULL) {
258 					yyerror("malloc");
259 					YYERROR;
260 				}
261 				memcpy(&iface_conf->c_id[2], buf,
262 				    iface_conf->c_id_len - 2);
263 			} else {
264 				free($4);
265 				iface_conf->c_id_len = 2 + i;
266 				iface_conf->c_id = malloc(iface_conf->c_id_len);
267 				if (iface_conf->c_id == NULL) {
268 					yyerror("malloc");
269 					YYERROR;
270 				}
271 				memcpy(&iface_conf->c_id[2], buf,
272 				    iface_conf->c_id_len - 2);
273 			}
274 			iface_conf->c_id[0] = DHO_DHCP_CLIENT_IDENTIFIER;
275 			iface_conf->c_id[1] = iface_conf->c_id_len - 2;
276 		}
277 		| IGNORE ROUTES {
278 			iface_conf->ignore |= IGN_ROUTES;
279 		}
280 		| IGNORE DNS {
281 			iface_conf->ignore |= IGN_DNS;
282 		}
283 		| IGNORE STRING {
284 			int res;
285 
286 			if (iface_conf->ignore_servers_len >= MAX_SERVERS) {
287 				yyerror("too many servers to ignore");
288 				free($2);
289 				YYERROR;
290 			}
291 			res = inet_pton(AF_INET, $2,
292 			    &iface_conf->ignore_servers[
293 			    iface_conf->ignore_servers_len++]);
294 
295 			if (res != 1) {
296 				yyerror("Invalid server IP %s", $2);
297 				free($2);
298 				YYERROR;
299 			}
300 			free($2);
301 		}
302 		;
303 %%
304 
305 struct keywords {
306 	const char	*k_name;
307 	int		 k_val;
308 };
309 
310 int
311 yyerror(const char *fmt, ...)
312 {
313 	va_list		 ap;
314 	char		*msg;
315 
316 	file->errors++;
317 	va_start(ap, fmt);
318 	if (vasprintf(&msg, fmt, ap) == -1)
319 		fatalx("yyerror vasprintf");
320 	va_end(ap);
321 	logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
322 	free(msg);
323 	return (0);
324 }
325 
326 int
327 kw_cmp(const void *k, const void *e)
328 {
329 	return (strcmp(k, ((const struct keywords *)e)->k_name));
330 }
331 
332 int
333 lookup(char *s)
334 {
335 	/* This has to be sorted always. */
336 	static const struct keywords keywords[] = {
337 		{"class",		CLASS},
338 		{"client",		CLIENT},
339 		{"dns",			DNS},
340 		{"id",			ID},
341 		{"ignore",		IGNORE},
342 		{"interface",		DHCP_IFACE},
343 		{"routes",		ROUTES},
344 		{"send",		SEND},
345 		{"vendor",		VENDOR},
346 	};
347 	const struct keywords	*p;
348 
349 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
350 	    sizeof(keywords[0]), kw_cmp);
351 
352 	if (p)
353 		return (p->k_val);
354 	else
355 		return (STRING);
356 }
357 
358 #define START_EXPAND	1
359 #define DONE_EXPAND	2
360 
361 static int	expanding;
362 
363 int
364 igetc(void)
365 {
366 	int	c;
367 
368 	while (1) {
369 		if (file->ungetpos > 0)
370 			c = file->ungetbuf[--file->ungetpos];
371 		else
372 			c = getc(file->stream);
373 
374 		if (c == START_EXPAND)
375 			expanding = 1;
376 		else if (c == DONE_EXPAND)
377 			expanding = 0;
378 		else
379 			break;
380 	}
381 	return (c);
382 }
383 
384 int
385 lgetc(int quotec)
386 {
387 	int		c, next;
388 
389 	if (quotec) {
390 		if ((c = igetc()) == EOF) {
391 			yyerror("reached end of file while parsing "
392 			    "quoted string");
393 			if (file == topfile || popfile() == EOF)
394 				return (EOF);
395 			return (quotec);
396 		}
397 		return (c);
398 	}
399 
400 	while ((c = igetc()) == '\\') {
401 		next = igetc();
402 		if (next != '\n') {
403 			c = next;
404 			break;
405 		}
406 		yylval.lineno = file->lineno;
407 		file->lineno++;
408 	}
409 
410 	if (c == EOF) {
411 		/*
412 		 * Fake EOL when hit EOF for the first time. This gets line
413 		 * count right if last line in included file is syntactically
414 		 * invalid and has no newline.
415 		 */
416 		if (file->eof_reached == 0) {
417 			file->eof_reached = 1;
418 			return ('\n');
419 		}
420 		while (c == EOF) {
421 			if (file == topfile || popfile() == EOF)
422 				return (EOF);
423 			c = igetc();
424 		}
425 	}
426 	return (c);
427 }
428 
429 void
430 lungetc(int c)
431 {
432 	if (c == EOF)
433 		return;
434 
435 	if (file->ungetpos >= file->ungetsize) {
436 		void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
437 		if (p == NULL)
438 			err(1, "lungetc");
439 		file->ungetbuf = p;
440 		file->ungetsize *= 2;
441 	}
442 	file->ungetbuf[file->ungetpos++] = c;
443 }
444 
445 int
446 findeol(void)
447 {
448 	int	c;
449 
450 	/* Skip to either EOF or the first real EOL. */
451 	while (1) {
452 		c = lgetc(0);
453 		if (c == '\n') {
454 			file->lineno++;
455 			break;
456 		}
457 		if (c == EOF)
458 			break;
459 	}
460 	return (ERROR);
461 }
462 
463 int
464 yylex(void)
465 {
466 	unsigned char	 buf[8096];
467 	unsigned char	*p, *val;
468 	int		 quotec, next, c;
469 	int		 token;
470 
471 top:
472 	p = buf;
473 	while ((c = lgetc(0)) == ' ' || c == '\t')
474 		; /* nothing */
475 
476 	yylval.lineno = file->lineno;
477 	if (c == '#')
478 		while ((c = lgetc(0)) != '\n' && c != EOF)
479 			; /* nothing */
480 	if (c == '$' && !expanding) {
481 		while (1) {
482 			if ((c = lgetc(0)) == EOF)
483 				return (0);
484 
485 			if (p + 1 >= buf + sizeof(buf) - 1) {
486 				yyerror("string too long");
487 				return (findeol());
488 			}
489 			if (isalnum(c) || c == '_') {
490 				*p++ = c;
491 				continue;
492 			}
493 			*p = '\0';
494 			lungetc(c);
495 			break;
496 		}
497 		val = symget(buf);
498 		if (val == NULL) {
499 			yyerror("macro '%s' not defined", buf);
500 			return (findeol());
501 		}
502 		p = val + strlen(val) - 1;
503 		lungetc(DONE_EXPAND);
504 		while (p >= val) {
505 			lungetc(*p);
506 			p--;
507 		}
508 		lungetc(START_EXPAND);
509 		goto top;
510 	}
511 
512 	switch (c) {
513 	case '\'':
514 	case '"':
515 		quotec = c;
516 		while (1) {
517 			if ((c = lgetc(quotec)) == EOF)
518 				return (0);
519 			if (c == '\n') {
520 				file->lineno++;
521 				continue;
522 			} else if (c == '\\') {
523 				if ((next = lgetc(quotec)) == EOF)
524 					return (0);
525 				if (next == quotec || next == ' ' ||
526 				    next == '\t')
527 					c = next;
528 				else if (next == '\n') {
529 					file->lineno++;
530 					continue;
531 				} else
532 					lungetc(next);
533 			} else if (c == quotec) {
534 				*p = '\0';
535 				break;
536 			} else if (c == '\0') {
537 				yyerror("syntax error");
538 				return (findeol());
539 			}
540 			if (p + 1 >= buf + sizeof(buf) - 1) {
541 				yyerror("string too long");
542 				return (findeol());
543 			}
544 			*p++ = c;
545 		}
546 		yylval.v.string = strdup(buf);
547 		if (yylval.v.string == NULL)
548 			err(1, "yylex: strdup");
549 		return (STRING);
550 	}
551 
552 #define allowed_to_end_number(x) \
553 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
554 
555 	if (c == '-' || isdigit(c)) {
556 		do {
557 			*p++ = c;
558 			if ((size_t)(p-buf) >= sizeof(buf)) {
559 				yyerror("string too long");
560 				return (findeol());
561 			}
562 		} while ((c = lgetc(0)) != EOF && isdigit(c));
563 		lungetc(c);
564 		if (p == buf + 1 && buf[0] == '-')
565 			goto nodigits;
566 		if (c == EOF || allowed_to_end_number(c)) {
567 			const char *errstr = NULL;
568 
569 			*p = '\0';
570 			yylval.v.number = strtonum(buf, LLONG_MIN,
571 			    LLONG_MAX, &errstr);
572 			if (errstr) {
573 				yyerror("\"%s\" invalid number: %s",
574 				    buf, errstr);
575 				return (findeol());
576 			}
577 			return (NUMBER);
578 		} else {
579 nodigits:
580 			while (p > buf + 1)
581 				lungetc(*--p);
582 			c = *--p;
583 			if (c == '-')
584 				return (c);
585 		}
586 	}
587 
588 #define allowed_in_string(x) \
589 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
590 	x != '{' && x != '}' && \
591 	x != '!' && x != '=' && x != '#' && \
592 	x != ','))
593 
594 	if (isalnum(c) || c == ':' || c == '_') {
595 		do {
596 			*p++ = c;
597 			if ((size_t)(p-buf) >= sizeof(buf)) {
598 				yyerror("string too long");
599 				return (findeol());
600 			}
601 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
602 		lungetc(c);
603 		*p = '\0';
604 		if ((token = lookup(buf)) == STRING)
605 			if ((yylval.v.string = strdup(buf)) == NULL)
606 				err(1, "yylex: strdup");
607 		return (token);
608 	}
609 	if (c == '\n') {
610 		yylval.lineno = file->lineno;
611 		file->lineno++;
612 	}
613 	if (c == EOF)
614 		return (0);
615 	return (c);
616 }
617 
618 int
619 check_file_secrecy(int fd, const char *fname)
620 {
621 	struct stat	st;
622 
623 	if (fstat(fd, &st)) {
624 		log_warn("cannot stat %s", fname);
625 		return (-1);
626 	}
627 	if (st.st_uid != 0 && st.st_uid != getuid()) {
628 		log_warnx("%s: owner not root or current user", fname);
629 		return (-1);
630 	}
631 	if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
632 		log_warnx("%s: group writable or world read/writable", fname);
633 		return (-1);
634 	}
635 	return (0);
636 }
637 
638 struct file *
639 pushfile(const char *name, int secret)
640 {
641 	struct file	*nfile;
642 
643 	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
644 		log_warn("calloc");
645 		return (NULL);
646 	}
647 	if ((nfile->name = strdup(name)) == NULL) {
648 		log_warn("strdup");
649 		free(nfile);
650 		return (NULL);
651 	}
652 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
653 		free(nfile->name);
654 		free(nfile);
655 		return (NULL);
656 	} else if (secret &&
657 	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
658 		fclose(nfile->stream);
659 		free(nfile->name);
660 		free(nfile);
661 		return (NULL);
662 	}
663 	nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
664 	nfile->ungetsize = 16;
665 	nfile->ungetbuf = malloc(nfile->ungetsize);
666 	if (nfile->ungetbuf == NULL) {
667 		log_warn("malloc");
668 		fclose(nfile->stream);
669 		free(nfile->name);
670 		free(nfile);
671 		return (NULL);
672 	}
673 	TAILQ_INSERT_TAIL(&files, nfile, entry);
674 	return (nfile);
675 }
676 
677 int
678 popfile(void)
679 {
680 	struct file	*prev;
681 
682 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
683 		prev->errors += file->errors;
684 
685 	TAILQ_REMOVE(&files, file, entry);
686 	fclose(file->stream);
687 	free(file->name);
688 	free(file->ungetbuf);
689 	free(file);
690 	file = prev;
691 	return (file ? 0 : EOF);
692 }
693 
694 struct dhcpleased_conf *
695 parse_config(char *filename)
696 {
697 	struct sym		*sym, *next;
698 
699 	conf = config_new_empty();
700 
701 	file = pushfile(filename != NULL ? filename : _PATH_CONF_FILE, 0);
702 	if (file == NULL) {
703 		/* no default config file is fine */
704 		if (errno == ENOENT && filename == NULL)
705 			return (conf);
706 		log_warn("%s", filename);
707 		free(conf);
708 		return (NULL);
709 	}
710 	topfile = file;
711 
712 	yyparse();
713 	errors = file->errors;
714 	popfile();
715 
716 	/* Free macros and check which have not been used. */
717 	TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
718 		if ((log_getverbose() == 2) && !sym->used)
719 			fprintf(stderr, "warning: macro '%s' not used\n",
720 			    sym->nam);
721 		if (!sym->persist) {
722 			free(sym->nam);
723 			free(sym->val);
724 			TAILQ_REMOVE(&symhead, sym, entry);
725 			free(sym);
726 		}
727 	}
728 
729 	if (errors) {
730 		config_clear(conf);
731 		return (NULL);
732 	}
733 
734 	return (conf);
735 }
736 
737 int
738 symset(const char *nam, const char *val, int persist)
739 {
740 	struct sym	*sym;
741 
742 	TAILQ_FOREACH(sym, &symhead, entry) {
743 		if (strcmp(nam, sym->nam) == 0)
744 			break;
745 	}
746 
747 	if (sym != NULL) {
748 		if (sym->persist == 1)
749 			return (0);
750 		else {
751 			free(sym->nam);
752 			free(sym->val);
753 			TAILQ_REMOVE(&symhead, sym, entry);
754 			free(sym);
755 		}
756 	}
757 	if ((sym = calloc(1, sizeof(*sym))) == NULL)
758 		return (-1);
759 
760 	sym->nam = strdup(nam);
761 	if (sym->nam == NULL) {
762 		free(sym);
763 		return (-1);
764 	}
765 	sym->val = strdup(val);
766 	if (sym->val == NULL) {
767 		free(sym->nam);
768 		free(sym);
769 		return (-1);
770 	}
771 	sym->used = 0;
772 	sym->persist = persist;
773 	TAILQ_INSERT_TAIL(&symhead, sym, entry);
774 	return (0);
775 }
776 
777 int
778 cmdline_symset(char *s)
779 {
780 	char	*sym, *val;
781 	int	ret;
782 
783 	if ((val = strrchr(s, '=')) == NULL)
784 		return (-1);
785 	sym = strndup(s, val - s);
786 	if (sym == NULL)
787 		errx(1, "%s: strndup", __func__);
788 	ret = symset(sym, val + 1, 1);
789 	free(sym);
790 
791 	return (ret);
792 }
793 
794 char *
795 symget(const char *nam)
796 {
797 	struct sym	*sym;
798 
799 	TAILQ_FOREACH(sym, &symhead, entry) {
800 		if (strcmp(nam, sym->nam) == 0) {
801 			sym->used = 1;
802 			return (sym->val);
803 		}
804 	}
805 	return (NULL);
806 }
807 
808 struct iface_conf *
809 conf_get_iface(char *name)
810 {
811 	struct iface_conf	*iface;
812 	size_t			 n;
813 
814 	SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) {
815 		if (strcmp(name, iface->name) == 0)
816 			return (iface);
817 	}
818 
819 	iface = calloc(1, sizeof(*iface));
820 	if (iface == NULL)
821 		errx(1, "%s: calloc", __func__);
822 	n = strlcpy(iface->name, name, sizeof(iface->name));
823 	if (n >= sizeof(iface->name))
824 		errx(1, "%s: name too long", __func__);
825 
826 
827 	SIMPLEQ_INSERT_TAIL(&conf->iface_list, iface, entry);
828 
829 	return (iface);
830 }
831