xref: /openbsd/sbin/dhcpleased/parse.y (revision 5b2dcb8f)
1 /*	$OpenBSD: parse.y,v 1.7 2022/03/21 04:35:41 dlg 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
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 		| optnl
176 		;
177 
178 ifaceopts_l	: ifaceopts_l ifaceoptsl nl
179 		| ifaceoptsl optnl
180 		;
181 
182 ifaceoptsl	: SEND VENDOR CLASS ID STRING {
183 			ssize_t len;
184 			char	buf[256];
185 
186 			if (iface_conf->vc_id != NULL) {
187 				yyerror("vendor class id already set");
188 				YYERROR;
189 			}
190 
191 			len = strnunvis(buf, $5, sizeof(buf));
192 			free($5);
193 
194 			if (len == -1) {
195 				yyerror("invalid vendor class id");
196 				YYERROR;
197 			}
198 			if ((size_t)len >= sizeof(buf)) {
199 				yyerror("vendor class id too long");
200 				YYERROR;
201 			}
202 
203 			iface_conf->vc_id_len = 2 + strlen(buf);
204 			iface_conf->vc_id = malloc(iface_conf->vc_id_len);
205 			if (iface_conf->vc_id == NULL) {
206 				yyerror("malloc");
207 				YYERROR;
208 			}
209 			iface_conf->vc_id[0] = DHO_DHCP_CLASS_IDENTIFIER;
210 			iface_conf->vc_id[1] = iface_conf->vc_id_len - 2;
211 			memcpy(&iface_conf->vc_id[2], buf,
212 			    iface_conf->vc_id_len - 2);
213 		}
214 		| SEND CLIENT ID STRING {
215 			size_t			 i;
216 			ssize_t			 len;
217 			int			 not_hex = 0, val;
218 			char			 buf[256], *hex, *p, excess;
219 
220 			if (iface_conf->c_id != NULL) {
221 				yyerror("client-id already set");
222 				YYERROR;
223 			}
224 
225 			/* parse as hex string including the type byte */
226 			if ((hex = strdup($4)) == NULL) {
227 				free($4);
228 				yyerror("malloc");
229 				YYERROR;
230 			}
231 			for (i = 0; (p = strsep(&hex, ":")) != NULL && i <
232 				 sizeof(buf); ) {
233 				if (sscanf(p, "%x%c", &val, &excess) != 1 ||
234 				    val < 0 || val > 0xff) {
235 					not_hex = 1;
236 					break;
237 				}
238 				buf[i++] = (val & 0xff);
239 			}
240 			if (p != NULL && i == sizeof(buf))
241 				not_hex = 1;
242 			free(hex);
243 
244 			if (not_hex) {
245 				len = strnunvis(buf, $4, sizeof(buf));
246 				free($4);
247 
248 				if (len == -1) {
249 					yyerror("invalid client-id");
250 					YYERROR;
251 				}
252 				if ((size_t)len >= sizeof(buf)) {
253 					yyerror("client-id too long");
254 					YYERROR;
255 				}
256 				iface_conf->c_id_len = 2 + len;
257 				iface_conf->c_id = malloc(iface_conf->c_id_len);
258 				if (iface_conf->c_id == NULL) {
259 					yyerror("malloc");
260 					YYERROR;
261 				}
262 				memcpy(&iface_conf->c_id[2], buf,
263 				    iface_conf->c_id_len - 2);
264 			} else {
265 				free($4);
266 				iface_conf->c_id_len = 2 + i;
267 				iface_conf->c_id = malloc(iface_conf->c_id_len);
268 				if (iface_conf->c_id == NULL) {
269 					yyerror("malloc");
270 					YYERROR;
271 				}
272 				memcpy(&iface_conf->c_id[2], buf,
273 				    iface_conf->c_id_len - 2);
274 			}
275 			iface_conf->c_id[0] = DHO_DHCP_CLIENT_IDENTIFIER;
276 			iface_conf->c_id[1] = iface_conf->c_id_len - 2;
277 		}
278 		| SEND HOST NAME STRING {
279 			if (iface_conf->h_name != NULL) {
280 				free($4);
281 				yyerror("host name already set");
282 				YYERROR;
283 			}
284 			if (strlen($4) > 255) {
285 				free($4);
286 				yyerror("host name too long");
287 				YYERROR;
288 			}
289 			iface_conf->h_name = $4;
290 		}
291 		| SEND NO HOST NAME {
292 			if (iface_conf->h_name != NULL) {
293 				yyerror("host name already set");
294 				YYERROR;
295 			}
296 
297 			if ((iface_conf->h_name = strdup("")) == NULL) {
298 				yyerror("malloc");
299 				YYERROR;
300 			}
301 		}
302 		| IGNORE ROUTES {
303 			iface_conf->ignore |= IGN_ROUTES;
304 		}
305 		| IGNORE DNS {
306 			iface_conf->ignore |= IGN_DNS;
307 		}
308 		| IGNORE STRING {
309 			int res;
310 
311 			if (iface_conf->ignore_servers_len >= MAX_SERVERS) {
312 				yyerror("too many servers to ignore");
313 				free($2);
314 				YYERROR;
315 			}
316 			res = inet_pton(AF_INET, $2,
317 			    &iface_conf->ignore_servers[
318 			    iface_conf->ignore_servers_len++]);
319 
320 			if (res != 1) {
321 				yyerror("Invalid server IP %s", $2);
322 				free($2);
323 				YYERROR;
324 			}
325 			free($2);
326 		}
327 		;
328 %%
329 
330 struct keywords {
331 	const char	*k_name;
332 	int		 k_val;
333 };
334 
335 int
336 yyerror(const char *fmt, ...)
337 {
338 	va_list		 ap;
339 	char		*msg;
340 
341 	file->errors++;
342 	va_start(ap, fmt);
343 	if (vasprintf(&msg, fmt, ap) == -1)
344 		fatalx("yyerror vasprintf");
345 	va_end(ap);
346 	logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
347 	free(msg);
348 	return (0);
349 }
350 
351 int
352 kw_cmp(const void *k, const void *e)
353 {
354 	return (strcmp(k, ((const struct keywords *)e)->k_name));
355 }
356 
357 int
358 lookup(char *s)
359 {
360 	/* This has to be sorted always. */
361 	static const struct keywords keywords[] = {
362 		{"class",		CLASS},
363 		{"client",		CLIENT},
364 		{"dns",			DNS},
365 		{"host",		HOST},
366 		{"id",			ID},
367 		{"ignore",		IGNORE},
368 		{"interface",		DHCP_IFACE},
369 		{"name",		NAME},
370 		{"no",			NO},
371 		{"routes",		ROUTES},
372 		{"send",		SEND},
373 		{"vendor",		VENDOR},
374 	};
375 	const struct keywords	*p;
376 
377 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
378 	    sizeof(keywords[0]), kw_cmp);
379 
380 	if (p)
381 		return (p->k_val);
382 	else
383 		return (STRING);
384 }
385 
386 #define START_EXPAND	1
387 #define DONE_EXPAND	2
388 
389 static int	expanding;
390 
391 int
392 igetc(void)
393 {
394 	int	c;
395 
396 	while (1) {
397 		if (file->ungetpos > 0)
398 			c = file->ungetbuf[--file->ungetpos];
399 		else
400 			c = getc(file->stream);
401 
402 		if (c == START_EXPAND)
403 			expanding = 1;
404 		else if (c == DONE_EXPAND)
405 			expanding = 0;
406 		else
407 			break;
408 	}
409 	return (c);
410 }
411 
412 int
413 lgetc(int quotec)
414 {
415 	int		c, next;
416 
417 	if (quotec) {
418 		if ((c = igetc()) == EOF) {
419 			yyerror("reached end of file while parsing "
420 			    "quoted string");
421 			if (file == topfile || popfile() == EOF)
422 				return (EOF);
423 			return (quotec);
424 		}
425 		return (c);
426 	}
427 
428 	while ((c = igetc()) == '\\') {
429 		next = igetc();
430 		if (next != '\n') {
431 			c = next;
432 			break;
433 		}
434 		yylval.lineno = file->lineno;
435 		file->lineno++;
436 	}
437 
438 	if (c == EOF) {
439 		/*
440 		 * Fake EOL when hit EOF for the first time. This gets line
441 		 * count right if last line in included file is syntactically
442 		 * invalid and has no newline.
443 		 */
444 		if (file->eof_reached == 0) {
445 			file->eof_reached = 1;
446 			return ('\n');
447 		}
448 		while (c == EOF) {
449 			if (file == topfile || popfile() == EOF)
450 				return (EOF);
451 			c = igetc();
452 		}
453 	}
454 	return (c);
455 }
456 
457 void
458 lungetc(int c)
459 {
460 	if (c == EOF)
461 		return;
462 
463 	if (file->ungetpos >= file->ungetsize) {
464 		void *p = reallocarray(file->ungetbuf, file->ungetsize, 2);
465 		if (p == NULL)
466 			err(1, "lungetc");
467 		file->ungetbuf = p;
468 		file->ungetsize *= 2;
469 	}
470 	file->ungetbuf[file->ungetpos++] = c;
471 }
472 
473 int
474 findeol(void)
475 {
476 	int	c;
477 
478 	/* Skip to either EOF or the first real EOL. */
479 	while (1) {
480 		c = lgetc(0);
481 		if (c == '\n') {
482 			file->lineno++;
483 			break;
484 		}
485 		if (c == EOF)
486 			break;
487 	}
488 	return (ERROR);
489 }
490 
491 int
492 yylex(void)
493 {
494 	char	 buf[8096];
495 	char	*p, *val;
496 	int	 quotec, next, c;
497 	int	 token;
498 
499 top:
500 	p = buf;
501 	while ((c = lgetc(0)) == ' ' || c == '\t')
502 		; /* nothing */
503 
504 	yylval.lineno = file->lineno;
505 	if (c == '#')
506 		while ((c = lgetc(0)) != '\n' && c != EOF)
507 			; /* nothing */
508 	if (c == '$' && !expanding) {
509 		while (1) {
510 			if ((c = lgetc(0)) == EOF)
511 				return (0);
512 
513 			if (p + 1 >= buf + sizeof(buf) - 1) {
514 				yyerror("string too long");
515 				return (findeol());
516 			}
517 			if (isalnum(c) || c == '_') {
518 				*p++ = c;
519 				continue;
520 			}
521 			*p = '\0';
522 			lungetc(c);
523 			break;
524 		}
525 		val = symget(buf);
526 		if (val == NULL) {
527 			yyerror("macro '%s' not defined", buf);
528 			return (findeol());
529 		}
530 		p = val + strlen(val) - 1;
531 		lungetc(DONE_EXPAND);
532 		while (p >= val) {
533 			lungetc((unsigned char)*p);
534 			p--;
535 		}
536 		lungetc(START_EXPAND);
537 		goto top;
538 	}
539 
540 	switch (c) {
541 	case '\'':
542 	case '"':
543 		quotec = c;
544 		while (1) {
545 			if ((c = lgetc(quotec)) == EOF)
546 				return (0);
547 			if (c == '\n') {
548 				file->lineno++;
549 				continue;
550 			} else if (c == '\\') {
551 				if ((next = lgetc(quotec)) == EOF)
552 					return (0);
553 				if (next == quotec || next == ' ' ||
554 				    next == '\t')
555 					c = next;
556 				else if (next == '\n') {
557 					file->lineno++;
558 					continue;
559 				} else
560 					lungetc(next);
561 			} else if (c == quotec) {
562 				*p = '\0';
563 				break;
564 			} else if (c == '\0') {
565 				yyerror("syntax error");
566 				return (findeol());
567 			}
568 			if (p + 1 >= buf + sizeof(buf) - 1) {
569 				yyerror("string too long");
570 				return (findeol());
571 			}
572 			*p++ = c;
573 		}
574 		yylval.v.string = strdup(buf);
575 		if (yylval.v.string == NULL)
576 			err(1, "yylex: strdup");
577 		return (STRING);
578 	}
579 
580 #define allowed_to_end_number(x) \
581 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
582 
583 	if (c == '-' || isdigit(c)) {
584 		do {
585 			*p++ = c;
586 			if ((size_t)(p-buf) >= sizeof(buf)) {
587 				yyerror("string too long");
588 				return (findeol());
589 			}
590 		} while ((c = lgetc(0)) != EOF && isdigit(c));
591 		lungetc(c);
592 		if (p == buf + 1 && buf[0] == '-')
593 			goto nodigits;
594 		if (c == EOF || allowed_to_end_number(c)) {
595 			const char *errstr = NULL;
596 
597 			*p = '\0';
598 			yylval.v.number = strtonum(buf, LLONG_MIN,
599 			    LLONG_MAX, &errstr);
600 			if (errstr) {
601 				yyerror("\"%s\" invalid number: %s",
602 				    buf, errstr);
603 				return (findeol());
604 			}
605 			return (NUMBER);
606 		} else {
607 nodigits:
608 			while (p > buf + 1)
609 				lungetc((unsigned char)*--p);
610 			c = (unsigned char)*--p;
611 			if (c == '-')
612 				return (c);
613 		}
614 	}
615 
616 #define allowed_in_string(x) \
617 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
618 	x != '{' && x != '}' && \
619 	x != '!' && x != '=' && x != '#' && \
620 	x != ','))
621 
622 	if (isalnum(c) || c == ':' || c == '_') {
623 		do {
624 			*p++ = c;
625 			if ((size_t)(p-buf) >= sizeof(buf)) {
626 				yyerror("string too long");
627 				return (findeol());
628 			}
629 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
630 		lungetc(c);
631 		*p = '\0';
632 		if ((token = lookup(buf)) == STRING)
633 			if ((yylval.v.string = strdup(buf)) == NULL)
634 				err(1, "yylex: strdup");
635 		return (token);
636 	}
637 	if (c == '\n') {
638 		yylval.lineno = file->lineno;
639 		file->lineno++;
640 	}
641 	if (c == EOF)
642 		return (0);
643 	return (c);
644 }
645 
646 int
647 check_file_secrecy(int fd, const char *fname)
648 {
649 	struct stat	st;
650 
651 	if (fstat(fd, &st)) {
652 		log_warn("cannot stat %s", fname);
653 		return (-1);
654 	}
655 	if (st.st_uid != 0 && st.st_uid != getuid()) {
656 		log_warnx("%s: owner not root or current user", fname);
657 		return (-1);
658 	}
659 	if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
660 		log_warnx("%s: group writable or world read/writable", fname);
661 		return (-1);
662 	}
663 	return (0);
664 }
665 
666 struct file *
667 pushfile(const char *name, int secret)
668 {
669 	struct file	*nfile;
670 
671 	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
672 		log_warn("calloc");
673 		return (NULL);
674 	}
675 	if ((nfile->name = strdup(name)) == NULL) {
676 		log_warn("strdup");
677 		free(nfile);
678 		return (NULL);
679 	}
680 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
681 		free(nfile->name);
682 		free(nfile);
683 		return (NULL);
684 	} else if (secret &&
685 	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
686 		fclose(nfile->stream);
687 		free(nfile->name);
688 		free(nfile);
689 		return (NULL);
690 	}
691 	nfile->lineno = TAILQ_EMPTY(&files) ? 1 : 0;
692 	nfile->ungetsize = 16;
693 	nfile->ungetbuf = malloc(nfile->ungetsize);
694 	if (nfile->ungetbuf == NULL) {
695 		log_warn("malloc");
696 		fclose(nfile->stream);
697 		free(nfile->name);
698 		free(nfile);
699 		return (NULL);
700 	}
701 	TAILQ_INSERT_TAIL(&files, nfile, entry);
702 	return (nfile);
703 }
704 
705 int
706 popfile(void)
707 {
708 	struct file	*prev;
709 
710 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
711 		prev->errors += file->errors;
712 
713 	TAILQ_REMOVE(&files, file, entry);
714 	fclose(file->stream);
715 	free(file->name);
716 	free(file->ungetbuf);
717 	free(file);
718 	file = prev;
719 	return (file ? 0 : EOF);
720 }
721 
722 struct dhcpleased_conf *
723 parse_config(const char *filename)
724 {
725 	extern const char	 default_conffile[];
726 	struct sym		*sym, *next;
727 
728 	conf = config_new_empty();
729 
730 	file = pushfile(filename, 0);
731 	if (file == NULL) {
732 		/* no default config file is fine */
733 		if (errno == ENOENT && filename == default_conffile)
734 			return (conf);
735 		log_warn("%s", filename);
736 		free(conf);
737 		return (NULL);
738 	}
739 	topfile = file;
740 
741 	yyparse();
742 	errors = file->errors;
743 	popfile();
744 
745 	/* Free macros and check which have not been used. */
746 	TAILQ_FOREACH_SAFE(sym, &symhead, entry, next) {
747 		if ((log_getverbose() == 2) && !sym->used)
748 			fprintf(stderr, "warning: macro '%s' not used\n",
749 			    sym->nam);
750 		if (!sym->persist) {
751 			free(sym->nam);
752 			free(sym->val);
753 			TAILQ_REMOVE(&symhead, sym, entry);
754 			free(sym);
755 		}
756 	}
757 
758 	if (errors) {
759 		config_clear(conf);
760 		return (NULL);
761 	}
762 
763 	return (conf);
764 }
765 
766 int
767 symset(const char *nam, const char *val, int persist)
768 {
769 	struct sym	*sym;
770 
771 	TAILQ_FOREACH(sym, &symhead, entry) {
772 		if (strcmp(nam, sym->nam) == 0)
773 			break;
774 	}
775 
776 	if (sym != NULL) {
777 		if (sym->persist == 1)
778 			return (0);
779 		else {
780 			free(sym->nam);
781 			free(sym->val);
782 			TAILQ_REMOVE(&symhead, sym, entry);
783 			free(sym);
784 		}
785 	}
786 	if ((sym = calloc(1, sizeof(*sym))) == NULL)
787 		return (-1);
788 
789 	sym->nam = strdup(nam);
790 	if (sym->nam == NULL) {
791 		free(sym);
792 		return (-1);
793 	}
794 	sym->val = strdup(val);
795 	if (sym->val == NULL) {
796 		free(sym->nam);
797 		free(sym);
798 		return (-1);
799 	}
800 	sym->used = 0;
801 	sym->persist = persist;
802 	TAILQ_INSERT_TAIL(&symhead, sym, entry);
803 	return (0);
804 }
805 
806 int
807 cmdline_symset(char *s)
808 {
809 	char	*sym, *val;
810 	int	ret;
811 
812 	if ((val = strrchr(s, '=')) == NULL)
813 		return (-1);
814 	sym = strndup(s, val - s);
815 	if (sym == NULL)
816 		errx(1, "%s: strndup", __func__);
817 	ret = symset(sym, val + 1, 1);
818 	free(sym);
819 
820 	return (ret);
821 }
822 
823 char *
824 symget(const char *nam)
825 {
826 	struct sym	*sym;
827 
828 	TAILQ_FOREACH(sym, &symhead, entry) {
829 		if (strcmp(nam, sym->nam) == 0) {
830 			sym->used = 1;
831 			return (sym->val);
832 		}
833 	}
834 	return (NULL);
835 }
836 
837 struct iface_conf *
838 conf_get_iface(char *name)
839 {
840 	struct iface_conf	*iface;
841 	size_t			 n;
842 
843 	SIMPLEQ_FOREACH(iface, &conf->iface_list, entry) {
844 		if (strcmp(name, iface->name) == 0)
845 			return (iface);
846 	}
847 
848 	iface = calloc(1, sizeof(*iface));
849 	if (iface == NULL)
850 		errx(1, "%s: calloc", __func__);
851 	n = strlcpy(iface->name, name, sizeof(iface->name));
852 	if (n >= sizeof(iface->name))
853 		errx(1, "%s: name too long", __func__);
854 
855 
856 	SIMPLEQ_INSERT_TAIL(&conf->iface_list, iface, entry);
857 
858 	return (iface);
859 }
860