xref: /openbsd/usr.sbin/ifstated/parse.y (revision 8932bfb7)
1 /*	$OpenBSD: parse.y,v 1.30 2010/08/03 18:42:40 henning 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 					file->lineno++;
561 					continue;
562 				} else
563 					lungetc(next);
564 			} else if (c == quotec) {
565 				*p = '\0';
566 				break;
567 			}
568 			if (p + 1 >= buf + sizeof(buf) - 1) {
569 				yyerror("string too long");
570 				return (findeol());
571 			}
572 			*p++ = (char)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 ((unsigned)(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(*--p);
610 			c = *--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 != ',' && x != '.'))
621 
622 	if (isalnum(c) || c == ':' || c == '_' || c == '&' || c == '|') {
623 		do {
624 			*p++ = c;
625 			if ((unsigned)(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 		warn("cannot stat %s", fname);
653 		return (-1);
654 	}
655 	if (st.st_uid != 0 && st.st_uid != getuid()) {
656 		warnx("%s: owner not root or current user", fname);
657 		return (-1);
658 	}
659 	if (st.st_mode & (S_IRWXG | S_IRWXO)) {
660 		warnx("%s: group/world readable/writeable", 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 		warn("malloc");
673 		return (NULL);
674 	}
675 	if ((nfile->name = strdup(name)) == NULL) {
676 		warn("malloc");
677 		free(nfile);
678 		return (NULL);
679 	}
680 	if ((nfile->stream = fopen(nfile->name, "r")) == NULL) {
681 		warnx("%s", nfile->name);
682 		free(nfile->name);
683 		free(nfile);
684 		return (NULL);
685 	} else if (secret &&
686 	    check_file_secrecy(fileno(nfile->stream), nfile->name)) {
687 		fclose(nfile->stream);
688 		free(nfile->name);
689 		free(nfile);
690 		return (NULL);
691 	}
692 	nfile->lineno = 1;
693 	TAILQ_INSERT_TAIL(&files, nfile, entry);
694 	return (nfile);
695 }
696 
697 int
698 popfile(void)
699 {
700 	struct file	*prev;
701 
702 	if ((prev = TAILQ_PREV(file, files, entry)) != NULL)
703 		prev->errors += file->errors;
704 
705 	TAILQ_REMOVE(&files, file, entry);
706 	fclose(file->stream);
707 	free(file->name);
708 	free(file);
709 	file = prev;
710 	return (file ? 0 : EOF);
711 }
712 
713 struct ifsd_config *
714 parse_config(char *filename, int opts)
715 {
716 	int		 errors = 0;
717 	struct sym	*sym, *next;
718 	struct ifsd_state *state;
719 
720 	if ((conf = calloc(1, sizeof(struct ifsd_config))) == NULL) {
721 		errx(1, "parse_config calloc");
722 		return (NULL);
723 	}
724 
725 	if ((file = pushfile(filename, 0)) == NULL) {
726 		free(conf);
727 		return (NULL);
728 	}
729 	topfile = file;
730 
731 	TAILQ_INIT(&conf->states);
732 
733 	init_state(&conf->always);
734 	curaction = conf->always.always;
735 	conf->opts = opts;
736 
737 	yyparse();
738 
739 	/* Link states */
740 	TAILQ_FOREACH(state, &conf->states, entries) {
741 		link_states(state->init);
742 		link_states(state->always);
743 	}
744 
745 	errors = file->errors;
746 	popfile();
747 
748 	if (start_state != NULL) {
749 		TAILQ_FOREACH(state, &conf->states, entries) {
750 			if (strcmp(start_state, state->name) == 0) {
751 				conf->curstate = state;
752 				break;
753 			}
754 		}
755 		if (conf->curstate == NULL)
756 			errx(1, "invalid start state %s", start_state);
757 	} else {
758 		conf->curstate = TAILQ_FIRST(&conf->states);
759 	}
760 
761 	/* Free macros and check which have not been used. */
762 	for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) {
763 		next = TAILQ_NEXT(sym, entry);
764 		if ((conf->opts & IFSD_OPT_VERBOSE2) && !sym->used)
765 			fprintf(stderr, "warning: macro '%s' not "
766 			    "used\n", sym->nam);
767 		if (!sym->persist) {
768 			free(sym->nam);
769 			free(sym->val);
770 			TAILQ_REMOVE(&symhead, sym, entry);
771 			free(sym);
772 		}
773 	}
774 
775 	if (errors) {
776 		clear_config(conf);
777 		errors = 0;
778 		return (NULL);
779 	}
780 
781 	return (conf);
782 }
783 
784 void
785 link_states(struct ifsd_action *action)
786 {
787 	struct ifsd_action *subaction;
788 
789 	switch (action->type) {
790 	default:
791 	case IFSD_ACTION_COMMAND:
792 		break;
793 	case IFSD_ACTION_CHANGESTATE: {
794 		struct ifsd_state *state;
795 
796 		TAILQ_FOREACH(state, &conf->states, entries) {
797 			if (strcmp(action->act.statename,
798 			    state->name) == 0) {
799 				action->act.nextstate = state;
800 				break;
801 			}
802 		}
803 		if (state == NULL) {
804 			fprintf(stderr, "error: state '%s' not declared\n",
805 			    action->act.statename);
806 			file->errors++;
807 		}
808 		break;
809 	}
810 	case IFSD_ACTION_CONDITION:
811 		TAILQ_FOREACH(subaction, &action->act.c.actions, entries)
812 			link_states(subaction);
813 		break;
814 	}
815 }
816 
817 int
818 symset(const char *nam, const char *val, int persist)
819 {
820 	struct sym	*sym;
821 
822 	for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam);
823 	    sym = TAILQ_NEXT(sym, entry))
824 		;	/* nothing */
825 
826 	if (sym != NULL) {
827 		if (sym->persist == 1)
828 			return (0);
829 		else {
830 			free(sym->nam);
831 			free(sym->val);
832 			TAILQ_REMOVE(&symhead, sym, entry);
833 			free(sym);
834 		}
835 	}
836 	if ((sym = calloc(1, sizeof(*sym))) == NULL)
837 		return (-1);
838 
839 	sym->nam = strdup(nam);
840 	if (sym->nam == NULL) {
841 		free(sym);
842 		return (-1);
843 	}
844 	sym->val = strdup(val);
845 	if (sym->val == NULL) {
846 		free(sym->nam);
847 		free(sym);
848 		return (-1);
849 	}
850 	sym->used = 0;
851 	sym->persist = persist;
852 	TAILQ_INSERT_TAIL(&symhead, sym, entry);
853 	return (0);
854 }
855 
856 int
857 cmdline_symset(char *s)
858 {
859 	char	*sym, *val;
860 	int	ret;
861 	size_t	len;
862 
863 	if ((val = strrchr(s, '=')) == NULL)
864 		return (-1);
865 
866 	len = strlen(s) - strlen(val) + 1;
867 	if ((sym = malloc(len)) == NULL)
868 		errx(1, "cmdline_symset: malloc");
869 
870 	strlcpy(sym, s, len);
871 
872 	ret = symset(sym, val + 1, 1);
873 	free(sym);
874 
875 	return (ret);
876 }
877 
878 char *
879 symget(const char *nam)
880 {
881 	struct sym	*sym;
882 
883 	TAILQ_FOREACH(sym, &symhead, entry)
884 		if (strcmp(nam, sym->nam) == 0) {
885 			sym->used = 1;
886 			return (sym->val);
887 		}
888 	return (NULL);
889 }
890 
891 void
892 set_expression_depth(struct ifsd_expression *expression, int depth)
893 {
894 	expression->depth = depth;
895 	if (conf->maxdepth < depth)
896 		conf->maxdepth = depth;
897 	if (expression->left != NULL)
898 		set_expression_depth(expression->left, depth + 1);
899 	if (expression->right != NULL)
900 		set_expression_depth(expression->right, depth + 1);
901 }
902 
903 void
904 init_state(struct ifsd_state *state)
905 {
906 	TAILQ_INIT(&state->interface_states);
907 	TAILQ_INIT(&state->external_tests);
908 
909 	if ((state->init = calloc(1, sizeof(*state->init))) == NULL)
910 		err(1, "init_state: calloc");
911 	state->init->type = IFSD_ACTION_CONDITION;
912 	TAILQ_INIT(&state->init->act.c.actions);
913 
914 	if ((state->always = calloc(1, sizeof(*state->always))) == NULL)
915 		err(1, "init_state: calloc");
916 	state->always->type = IFSD_ACTION_CONDITION;
917 	TAILQ_INIT(&state->always->act.c.actions);
918 }
919 
920 struct ifsd_ifstate *
921 new_ifstate(u_short ifindex, int s)
922 {
923 	struct ifsd_ifstate *ifstate = NULL;
924 	struct ifsd_state *state;
925 
926 	if (curstate != NULL)
927 		state = curstate;
928 	else
929 		state = &conf->always;
930 
931 	TAILQ_FOREACH(ifstate, &state->interface_states, entries)
932 		if (ifstate->ifindex == ifindex && ifstate->ifstate == s)
933 			break;
934 	if (ifstate == NULL) {
935 		if ((ifstate = calloc(1, sizeof(*ifstate))) == NULL)
936 			errx(1, "new_ifstate: calloc");
937 		ifstate->ifindex = ifindex;
938 		ifstate->ifstate = s;
939 		TAILQ_INIT(&ifstate->expressions);
940 		TAILQ_INSERT_TAIL(&state->interface_states, ifstate, entries);
941 	}
942 	ifstate->prevstate = -1;
943 	ifstate->refcount++;
944 	return (ifstate);
945 }
946 
947 struct ifsd_external *
948 new_external(char *command, u_int32_t frequency)
949 {
950 	struct ifsd_external *external = NULL;
951 	struct ifsd_state *state;
952 
953 	if (curstate != NULL)
954 		state = curstate;
955 	else
956 		state = &conf->always;
957 
958 	TAILQ_FOREACH(external, &state->external_tests, entries)
959 		if (strcmp(external->command, command) == 0 &&
960 		    external->frequency == frequency)
961 			break;
962 	if (external == NULL) {
963 		if ((external = calloc(1, sizeof(*external))) == NULL)
964 			errx(1, "new_external: calloc");
965 		if ((external->command = strdup(command)) == NULL)
966 			errx(1, "new_external: strdup");
967 		external->frequency = frequency;
968 		TAILQ_INIT(&external->expressions);
969 		TAILQ_INSERT_TAIL(&state->external_tests, external, entries);
970 	}
971 	external->prevstatus = -1;
972 	external->refcount++;
973 	return (external);
974 }
975