xref: /openbsd-src/sbin/dhcpleased/parse.y (revision 726515c9a7507cbfc47afd021b423aa0769f0228)
1 /*	$OpenBSD: parse.y,v 1.10 2024/11/11 15:19:31 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 HOST NAME
112 %token	NO PREFER IPV6
113 
114 %token	<v.string>	STRING
115 %token	<v.number>	NUMBER
116 %type	<v.string>	string
117 
118 %%
119 
120 grammar		: /* empty */
121 		| grammar '\n'
122 		| grammar varset '\n'
123 		| grammar dhcp_iface '\n'
124 		| grammar error '\n'		{ file->errors++; }
125 		;
126 
127 string		: string STRING	{
128 			if (asprintf(&$$, "%s %s", $1, $2) == -1) {
129 				free($1);
130 				free($2);
131 				yyerror("string: asprintf");
132 				YYERROR;
133 			}
134 			free($1);
135 			free($2);
136 		}
137 		| STRING
138 		;
139 
140 varset		: STRING '=' string		{
141 			char *s = $1;
142 			if (log_getverbose() == 1)
143 				printf("%s = \"%s\"\n", $1, $3);
144 			while (*s++) {
145 				if (isspace((unsigned char)*s)) {
146 					yyerror("macro name cannot contain "
147 					    "whitespace");
148 					free($1);
149 					free($3);
150 					YYERROR;
151 				}
152 			}
153 			if (symset($1, $3, 0) == -1)
154 				fatal("cannot store variable");
155 			free($1);
156 			free($3);
157 		}
158 		;
159 
160 optnl		: '\n' optnl		/* zero or more newlines */
161 		| /*empty*/
162 		;
163 
164 nl		: '\n' optnl		/* one or more newlines */
165 		;
166 
167 dhcp_iface	: DHCP_IFACE STRING {
168 			iface_conf = conf_get_iface($2);
169 		} '{' iface_block '}' {
170 			iface_conf = NULL;
171 		}
172 		;
173 
174 iface_block	: optnl ifaceopts_l
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 		| SEND HOST NAME STRING {
278 			if (iface_conf->h_name != NULL) {
279 				free($4);
280 				yyerror("host name already set");
281 				YYERROR;
282 			}
283 			if (strlen($4) > 255) {
284 				free($4);
285 				yyerror("host name too long");
286 				YYERROR;
287 			}
288 			iface_conf->h_name = $4;
289 		}
290 		| SEND NO HOST NAME {
291 			if (iface_conf->h_name != NULL) {
292 				yyerror("host name already set");
293 				YYERROR;
294 			}
295 
296 			if ((iface_conf->h_name = strdup("")) == NULL) {
297 				yyerror("malloc");
298 				YYERROR;
299 			}
300 		}
301 		| IGNORE ROUTES {
302 			iface_conf->ignore |= IGN_ROUTES;
303 		}
304 		| IGNORE DNS {
305 			iface_conf->ignore |= IGN_DNS;
306 		}
307 		| IGNORE STRING {
308 			int res;
309 
310 			if (iface_conf->ignore_servers_len >= MAX_SERVERS) {
311 				yyerror("too many servers to ignore");
312 				free($2);
313 				YYERROR;
314 			}
315 			res = inet_pton(AF_INET, $2,
316 			    &iface_conf->ignore_servers[
317 			    iface_conf->ignore_servers_len++]);
318 
319 			if (res != 1) {
320 				yyerror("Invalid server IP %s", $2);
321 				free($2);
322 				YYERROR;
323 			}
324 			free($2);
325 		}
326 		| PREFER IPV6 {
327 			iface_conf->prefer_ipv6 = 1;
328 		}
329 		;
330 %%
331 
332 struct keywords {
333 	const char	*k_name;
334 	int		 k_val;
335 };
336 
337 int
338 yyerror(const char *fmt, ...)
339 {
340 	va_list		 ap;
341 	char		*msg;
342 
343 	file->errors++;
344 	va_start(ap, fmt);
345 	if (vasprintf(&msg, fmt, ap) == -1)
346 		fatalx("yyerror vasprintf");
347 	va_end(ap);
348 	logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
349 	free(msg);
350 	return (0);
351 }
352 
353 int
354 kw_cmp(const void *k, const void *e)
355 {
356 	return (strcmp(k, ((const struct keywords *)e)->k_name));
357 }
358 
359 int
360 lookup(char *s)
361 {
362 	/* This has to be sorted always. */
363 	static const struct keywords keywords[] = {
364 		{"class",		CLASS},
365 		{"client",		CLIENT},
366 		{"dns",			DNS},
367 		{"host",		HOST},
368 		{"id",			ID},
369 		{"ignore",		IGNORE},
370 		{"interface",		DHCP_IFACE},
371 		{"ipv6",		IPV6},
372 		{"name",		NAME},
373 		{"no",			NO},
374 		{"prefer",		PREFER},
375 		{"routes",		ROUTES},
376 		{"send",		SEND},
377 		{"vendor",		VENDOR},
378 	};
379 	const struct keywords	*p;
380 
381 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
382 	    sizeof(keywords[0]), kw_cmp);
383 
384 	if (p)
385 		return (p->k_val);
386 	else
387 		return (STRING);
388 }
389 
390 #define START_EXPAND	1
391 #define DONE_EXPAND	2
392 
393 static int	expanding;
394 
395 int
396 igetc(void)
397 {
398 	int	c;
399 
400 	while (1) {
401 		if (file->ungetpos > 0)
402 			c = file->ungetbuf[--file->ungetpos];
403 		else
404 			c = getc(file->stream);
405 
406 		if (c == START_EXPAND)
407 			expanding = 1;
408 		else if (c == DONE_EXPAND)
409 			expanding = 0;
410 		else
411 			break;
412 	}
413 	return (c);
414 }
415 
416 int
417 lgetc(int quotec)
418 {
419 	int		c, next;
420 
421 	if (quotec) {
422 		if ((c = igetc()) == EOF) {
423 			yyerror("reached end of file while parsing "
424 			    "quoted string");
425 			if (file == topfile || popfile() == EOF)
426 				return (EOF);
427 			return (quotec);
428 		}
429 		return (c);
430 	}
431 
432 	while ((c = igetc()) == '\\') {
433 		next = igetc();
434 		if (next != '\n') {
435 			c = next;
436 			break;
437 		}
438 		yylval.lineno = file->lineno;
439 		file->lineno++;
440 	}
441 
442 	if (c == EOF) {
443 		/*
444 		 * Fake EOL when hit EOF for the first time. This gets line
445 		 * count right if last line in included file is syntactically
446 		 * invalid and has no newline.
447 		 */
448 		if (file->eof_reached == 0) {
449 			file->eof_reached = 1;
450 			return ('\n');
451 		}
452 		while (c == EOF) {
453 			if (file == topfile || popfile() == EOF)
454 				return (EOF);
455 			c = igetc();
456 		}
457 	}
458 	return (c);
459 }
460 
461 void
462 lungetc(int c)
463 {
464 	if (c == EOF)
465 		return;
466 
467 	if (file->ungetpos >= file->ungetsize) {
468 		void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
469 		if (p == NULL)
470 			err(1, "lungetc");
471 		file->ungetbuf = p;
472 		file->ungetsize *= 2;
473 	}
474 	file->ungetbuf[file->ungetpos++] = c;
475 }
476 
477 int
478 findeol(void)
479 {
480 	int	c;
481 
482 	/* Skip to either EOF or the first real EOL. */
483 	while (1) {
484 		c = lgetc(0);
485 		if (c == '\n') {
486 			file->lineno++;
487 			break;
488 		}
489 		if (c == EOF)
490 			break;
491 	}
492 	return (ERROR);
493 }
494 
495 int
496 yylex(void)
497 {
498 	char	 buf[8096];
499 	char	*p, *val;
500 	int	 quotec, next, c;
501 	int	 token;
502 
503 top:
504 	p = buf;
505 	while ((c = lgetc(0)) == ' ' || c == '\t')
506 		; /* nothing */
507 
508 	yylval.lineno = file->lineno;
509 	if (c == '#')
510 		while ((c = lgetc(0)) != '\n' && c != EOF)
511 			; /* nothing */
512 	if (c == '$' && !expanding) {
513 		while (1) {
514 			if ((c = lgetc(0)) == EOF)
515 				return (0);
516 
517 			if (p + 1 >= buf + sizeof(buf) - 1) {
518 				yyerror("string too long");
519 				return (findeol());
520 			}
521 			if (isalnum(c) || c == '_') {
522 				*p++ = c;
523 				continue;
524 			}
525 			*p = '\0';
526 			lungetc(c);
527 			break;
528 		}
529 		val = symget(buf);
530 		if (val == NULL) {
531 			yyerror("macro '%s' not defined", buf);
532 			return (findeol());
533 		}
534 		p = val + strlen(val) - 1;
535 		lungetc(DONE_EXPAND);
536 		while (p >= val) {
537 			lungetc((unsigned char)*p);
538 			p--;
539 		}
540 		lungetc(START_EXPAND);
541 		goto top;
542 	}
543 
544 	switch (c) {
545 	case '\'':
546 	case '"':
547 		quotec = c;
548 		while (1) {
549 			if ((c = lgetc(quotec)) == EOF)
550 				return (0);
551 			if (c == '\n') {
552 				file->lineno++;
553 				continue;
554 			} else if (c == '\\') {
555 				if ((next = lgetc(quotec)) == EOF)
556 					return (0);
557 				if (next == quotec || next == ' ' ||
558 				    next == '\t')
559 					c = next;
560 				else if (next == '\n') {
561 					file->lineno++;
562 					continue;
563 				} else
564 					lungetc(next);
565 			} else if (c == quotec) {
566 				*p = '\0';
567 				break;
568 			} else if (c == '\0') {
569 				yyerror("syntax error");
570 				return (findeol());
571 			}
572 			if (p + 1 >= buf + sizeof(buf) - 1) {
573 				yyerror("string too long");
574 				return (findeol());
575 			}
576 			*p++ = c;
577 		}
578 		yylval.v.string = strdup(buf);
579 		if (yylval.v.string == NULL)
580 			err(1, "yylex: strdup");
581 		return (STRING);
582 	}
583 
584 #define allowed_to_end_number(x) \
585 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
586 
587 	if (c == '-' || isdigit(c)) {
588 		do {
589 			*p++ = c;
590 			if ((size_t)(p-buf) >= sizeof(buf)) {
591 				yyerror("string too long");
592 				return (findeol());
593 			}
594 		} while ((c = lgetc(0)) != EOF && isdigit(c));
595 		lungetc(c);
596 		if (p == buf + 1 && buf[0] == '-')
597 			goto nodigits;
598 		if (c == EOF || allowed_to_end_number(c)) {
599 			const char *errstr = NULL;
600 
601 			*p = '\0';
602 			yylval.v.number = strtonum(buf, LLONG_MIN,
603 			    LLONG_MAX, &errstr);
604 			if (errstr) {
605 				yyerror("\"%s\" invalid number: %s",
606 				    buf, errstr);
607 				return (findeol());
608 			}
609 			return (NUMBER);
610 		} else {
611 nodigits:
612 			while (p > buf + 1)
613 				lungetc((unsigned char)*--p);
614 			c = (unsigned char)*--p;
615 			if (c == '-')
616 				return (c);
617 		}
618 	}
619 
620 #define allowed_in_string(x) \
621 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
622 	x != '{' && x != '}' && \
623 	x != '!' && x != '=' && x != '#' && \
624 	x != ','))
625 
626 	if (isalnum(c) || c == ':' || c == '_') {
627 		do {
628 			*p++ = c;
629 			if ((size_t)(p-buf) >= sizeof(buf)) {
630 				yyerror("string too long");
631 				return (findeol());
632 			}
633 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
634 		lungetc(c);
635 		*p = '\0';
636 		if ((token = lookup(buf)) == STRING)
637 			if ((yylval.v.string = strdup(buf)) == NULL)
638 				err(1, "yylex: strdup");
639 		return (token);
640 	}
641 	if (c == '\n') {
642 		yylval.lineno = file->lineno;
643 		file->lineno++;
644 	}
645 	if (c == EOF)
646 		return (0);
647 	return (c);
648 }
649 
650 int
651 check_file_secrecy(int fd, const char *fname)
652 {
653 	struct stat	st;
654 
655 	if (fstat(fd, &st)) {
656 		log_warn("cannot stat %s", fname);
657 		return (-1);
658 	}
659 	if (st.st_uid != 0 && st.st_uid != getuid()) {
660 		log_warnx("%s: owner not root or current user", fname);
661 		return (-1);
662 	}
663 	if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
664 		log_warnx("%s: group writable or world read/writable", fname);
665 		return (-1);
666 	}
667 	return (0);
668 }
669 
670 struct file *
671 pushfile(const char *name, int secret)
672 {
673 	struct file	*nfile;
674 
675 	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
676 		log_warn("calloc");
677 		return (NULL);
678 	}
679 	if ((nfile->name = strdup(name)) == NULL) {
680 		log_warn("strdup");
681 		free(nfile);
682 		return (NULL);
683 	}
684 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
685 		free(nfile->name);
686 		free(nfile);
687 		return (NULL);
688 	} else if (secret &&
689 	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
690 		fclose(nfile->stream);
691 		free(nfile->name);
692 		free(nfile);
693 		return (NULL);
694 	}
695 	nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
696 	nfile->ungetsize = 16;
697 	nfile->ungetbuf = malloc(nfile->ungetsize);
698 	if (nfile->ungetbuf == NULL) {
699 		log_warn("malloc");
700 		fclose(nfile->stream);
701 		free(nfile->name);
702 		free(nfile);
703 		return (NULL);
704 	}
705 	TAILQ_INSERT_TAIL(&files, nfile, entry);
706 	return (nfile);
707 }
708 
709 int
710 popfile(void)
711 {
712 	struct file	*prev;
713 
714 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
715 		prev->errors += file->errors;
716 
717 	TAILQ_REMOVE(&files, file, entry);
718 	fclose(file->stream);
719 	free(file->name);
720 	free(file->ungetbuf);
721 	free(file);
722 	file = prev;
723 	return (file ? 0 : EOF);
724 }
725 
726 struct dhcpleased_conf *
727 parse_config(const char *filename)
728 {
729 	extern const char	 default_conffile[];
730 	struct sym		*sym, *next;
731 
732 	conf = config_new_empty();
733 
734 	file = pushfile(filename, 0);
735 	if (file == NULL) {
736 		/* no default config file is fine */
737 		if (errno == ENOENT && filename == default_conffile)
738 			return (conf);
739 		log_warn("%s", filename);
740 		free(conf);
741 		return (NULL);
742 	}
743 	topfile = file;
744 
745 	yyparse();
746 	errors = file->errors;
747 	popfile();
748 
749 	/* Free macros and check which have not been used. */
750 	TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
751 		if ((log_getverbose() == 2) && !sym->used)
752 			fprintf(stderr, "warning: macro '%s' not used\n",
753 			    sym->nam);
754 		if (!sym->persist) {
755 			free(sym->nam);
756 			free(sym->val);
757 			TAILQ_REMOVE(&symhead, sym, entry);
758 			free(sym);
759 		}
760 	}
761 
762 	if (errors) {
763 		config_clear(conf);
764 		return (NULL);
765 	}
766 
767 	return (conf);
768 }
769 
770 int
771 symset(const char *nam, const char *val, int persist)
772 {
773 	struct sym	*sym;
774 
775 	TAILQ_FOREACH(sym, &symhead, entry) {
776 		if (strcmp(nam, sym->nam) == 0)
777 			break;
778 	}
779 
780 	if (sym != NULL) {
781 		if (sym->persist == 1)
782 			return (0);
783 		else {
784 			free(sym->nam);
785 			free(sym->val);
786 			TAILQ_REMOVE(&symhead, sym, entry);
787 			free(sym);
788 		}
789 	}
790 	if ((sym = calloc(1, sizeof(*sym))) == NULL)
791 		return (-1);
792 
793 	sym->nam = strdup(nam);
794 	if (sym->nam == NULL) {
795 		free(sym);
796 		return (-1);
797 	}
798 	sym->val = strdup(val);
799 	if (sym->val == NULL) {
800 		free(sym->nam);
801 		free(sym);
802 		return (-1);
803 	}
804 	sym->used = 0;
805 	sym->persist = persist;
806 	TAILQ_INSERT_TAIL(&symhead, sym, entry);
807 	return (0);
808 }
809 
810 int
811 cmdline_symset(char *s)
812 {
813 	char	*sym, *val;
814 	int	ret;
815 
816 	if ((val = strrchr(s, '=')) == NULL)
817 		return (-1);
818 	sym = strndup(s, val - s);
819 	if (sym == NULL)
820 		errx(1, "%s: strndup", __func__);
821 	ret = symset(sym, val + 1, 1);
822 	free(sym);
823 
824 	return (ret);
825 }
826 
827 char *
828 symget(const char *nam)
829 {
830 	struct sym	*sym;
831 
832 	TAILQ_FOREACH(sym, &symhead, entry) {
833 		if (strcmp(nam, sym->nam) == 0) {
834 			sym->used = 1;
835 			return (sym->val);
836 		}
837 	}
838 	return (NULL);
839 }
840 
841 struct iface_conf *
842 conf_get_iface(char *name)
843 {
844 	struct iface_conf	*iface;
845 	size_t			 n;
846 
847 	SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) {
848 		if (strcmp(name, iface->name) == 0)
849 			return (iface);
850 	}
851 
852 	iface = calloc(1, sizeof(*iface));
853 	if (iface == NULL)
854 		errx(1, "%s: calloc", __func__);
855 	n = strlcpy(iface->name, name, sizeof(iface->name));
856 	if (n >= sizeof(iface->name))
857 		errx(1, "%s: name too long", __func__);
858 
859 
860 	SIMPLEQ_INSERT_TAIL(&conf->iface_list, iface, entry);
861 
862 	return (iface);
863 }
864