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