xref: /openbsd-src/usr.sbin/ifstated/parse.y (revision 0b7734b3d77bb9b21afec6f4621cae6c805dbd45)
1 /*	$OpenBSD: parse.y,v 1.38 2016/06/21 21:35:24 benno Exp $	*/
2 
3 /*
4  * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
5  * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
6  * Copyright (c) 2001 Markus Friedl.  All rights reserved.
7  * Copyright (c) 2001 Daniel Hartmeier.  All rights reserved.
8  * Copyright (c) 2001 Theo de Raadt.  All rights reserved.
9  *
10  * Permission to use, copy, modify, and distribute this software for any
11  * purpose with or without fee is hereby granted, provided that the above
12  * copyright notice and this permission notice appear in all copies.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
15  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
16  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
17  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
18  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
19  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
20  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
21  */
22 
23 %{
24 #include <sys/types.h>
25 #include <sys/time.h>
26 #include <sys/socket.h>
27 #include <sys/stat.h>
28 #include <netinet/in.h>
29 #include <arpa/inet.h>
30 #include <net/if.h>
31 
32 #include <ctype.h>
33 #include <unistd.h>
34 #include <err.h>
35 #include <errno.h>
36 #include <limits.h>
37 #include <stdarg.h>
38 #include <stdio.h>
39 #include <string.h>
40 #include <syslog.h>
41 #include <event.h>
42 
43 #include "ifstated.h"
44 
45 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
46 static struct file {
47 	TAILQ_ENTRY(file)	 entry;
48 	FILE			*stream;
49 	char			*name;
50 	int			 lineno;
51 	int			 errors;
52 } *file, *topfile;
53 struct file	*pushfile(const char *, int);
54 int		 popfile(void);
55 int		 check_file_secrecy(int, const char *);
56 int		 yyparse(void);
57 int		 yylex(void);
58 int		 yyerror(const char *, ...)
59     __attribute__((__format__ (printf, 1, 2)))
60     __attribute__((__nonnull__ (1)));
61 int		 kw_cmp(const void *, const void *);
62 int		 lookup(char *);
63 int		 lgetc(int);
64 int		 lungetc(int);
65 int		 findeol(void);
66 
67 TAILQ_HEAD(symhead, sym)	 symhead = TAILQ_HEAD_INITIALIZER(symhead);
68 struct sym {
69 	TAILQ_ENTRY(sym)	 entry;
70 	int			 used;
71 	int			 persist;
72 	char			*nam;
73 	char			*val;
74 };
75 int		 symset(const char *, const char *, int);
76 char		*symget(const char *);
77 
78 static struct ifsd_config	*conf;
79 char				*start_state;
80 
81 struct ifsd_action		*curaction;
82 struct ifsd_state		*curstate = NULL;
83 
84 void			 link_states(struct ifsd_action *);
85 void			 set_expression_depth(struct ifsd_expression *, int);
86 void			 init_state(struct ifsd_state *);
87 struct ifsd_ifstate	*new_ifstate(u_short, int);
88 struct ifsd_external	*new_external(char *, u_int32_t);
89 
90 typedef struct {
91 	union {
92 		int64_t		 number;
93 		char		*string;
94 		struct in_addr	 addr;
95 		u_short		 interface;
96 
97 		struct ifsd_expression	*expression;
98 		struct ifsd_ifstate	*ifstate;
99 		struct ifsd_external	*external;
100 
101 	} v;
102 	int lineno;
103 } YYSTYPE;
104 
105 %}
106 
107 %token	STATE INITSTATE
108 %token	LINK UP DOWN UNKNOWN ADDED REMOVED
109 %token	IF RUN SETSTATE EVERY INIT
110 %left	AND OR
111 %left	UNARY
112 %token	ERROR
113 %token	<v.string>	STRING
114 %token	<v.number>	NUMBER
115 %type	<v.string>	string
116 %type	<v.interface>	interface
117 %type	<v.ifstate>	if_test
118 %type	<v.external>	ext_test
119 %type	<v.expression>	expr term
120 %%
121 
122 grammar		: /* empty */
123 		| grammar '\n'
124 		| grammar conf_main '\n'
125 		| grammar varset '\n'
126 		| grammar action '\n'
127 		| grammar state '\n'
128 		| grammar error '\n'		{ file->errors++; }
129 		;
130 
131 string		: string STRING				{
132 			if (asprintf(&$$, "%s %s", $1, $2) == -1) {
133 				free($1);
134 				free($2);
135 				yyerror("string: asprintf");
136 				YYERROR;
137 			}
138 			free($1);
139 			free($2);
140 		}
141 		| STRING
142 		;
143 
144 varset		: STRING '=' string		{
145 			char *s = $1;
146 			if (conf->opts & IFSD_OPT_VERBOSE)
147 				printf("%s = \"%s\"\n", $1, $3);
148 			while (*s++) {
149 				if (isspace((unsigned char)*s)) {
150 					yyerror("macro name cannot contain "
151 					    "whitespace");
152 					YYERROR;
153 				}
154 			}
155 			if (symset($1, $3, 0) == -1) {
156 				free($1);
157 				free($3);
158 				yyerror("cannot store variable");
159 				YYERROR;
160 			}
161 			free($1);
162 			free($3);
163 		}
164 		;
165 
166 conf_main	: INITSTATE STRING		{
167 			start_state = $2;
168 		}
169 		;
170 
171 interface	: STRING		{
172 			if (($$ = if_nametoindex($1)) == 0) {
173 				yyerror("unknown interface %s", $1);
174 				free($1);
175 				YYERROR;
176 			}
177 			free($1);
178 		}
179 		;
180 
181 optnl		: '\n' optnl
182 		|
183 		;
184 
185 nl		: '\n' optnl		/* one newline or more */
186 		;
187 
188 action		: RUN STRING		{
189 			struct ifsd_action *action;
190 
191 			if ((action = calloc(1, sizeof(*action))) == NULL)
192 				err(1, "action: calloc");
193 			action->type = IFSD_ACTION_COMMAND;
194 			action->act.command = $2;
195 			if (action->act.command == NULL)
196 				err(1, "action: strdup");
197 			TAILQ_INSERT_TAIL(&curaction->act.c.actions,
198 			    action, entries);
199 		}
200 		| SETSTATE STRING	{
201 			struct ifsd_action *action;
202 
203 			if (curstate == NULL) {
204 				free($2);
205 				yyerror("set-state must be used inside 'if'");
206 				YYERROR;
207 			}
208 			if ((action = calloc(1, sizeof(*action))) == NULL)
209 				err(1, "action: calloc");
210 			action->type = IFSD_ACTION_CHANGESTATE;
211 			action->act.statename = $2;
212 			TAILQ_INSERT_TAIL(&curaction->act.c.actions,
213 			    action, entries);
214 		}
215 		| IF {
216 			struct ifsd_action *action;
217 
218 			if ((action = calloc(1, sizeof(*action))) == NULL)
219 				err(1, "action: calloc");
220 			action->type = IFSD_ACTION_CONDITION;
221 			TAILQ_INIT(&action->act.c.actions);
222 			TAILQ_INSERT_TAIL(&curaction->act.c.actions,
223 			    action, entries);
224 			action->parent = curaction;
225 			curaction = action;
226 		} expr action_block {
227 			set_expression_depth(curaction->act.c.expression, 0);
228 			curaction = curaction->parent;
229 		}
230 		;
231 
232 action_block	: optnl '{' optnl action_l '}'
233 		| optnl action
234 		;
235 
236 action_l	: action_l action nl
237 		| action nl
238 		;
239 
240 init		: INIT {
241 			if (curstate != NULL)
242 				curaction = curstate->init;
243 			else
244 				curaction = conf->always.init;
245 		} action_block {
246 			if (curstate != NULL)
247 				curaction = curstate->always;
248 			else
249 				curaction = conf->always.always;
250 		}
251 		;
252 
253 if_test		: interface '.' LINK '.' UP		{
254 			$$ = new_ifstate($1, IFSD_LINKUP);
255 		}
256 		| interface '.' LINK '.' DOWN		{
257 			$$ = new_ifstate($1, IFSD_LINKDOWN);
258 		}
259 		| interface '.' LINK '.' UNKNOWN	{
260 			$$ = new_ifstate($1, IFSD_LINKUNKNOWN);
261 		}
262 		;
263 
264 ext_test	: STRING EVERY NUMBER {
265 			if ($3 <= 0 || $3 > UINT_MAX) {
266 				yyerror("invalid interval: %lld", $3);
267 				free($1);
268 				YYERROR;
269 			}
270 			$$ = new_external($1, $3);
271 			free($1);
272 		}
273 		;
274 
275 term		: if_test {
276 			if (($$ = calloc(1, sizeof(*$$))) == NULL)
277 				err(1, NULL);
278 			curaction->act.c.expression = $$;
279 			$$->type = IFSD_OPER_IFSTATE;
280 			$$->u.ifstate = $1;
281 			TAILQ_INSERT_TAIL(&$1->expressions, $$, entries);
282 		}
283 		| ext_test {
284 			if (($$ = calloc(1, sizeof(*$$))) == NULL)
285 				err(1, NULL);
286 			curaction->act.c.expression = $$;
287 			$$->type = IFSD_OPER_EXTERNAL;
288 			$$->u.external = $1;
289 			TAILQ_INSERT_TAIL(&$1->expressions, $$, entries);
290 		}
291 		| '(' expr ')'			{
292 			$$ = $2;
293 		}
294 		;
295 
296 expr		: '!' expr %prec UNARY			{
297 			if (($$ = calloc(1, sizeof(*$$))) == NULL)
298 				err(1, NULL);
299 			curaction->act.c.expression = $$;
300 			$$->type = IFSD_OPER_NOT;
301 			$2->parent = $$;
302 			$$->right = $2;
303 		}
304 		| expr AND expr			{
305 			if (($$ = calloc(1, sizeof(*$$))) == NULL)
306 				err(1, NULL);
307 			curaction->act.c.expression = $$;
308 			$$->type = IFSD_OPER_AND;
309 			$1->parent = $$;
310 			$3->parent = $$;
311 			$$->left = $1;
312 			$$->right = $3;
313 		}
314 		| expr OR expr			{
315 			if (($$ = calloc(1, sizeof(*$$))) == NULL)
316 				err(1, NULL);
317 			curaction->act.c.expression = $$;
318 			$$->type = IFSD_OPER_OR;
319 			$1->parent = $$;
320 			$3->parent = $$;
321 			$$->left = $1;
322 			$$->right = $3;
323 		}
324 		| term
325 		;
326 
327 state		: STATE string {
328 			struct ifsd_state *state = NULL;
329 
330 			TAILQ_FOREACH(state, &conf->states, entries)
331 				if (!strcmp(state->name, $2)) {
332 					yyerror("state %s already exists", $2);
333 					free($2);
334 					YYERROR;
335 				}
336 			if ((state = calloc(1, sizeof(*curstate))) == NULL)
337 				err(1, NULL);
338 			init_state(state);
339 			state->name = $2;
340 			curstate = state;
341 			curaction = state->always;
342 		} optnl '{' optnl stateopts_l '}' {
343 			TAILQ_INSERT_TAIL(&conf->states, curstate, entries);
344 			curstate = NULL;
345 			curaction = conf->always.always;
346 		}
347 		;
348 
349 stateopts_l	: stateopts_l stateoptsl
350 		| stateoptsl
351 		;
352 
353 stateoptsl	: init nl
354 		| action nl
355 		;
356 
357 %%
358 
359 struct keywords {
360 	const char	*k_name;
361 	int		 k_val;
362 };
363 
364 int
365 yyerror(const char *fmt, ...)
366 {
367 	va_list		 ap;
368 	char		*msg;
369 
370 	file->errors++;
371 	va_start(ap, fmt);
372 	if (vasprintf(&msg, fmt, ap) == -1)
373 		fatalx("yyerror vasprintf");
374 	va_end(ap);
375 	logit(LOG_CRIT, "%s:%d: %s", file->name, yylval.lineno, msg);
376 	free(msg);
377 	return (0);
378 }
379 
380 int
381 kw_cmp(const void *k, const void *e)
382 {
383 	return (strcmp(k, ((const struct keywords *)e)->k_name));
384 }
385 
386 int
387 lookup(char *s)
388 {
389 	/* this has to be sorted always */
390 	static const struct keywords keywords[] = {
391 		{ "&&",			AND},
392 		{ "added",		ADDED},
393 		{ "down",		DOWN},
394 		{ "every",		EVERY},
395 		{ "if",			IF},
396 		{ "init",		INIT},
397 		{ "init-state",		INITSTATE},
398 		{ "link",		LINK},
399 		{ "removed",		REMOVED},
400 		{ "run",		RUN},
401 		{ "set-state",		SETSTATE},
402 		{ "state",		STATE},
403 		{ "unknown",		UNKNOWN},
404 		{ "up",			UP},
405 		{ "||",			OR}
406 	};
407 	const struct keywords	*p;
408 
409 	p = bsearch(s, keywords, sizeof(keywords)/sizeof(keywords[0]),
410 	    sizeof(keywords[0]), kw_cmp);
411 
412 	if (p)
413 		return (p->k_val);
414 	else
415 		return (STRING);
416 }
417 
418 #define MAXPUSHBACK	128
419 
420 u_char	*parsebuf;
421 int	 parseindex;
422 u_char	 pushback_buffer[MAXPUSHBACK];
423 int	 pushback_index = 0;
424 
425 int
426 lgetc(int quotec)
427 {
428 	int		c, next;
429 
430 	if (parsebuf) {
431 		/* Read character from the parsebuffer instead of input. */
432 		if (parseindex >= 0) {
433 			c = parsebuf[parseindex++];
434 			if (c != '\0')
435 				return (c);
436 			parsebuf = NULL;
437 		} else
438 			parseindex++;
439 	}
440 
441 	if (pushback_index)
442 		return (pushback_buffer[--pushback_index]);
443 
444 	if (quotec) {
445 		if ((c = getc(file->stream)) == EOF) {
446 			yyerror("reached end of file while parsing "
447 			    "quoted string");
448 			if (file == topfile || popfile() == EOF)
449 				return (EOF);
450 			return (quotec);
451 		}
452 		return (c);
453 	}
454 
455 	while ((c = getc(file->stream)) == '\\') {
456 		next = getc(file->stream);
457 		if (next != '\n') {
458 			c = next;
459 			break;
460 		}
461 		yylval.lineno = file->lineno;
462 		file->lineno++;
463 	}
464 
465 	while (c == EOF) {
466 		if (file == topfile || popfile() == EOF)
467 			return (EOF);
468 		c = getc(file->stream);
469 	}
470 	return (c);
471 }
472 
473 int
474 lungetc(int c)
475 {
476 	if (c == EOF)
477 		return (EOF);
478 	if (parsebuf) {
479 		parseindex--;
480 		if (parseindex >= 0)
481 			return (c);
482 	}
483 	if (pushback_index < MAXPUSHBACK-1)
484 		return (pushback_buffer[pushback_index++] = c);
485 	else
486 		return (EOF);
487 }
488 
489 int
490 findeol(void)
491 {
492 	int	c;
493 
494 	parsebuf = NULL;
495 
496 	/* skip to either EOF or the first real EOL */
497 	while (1) {
498 		if (pushback_index)
499 			c = pushback_buffer[--pushback_index];
500 		else
501 			c = lgetc(0);
502 		if (c == '\n') {
503 			file->lineno++;
504 			break;
505 		}
506 		if (c == EOF)
507 			break;
508 	}
509 	return (ERROR);
510 }
511 
512 int
513 yylex(void)
514 {
515 	u_char	 buf[8096];
516 	u_char	*p, *val;
517 	int	 quotec, next, c;
518 	int	 token;
519 
520 top:
521 	p = buf;
522 	while ((c = lgetc(0)) == ' ' || c == '\t')
523 		; /* nothing */
524 
525 	yylval.lineno = file->lineno;
526 	if (c == '#')
527 		while ((c = lgetc(0)) != '\n' && c != EOF)
528 			; /* nothing */
529 	if (c == '$' && parsebuf == NULL) {
530 		while (1) {
531 			if ((c = lgetc(0)) == EOF)
532 				return (0);
533 
534 			if (p + 1 >= buf + sizeof(buf) - 1) {
535 				yyerror("string too long");
536 				return (findeol());
537 			}
538 			if (isalnum(c) || c == '_') {
539 				*p++ = c;
540 				continue;
541 			}
542 			*p = '\0';
543 			lungetc(c);
544 			break;
545 		}
546 		val = symget(buf);
547 		if (val == NULL) {
548 			yyerror("macro '%s' not defined", buf);
549 			return (findeol());
550 		}
551 		parsebuf = val;
552 		parseindex = 0;
553 		goto top;
554 	}
555 
556 	switch (c) {
557 	case '\'':
558 	case '"':
559 		quotec = c;
560 		while (1) {
561 			if ((c = lgetc(quotec)) == EOF)
562 				return (0);
563 			if (c == '\n') {
564 				file->lineno++;
565 				continue;
566 			} else if (c == '\\') {
567 				if ((next = lgetc(quotec)) == EOF)
568 					return (0);
569 				if (next == quotec || c == ' ' || c == '\t')
570 					c = next;
571 				else if (next == '\n') {
572 					file->lineno++;
573 					continue;
574 				} else
575 					lungetc(next);
576 			} else if (c == quotec) {
577 				*p = '\0';
578 				break;
579 			} else if (c == '\0') {
580 				yyerror("syntax error");
581 				return (findeol());
582 			}
583 			if (p + 1 >= buf + sizeof(buf) - 1) {
584 				yyerror("string too long");
585 				return (findeol());
586 			}
587 			*p++ = c;
588 		}
589 		yylval.v.string = strdup(buf);
590 		if (yylval.v.string == NULL)
591 			err(1, "yylex: strdup");
592 		return (STRING);
593 	}
594 
595 #define allowed_to_end_number(x) \
596 	(isspace(x) || x == ')' || x ==',' || x == '/' || x == '}' || x == '=')
597 
598 	if (c == '-' || isdigit(c)) {
599 		do {
600 			*p++ = c;
601 			if ((unsigned)(p-buf) >= sizeof(buf)) {
602 				yyerror("string too long");
603 				return (findeol());
604 			}
605 		} while ((c = lgetc(0)) != EOF && isdigit(c));
606 		lungetc(c);
607 		if (p == buf + 1 && buf[0] == '-')
608 			goto nodigits;
609 		if (c == EOF || allowed_to_end_number(c)) {
610 			const char *errstr = NULL;
611 
612 			*p = '\0';
613 			yylval.v.number = strtonum(buf, LLONG_MIN,
614 			    LLONG_MAX, &errstr);
615 			if (errstr) {
616 				yyerror("\"%s\" invalid number: %s",
617 				    buf, errstr);
618 				return (findeol());
619 			}
620 			return (NUMBER);
621 		} else {
622 nodigits:
623 			while (p > buf + 1)
624 				lungetc(*--p);
625 			c = *--p;
626 			if (c == '-')
627 				return (c);
628 		}
629 	}
630 
631 #define allowed_in_string(x) \
632 	(isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \
633 	x != '{' && x != '}' && \
634 	x != '!' && x != '=' && x != '#' && \
635 	x != ',' && x != '.'))
636 
637 	if (isalnum(c) || c == ':' || c == '_' || c == '&' || c == '|') {
638 		do {
639 			*p++ = c;
640 			if ((unsigned)(p-buf) >= sizeof(buf)) {
641 				yyerror("string too long");
642 				return (findeol());
643 			}
644 		} while ((c = lgetc(0)) != EOF && (allowed_in_string(c)));
645 		lungetc(c);
646 		*p = '\0';
647 		if ((token = lookup(buf)) == STRING)
648 			if ((yylval.v.string = strdup(buf)) == NULL)
649 				err(1, "yylex: strdup");
650 		return (token);
651 	}
652 	if (c == '\n') {
653 		yylval.lineno = file->lineno;
654 		file->lineno++;
655 	}
656 	if (c == EOF)
657 		return (0);
658 	return (c);
659 }
660 
661 int
662 check_file_secrecy(int fd, const char *fname)
663 {
664 	struct stat	st;
665 
666 	if (fstat(fd, &st)) {
667 		warn("cannot stat %s", fname);
668 		return (-1);
669 	}
670 	if (st.st_uid != 0 && st.st_uid != getuid()) {
671 		warnx("%s: owner not root or current user", fname);
672 		return (-1);
673 	}
674 	if (st.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)) {
675 		warnx("%s: group writable or world read/writable", fname);
676 		return (-1);
677 	}
678 	return (0);
679 }
680 
681 struct file *
682 pushfile(const char *name, int secret)
683 {
684 	struct file	*nfile;
685 
686 	if ((nfile = calloc(1, sizeof(struct file))) == NULL) {
687 		warn("malloc");
688 		return (NULL);
689 	}
690 	if ((nfile->name = strdup(name)) == NULL) {
691 		warn("malloc");
692 		free(nfile);
693 		return (NULL);
694 	}
695 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
696 		warn("%s", nfile->name);
697 		free(nfile->name);
698 		free(nfile);
699 		return (NULL);
700 	} else if (secret &&
701 	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
702 		fclose(nfile->stream);
703 		free(nfile->name);
704 		free(nfile);
705 		return (NULL);
706 	}
707 	nfile->lineno = 1;
708 	TAILQ_INSERT_TAIL(&files, nfile, entry);
709 	return (nfile);
710 }
711 
712 int
713 popfile(void)
714 {
715 	struct file	*prev;
716 
717 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
718 		prev->errors += file->errors;
719 
720 	TAILQ_REMOVE(&files, file, entry);
721 	fclose(file->stream);
722 	free(file->name);
723 	free(file);
724 	file = prev;
725 	return (file ? 0 : EOF);
726 }
727 
728 struct ifsd_config *
729 parse_config(char *filename, int opts)
730 {
731 	int		 errors = 0;
732 	struct sym	*sym, *next;
733 	struct ifsd_state *state;
734 
735 	if ((conf = calloc(1, sizeof(struct ifsd_config))) == NULL) {
736 		err(1, NULL);
737 		return (NULL);
738 	}
739 
740 	if ((file = pushfile(filename, 0)) == NULL) {
741 		free(conf);
742 		return (NULL);
743 	}
744 	topfile = file;
745 
746 	TAILQ_INIT(&conf->states);
747 
748 	init_state(&conf->always);
749 	curaction = conf->always.always;
750 	conf->opts = opts;
751 
752 	yyparse();
753 
754 	/* Link states */
755 	TAILQ_FOREACH(state, &conf->states, entries) {
756 		link_states(state->init);
757 		link_states(state->always);
758 	}
759 
760 	errors = file->errors;
761 	popfile();
762 
763 	if (start_state != NULL) {
764 		TAILQ_FOREACH(state, &conf->states, entries) {
765 			if (strcmp(start_state, state->name) == 0) {
766 				conf->curstate = state;
767 				break;
768 			}
769 		}
770 		if (conf->curstate == NULL)
771 			errx(1, "invalid start state %s", start_state);
772 	} else {
773 		conf->curstate = TAILQ_FIRST(&conf->states);
774 	}
775 
776 	/* Free macros and check which have not been used. */
777 	for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
778 		next = TAILQ_NEXT(sym, entry);
779 		if ((conf->opts & IFSD_OPT_VERBOSE2) && !sym->used)
780 			fprintf(stderr, "warning: macro '%s' not "
781 			    "used\n", sym->nam);
782 		if (!sym->persist) {
783 			free(sym->nam);
784 			free(sym->val);
785 			TAILQ_REMOVE(&symhead, sym, entry);
786 			free(sym);
787 		}
788 	}
789 
790 	if (errors) {
791 		clear_config(conf);
792 		errors = 0;
793 		return (NULL);
794 	}
795 
796 	return (conf);
797 }
798 
799 void
800 link_states(struct ifsd_action *action)
801 {
802 	struct ifsd_action *subaction;
803 
804 	switch (action->type) {
805 	default:
806 	case IFSD_ACTION_COMMAND:
807 		break;
808 	case IFSD_ACTION_CHANGESTATE: {
809 		struct ifsd_state *state;
810 
811 		TAILQ_FOREACH(state, &conf->states, entries) {
812 			if (strcmp(action->act.statename,
813 			    state->name) == 0) {
814 				action->act.nextstate = state;
815 				break;
816 			}
817 		}
818 		if (state == NULL) {
819 			fprintf(stderr, "error: state '%s' not declared\n",
820 			    action->act.statename);
821 			file->errors++;
822 		}
823 		break;
824 	}
825 	case IFSD_ACTION_CONDITION:
826 		TAILQ_FOREACH(subaction, &action->act.c.actions, entries)
827 			link_states(subaction);
828 		break;
829 	}
830 }
831 
832 int
833 symset(const char *nam, const char *val, int persist)
834 {
835 	struct sym	*sym;
836 
837 	for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
838 	    sym = TAILQ_NEXT(sym, entry))
839 		;	/* nothing */
840 
841 	if (sym != NULL) {
842 		if (sym->persist == 1)
843 			return (0);
844 		else {
845 			free(sym->nam);
846 			free(sym->val);
847 			TAILQ_REMOVE(&symhead, sym, entry);
848 			free(sym);
849 		}
850 	}
851 	if ((sym = calloc(1, sizeof(*sym))) == NULL)
852 		return (-1);
853 
854 	sym->nam = strdup(nam);
855 	if (sym->nam == NULL) {
856 		free(sym);
857 		return (-1);
858 	}
859 	sym->val = strdup(val);
860 	if (sym->val == NULL) {
861 		free(sym->nam);
862 		free(sym);
863 		return (-1);
864 	}
865 	sym->used = 0;
866 	sym->persist = persist;
867 	TAILQ_INSERT_TAIL(&symhead, sym, entry);
868 	return (0);
869 }
870 
871 int
872 cmdline_symset(char *s)
873 {
874 	char	*sym, *val;
875 	int	ret;
876 	size_t	len;
877 
878 	if ((val = strrchr(s, '=')) == NULL)
879 		return (-1);
880 
881 	len = strlen(s) - strlen(val) + 1;
882 	if ((sym = malloc(len)) == NULL)
883 		err(1, NULL);
884 
885 	strlcpy(sym, s, len);
886 
887 	ret = symset(sym, val + 1, 1);
888 	free(sym);
889 
890 	return (ret);
891 }
892 
893 char *
894 symget(const char *nam)
895 {
896 	struct sym	*sym;
897 
898 	TAILQ_FOREACH(sym, &symhead, entry)
899 		if (strcmp(nam, sym->nam) == 0) {
900 			sym->used = 1;
901 			return (sym->val);
902 		}
903 	return (NULL);
904 }
905 
906 void
907 set_expression_depth(struct ifsd_expression *expression, int depth)
908 {
909 	expression->depth = depth;
910 	if (conf->maxdepth < depth)
911 		conf->maxdepth = depth;
912 	if (expression->left != NULL)
913 		set_expression_depth(expression->left, depth + 1);
914 	if (expression->right != NULL)
915 		set_expression_depth(expression->right, depth + 1);
916 }
917 
918 void
919 init_state(struct ifsd_state *state)
920 {
921 	TAILQ_INIT(&state->interface_states);
922 	TAILQ_INIT(&state->external_tests);
923 
924 	if ((state->init = calloc(1, sizeof(*state->init))) == NULL)
925 		err(1, "init_state: calloc");
926 	state->init->type = IFSD_ACTION_CONDITION;
927 	TAILQ_INIT(&state->init->act.c.actions);
928 
929 	if ((state->always = calloc(1, sizeof(*state->always))) == NULL)
930 		err(1, "init_state: calloc");
931 	state->always->type = IFSD_ACTION_CONDITION;
932 	TAILQ_INIT(&state->always->act.c.actions);
933 }
934 
935 struct ifsd_ifstate *
936 new_ifstate(u_short ifindex, int s)
937 {
938 	struct ifsd_ifstate *ifstate = NULL;
939 	struct ifsd_state *state;
940 
941 	if (curstate != NULL)
942 		state = curstate;
943 	else
944 		state = &conf->always;
945 
946 	TAILQ_FOREACH(ifstate, &state->interface_states, entries)
947 		if (ifstate->ifindex == ifindex && ifstate->ifstate == s)
948 			break;
949 	if (ifstate == NULL) {
950 		if ((ifstate = calloc(1, sizeof(*ifstate))) == NULL)
951 			err(1, NULL);
952 		ifstate->ifindex = ifindex;
953 		ifstate->ifstate = s;
954 		TAILQ_INIT(&ifstate->expressions);
955 		TAILQ_INSERT_TAIL(&state->interface_states, ifstate, entries);
956 	}
957 	ifstate->prevstate = -1;
958 	ifstate->refcount++;
959 	return (ifstate);
960 }
961 
962 struct ifsd_external *
963 new_external(char *command, u_int32_t frequency)
964 {
965 	struct ifsd_external *external = NULL;
966 	struct ifsd_state *state;
967 
968 	if (curstate != NULL)
969 		state = curstate;
970 	else
971 		state = &conf->always;
972 
973 	TAILQ_FOREACH(external, &state->external_tests, entries)
974 		if (strcmp(external->command, command) == 0 &&
975 		    external->frequency == frequency)
976 			break;
977 	if (external == NULL) {
978 		if ((external = calloc(1, sizeof(*external))) == NULL)
979 			err(1, NULL);
980 		if ((external->command = strdup(command)) == NULL)
981 			err(1, NULL);
982 		external->frequency = frequency;
983 		TAILQ_INIT(&external->expressions);
984 		TAILQ_INSERT_TAIL(&state->external_tests, external, entries);
985 	}
986 	external->prevstatus = -1;
987 	external->refcount++;
988 	return (external);
989 }
990