xref: /openbsd-src/usr.sbin/ifstated/ifstated.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: ifstated.c,v 1.33 2008/05/12 19:15:02 pyr Exp $	*/
2 
3 /*
4  * Copyright (c) 2004 Marco Pfatschbacher <mpf@openbsd.org>
5  * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
6  *
7  * Permission to use, copy, modify, and distribute this software for any
8  * purpose with or without fee is hereby granted, provided that the above
9  * copyright notice and this permission notice appear in all copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18  */
19 
20 /*
21  * ifstated listens to link_state transitions on interfaces
22  * and executes predefined commands.
23  */
24 
25 #include <sys/types.h>
26 #include <sys/time.h>
27 #include <sys/ioctl.h>
28 #include <sys/socket.h>
29 #include <sys/wait.h>
30 
31 #include <net/if.h>
32 #include <net/route.h>
33 #include <netinet/in.h>
34 
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <signal.h>
39 #include <err.h>
40 #include <event.h>
41 #include <unistd.h>
42 #include <ifaddrs.h>
43 
44 #include "ifstated.h"
45 
46 struct	 ifsd_config *conf = NULL, *newconf = NULL;
47 
48 int	 opts = 0;
49 int	 opt_inhibit = 0;
50 char	*configfile = "/etc/ifstated.conf";
51 struct event	rt_msg_ev, sighup_ev, startup_ev, sigchld_ev;
52 
53 void	startup_handler(int, short, void *);
54 void	sighup_handler(int, short, void *);
55 int	load_config(void);
56 void	sigchld_handler(int, short, void *);
57 void	rt_msg_handler(int, short, void *);
58 void	external_handler(int, short, void *);
59 void	external_exec(struct ifsd_external *, int);
60 void	check_external_status(struct ifsd_state *);
61 void	external_evtimer_setup(struct ifsd_state *, int);
62 void	scan_ifstate(int, int, int);
63 int	scan_ifstate_single(int, int, struct ifsd_state *);
64 void	fetch_state(void);
65 void	usage(void);
66 void	adjust_expressions(struct ifsd_expression_list *, int);
67 void	adjust_external_expressions(struct ifsd_state *);
68 void	eval_state(struct ifsd_state *);
69 int	state_change(void);
70 void	do_action(struct ifsd_action *);
71 void	remove_action(struct ifsd_action *, struct ifsd_state *);
72 void	remove_expression(struct ifsd_expression *, struct ifsd_state *);
73 
74 void
75 usage(void)
76 {
77 	extern char *__progname;
78 
79 	fprintf(stderr, "usage: %s [-dhinv] [-D macro=value] [-f file]\n",
80 	    __progname);
81 	exit(1);
82 }
83 
84 int
85 main(int argc, char *argv[])
86 {
87 	struct timeval tv;
88 	int ch;
89 	int debug = 0;
90 
91 	log_init(1);
92 
93 	while ((ch = getopt(argc, argv, "dD:f:hniv")) != -1) {
94 		switch (ch) {
95 		case 'd':
96 			debug = 1;
97 			break;
98 		case 'D':
99 			if (cmdline_symset(optarg) < 0)
100 				errx(1, "could not parse macro definition %s",
101 				    optarg);
102 			break;
103 		case 'f':
104 			configfile = optarg;
105 			break;
106 		case 'h':
107 			usage();
108 			break;
109 		case 'n':
110 			opts |= IFSD_OPT_NOACTION;
111 			break;
112 		case 'i':
113 			opt_inhibit = 1;
114 			break;
115 		case 'v':
116 			if (opts & IFSD_OPT_VERBOSE)
117 				opts |= IFSD_OPT_VERBOSE2;
118 			opts |= IFSD_OPT_VERBOSE;
119 			break;
120 		default:
121 			usage();
122 		}
123 	}
124 
125 	argc -= optind;
126 	argv += optind;
127 	if (argc > 0)
128 		usage();
129 
130 	if (opts & IFSD_OPT_NOACTION) {
131 		if ((newconf = parse_config(configfile, opts)) == NULL)
132 			exit(1);
133 		warnx("configuration OK");
134 		exit(0);
135 	}
136 
137 	if (!debug) {
138 		daemon(1, 0);
139 		setproctitle(NULL);
140 	}
141 
142 	event_init();
143 	log_init(debug);
144 
145 	signal_set(&sigchld_ev, SIGCHLD, sigchld_handler, NULL);
146 	signal_add(&sigchld_ev, NULL);
147 
148 	/* Loading the config needs to happen in the event loop */
149 	tv.tv_usec = 0;
150 	tv.tv_sec = 0;
151 	evtimer_set(&startup_ev, startup_handler, NULL);
152 	evtimer_add(&startup_ev, &tv);
153 
154 	event_loop(0);
155 	exit(0);
156 }
157 
158 void
159 startup_handler(int fd, short event, void *arg)
160 {
161 	int rt_fd;
162 
163 	if ((rt_fd = socket(PF_ROUTE, SOCK_RAW, 0)) < 0)
164 		err(1, "no routing socket");
165 
166 	if (load_config() != 0) {
167 		log_warnx("unable to load config");
168 		exit(1);
169 	}
170 
171 	event_set(&rt_msg_ev, rt_fd, EV_READ|EV_PERSIST, rt_msg_handler, NULL);
172 	event_add(&rt_msg_ev, NULL);
173 
174 	signal_set(&sighup_ev, SIGHUP, sighup_handler, NULL);
175 	signal_add(&sighup_ev, NULL);
176 
177 	log_info("started");
178 }
179 
180 void
181 sighup_handler(int fd, short event, void *arg)
182 {
183 	log_info("reloading config");
184 	if (load_config() != 0)
185 		log_warnx("unable to reload config");
186 }
187 
188 int
189 load_config(void)
190 {
191 	if ((newconf = parse_config(configfile, opts)) == NULL)
192 		return (-1);
193 	if (conf != NULL)
194 		clear_config(conf);
195 	conf = newconf;
196 	conf->always.entered = time(NULL);
197 	fetch_state();
198 	external_evtimer_setup(&conf->always, IFSD_EVTIMER_ADD);
199 	adjust_external_expressions(&conf->always);
200 	eval_state(&conf->always);
201 	if (conf->curstate != NULL) {
202 		log_info("initial state: %s", conf->curstate->name);
203 		conf->curstate->entered = time(NULL);
204 		conf->nextstate = conf->curstate;
205 		conf->curstate = NULL;
206 		while (state_change())
207 			do_action(conf->curstate->always);
208 	}
209 	return (0);
210 }
211 
212 void
213 rt_msg_handler(int fd, short event, void *arg)
214 {
215 	char msg[2048];
216 	struct rt_msghdr *rtm = (struct rt_msghdr *)&msg;
217 	struct if_msghdr ifm;
218 	int len;
219 
220 	len = read(fd, msg, sizeof(msg));
221 
222 	/* XXX ignore errors? */
223 	if (len < sizeof(struct rt_msghdr))
224 		return;
225 
226 	if (rtm->rtm_version != RTM_VERSION)
227 		return;
228 
229 	if (rtm->rtm_type != RTM_IFINFO)
230 		return;
231 
232 	memcpy(&ifm, rtm, sizeof(ifm));
233 	scan_ifstate(ifm.ifm_index, ifm.ifm_data.ifi_link_state, 1);
234 }
235 
236 void
237 sigchld_handler(int fd, short event, void *arg)
238 {
239 	check_external_status(&conf->always);
240 	if (conf->curstate != NULL)
241 		check_external_status(conf->curstate);
242 }
243 
244 void
245 external_handler(int fd, short event, void *arg)
246 {
247 	struct ifsd_external *external = (struct ifsd_external *)arg;
248 	struct timeval tv;
249 
250 	/* re-schedule */
251 	tv.tv_usec = 0;
252 	tv.tv_sec = external->frequency;
253 	evtimer_set(&external->ev, external_handler, external);
254 	evtimer_add(&external->ev, &tv);
255 
256 	/* execute */
257 	external_exec(external, 1);
258 }
259 
260 void
261 external_exec(struct ifsd_external *external, int async)
262 {
263 	char *argp[] = {"sh", "-c", NULL, NULL};
264 	pid_t pid;
265 	int s;
266 
267 	if (external->pid > 0) {
268 		log_info("previous command %s [%d] still running, killing it",
269 		    external->command, external->pid);
270 		kill(external->pid, SIGKILL);
271 		waitpid(external->pid, &s, 0);
272 		external->pid = 0;
273 	}
274 
275 	argp[2] = external->command;
276 	log_debug("running %s", external->command);
277 	pid = fork();
278 	if (pid < 0) {
279 		log_warn("fork error");
280 	} else if (pid == 0) {
281 		execv("/bin/sh", argp);
282 		_exit(1);
283 		/* NOTREACHED */
284 	} else {
285 		external->pid = pid;
286 	}
287 	if (!async) {
288 		waitpid(external->pid, &s, 0);
289 		external->pid = 0;
290 		if (WIFEXITED(s))
291 			external->prevstatus = WEXITSTATUS(s);
292 	}
293 }
294 
295 void
296 adjust_external_expressions(struct ifsd_state *state)
297 {
298 	struct ifsd_external *external;
299 	struct ifsd_expression_list expressions;
300 
301 	TAILQ_INIT(&expressions);
302 	TAILQ_FOREACH(external, &state->external_tests, entries) {
303 		struct ifsd_expression *expression;
304 
305 		if (external->prevstatus == -1)
306 			continue;
307 
308 		TAILQ_FOREACH(expression, &external->expressions, entries) {
309 			TAILQ_INSERT_TAIL(&expressions,
310 			    expression, eval);
311 			expression->truth = !external->prevstatus;
312 		}
313 		adjust_expressions(&expressions, conf->maxdepth);
314 	}
315 }
316 
317 void
318 check_external_status(struct ifsd_state *state)
319 {
320 	struct ifsd_external *external, *end = NULL;
321 	int status, s, changed = 0;
322 
323 	/* Do this manually; change ordering so the oldest is first */
324 	external = TAILQ_FIRST(&state->external_tests);
325 	while (external != NULL && external != end) {
326 		struct ifsd_external *newexternal;
327 
328 		newexternal = TAILQ_NEXT(external, entries);
329 
330 		if (external->pid <= 0)
331 			goto loop;
332 
333 		if (wait4(external->pid, &s, WNOHANG, NULL) == 0)
334 			goto loop;
335 
336 		external->pid = 0;
337 		if (end == NULL)
338 			end = external;
339 		if (WIFEXITED(s))
340 			status = WEXITSTATUS(s);
341 		else {
342 			log_warnx("%s exited abnormally", external->command);
343 			goto loop;
344 		}
345 
346 		if (external->prevstatus != status &&
347 		    (external->prevstatus != -1 || !opt_inhibit)) {
348 			changed = 1;
349 			external->prevstatus = status;
350 		}
351 		external->lastexec = time(NULL);
352 		TAILQ_REMOVE(&state->external_tests, external, entries);
353 		TAILQ_INSERT_TAIL(&state->external_tests, external, entries);
354 loop:
355 		external = newexternal;
356 	}
357 
358 	if (changed) {
359 		adjust_external_expressions(state);
360 		eval_state(state);
361 	}
362 }
363 
364 void
365 external_evtimer_setup(struct ifsd_state *state, int action)
366 {
367 	struct ifsd_external *external;
368 	int s;
369 
370 	if (state != NULL) {
371 		switch (action) {
372 		case IFSD_EVTIMER_ADD:
373 			TAILQ_FOREACH(external,
374 			    &state->external_tests, entries) {
375 				struct timeval tv;
376 
377 				/* run it once right away */
378 				external_exec(external, 0);
379 
380 				/* schedule it for later */
381 				tv.tv_usec = 0;
382 				tv.tv_sec = external->frequency;
383 				evtimer_set(&external->ev, external_handler,
384 				    external);
385 				evtimer_add(&external->ev, &tv);
386 			}
387 			break;
388 		case IFSD_EVTIMER_DEL:
389 			TAILQ_FOREACH(external,
390 			    &state->external_tests, entries) {
391 				if (external->pid > 0) {
392 					kill(external->pid, SIGKILL);
393 					waitpid(external->pid, &s, 0);
394 					external->pid = 0;
395 				}
396 				evtimer_del(&external->ev);
397 			}
398 			break;
399 		}
400 	}
401 }
402 
403 int
404 scan_ifstate_single(int ifindex, int s, struct ifsd_state *state)
405 {
406 	struct ifsd_ifstate *ifstate;
407 	struct ifsd_expression_list expressions;
408 	int changed = 0;
409 
410 	TAILQ_INIT(&expressions);
411 
412 	TAILQ_FOREACH(ifstate, &state->interface_states, entries) {
413 		if (ifstate->ifindex == ifindex) {
414 			if (ifstate->prevstate != s &&
415 			    (ifstate->prevstate != -1 || !opt_inhibit)) {
416 				struct ifsd_expression *expression;
417 				int truth;
418 
419 				truth = (ifstate->ifstate == s) ||
420 				    (ifstate->ifstate == IFSD_LINKUP &&
421 				    LINK_STATE_IS_UP(s));
422 
423 				TAILQ_FOREACH(expression,
424 				    &ifstate->expressions, entries) {
425 					expression->truth = truth;
426 					TAILQ_INSERT_TAIL(&expressions,
427 					    expression, eval);
428 					changed = 1;
429 				}
430 				ifstate->prevstate = s;
431 			}
432 		}
433 	}
434 
435 	if (changed)
436 		adjust_expressions(&expressions, conf->maxdepth);
437 	return (changed);
438 }
439 
440 void
441 scan_ifstate(int ifindex, int s, int do_eval)
442 {
443 	struct ifsd_state *state;
444 	int cur_eval = 0;
445 
446 	if (scan_ifstate_single(ifindex, s, &conf->always) && do_eval)
447 		eval_state(&conf->always);
448 	TAILQ_FOREACH(state, &conf->states, entries) {
449 		if (scan_ifstate_single(ifindex, s, state) &&
450 		    (do_eval && state == conf->curstate))
451 			cur_eval = 1;
452 	}
453 	/* execute actions _after_ all expressions have been adjusted */
454 	if (cur_eval)
455 		eval_state(conf->curstate);
456 }
457 
458 /*
459  * Do a bottom-up ajustment of the expression tree's truth value,
460  * level-by-level to ensure that each expression's subexpressions have been
461  * evaluated.
462  */
463 void
464 adjust_expressions(struct ifsd_expression_list *expressions, int depth)
465 {
466 	struct ifsd_expression_list nexpressions;
467 	struct ifsd_expression *expression;
468 
469 	TAILQ_INIT(&nexpressions);
470 	while ((expression = TAILQ_FIRST(expressions)) != NULL) {
471 		TAILQ_REMOVE(expressions, expression, eval);
472 		if (expression->depth == depth) {
473 			struct ifsd_expression *te;
474 
475 			switch (expression->type) {
476 			case IFSD_OPER_AND:
477 				expression->truth = expression->left->truth &&
478 				    expression->right->truth;
479 				break;
480 			case IFSD_OPER_OR:
481 				expression->truth = expression->left->truth ||
482 				    expression->right->truth;
483 				break;
484 			case IFSD_OPER_NOT:
485 				expression->truth = !expression->right->truth;
486 				break;
487 			default:
488 				break;
489 			}
490 			if (expression->parent != NULL) {
491 				if (TAILQ_EMPTY(&nexpressions))
492 				te = NULL;
493 				TAILQ_FOREACH(te, &nexpressions, eval)
494 					if (expression->parent == te)
495 						break;
496 				if (te == NULL)
497 					TAILQ_INSERT_TAIL(&nexpressions,
498 					    expression->parent, eval);
499 			}
500 		} else
501 			TAILQ_INSERT_TAIL(&nexpressions, expression, eval);
502 	}
503 	if (depth > 0)
504 		adjust_expressions(&nexpressions, depth - 1);
505 }
506 
507 void
508 eval_state(struct ifsd_state *state)
509 {
510 	struct ifsd_external *external = TAILQ_FIRST(&state->external_tests);
511 	if (external == NULL || external->lastexec >= state->entered ||
512 	    external->lastexec == 0) {
513 		do_action(state->always);
514 		while (state_change())
515 			do_action(conf->curstate->always);
516 	}
517 }
518 
519 /*
520  *If a previous action included a state change, process it.
521  */
522 int
523 state_change(void)
524 {
525 	if (conf->nextstate != NULL && conf->curstate != conf->nextstate) {
526 		log_info("changing state to %s", conf->nextstate->name);
527 		if (conf->curstate != NULL) {
528 			evtimer_del(&conf->curstate->ev);
529 			external_evtimer_setup(conf->curstate,
530 			    IFSD_EVTIMER_DEL);
531 		}
532 		conf->curstate = conf->nextstate;
533 		conf->nextstate = NULL;
534 		conf->curstate->entered = time(NULL);
535 		external_evtimer_setup(conf->curstate, IFSD_EVTIMER_ADD);
536 		adjust_external_expressions(conf->curstate);
537 		do_action(conf->curstate->init);
538 		return (1);
539 	}
540 	return (0);
541 }
542 
543 /*
544  * Run recursively through the tree of actions.
545  */
546 void
547 do_action(struct ifsd_action *action)
548 {
549 	struct ifsd_action *subaction;
550 
551 	switch (action->type) {
552 	case IFSD_ACTION_COMMAND:
553 		log_info("running %s", action->act.command);
554 		system(action->act.command);
555 		break;
556 	case IFSD_ACTION_CHANGESTATE:
557 		conf->nextstate = action->act.nextstate;
558 		break;
559 	case IFSD_ACTION_CONDITION:
560 		if ((action->act.c.expression != NULL &&
561 		    action->act.c.expression->truth) ||
562 		    action->act.c.expression == NULL) {
563 			TAILQ_FOREACH(subaction, &action->act.c.actions,
564 			    entries)
565 				do_action(subaction);
566 		}
567 		break;
568 	default:
569 		log_debug("do_action: unknown action %d", action->type);
570 		break;
571 	}
572 }
573 
574 /*
575  * Fetch the current link states.
576  */
577 void
578 fetch_state(void)
579 {
580 	struct ifaddrs *ifap, *ifa;
581 	char *oname = NULL;
582 	int sock = socket(AF_INET, SOCK_DGRAM, 0);
583 
584 	if (getifaddrs(&ifap) != 0)
585 		err(1, "getifaddrs");
586 
587 	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
588 		struct ifreq ifr;
589 		struct if_data  ifrdat;
590 
591 		if (oname && !strcmp(oname, ifa->ifa_name))
592 			continue;
593 		oname = ifa->ifa_name;
594 
595 		strlcpy(ifr.ifr_name, ifa->ifa_name, sizeof(ifr.ifr_name));
596 		ifr.ifr_data = (caddr_t)&ifrdat;
597 
598 		if (ioctl(sock, SIOCGIFDATA, (caddr_t)&ifr) == -1)
599 			continue;
600 
601 		scan_ifstate(if_nametoindex(ifa->ifa_name),
602 		    ifrdat.ifi_link_state, 0);
603 	}
604 	freeifaddrs(ifap);
605 	close(sock);
606 }
607 
608 
609 
610 /*
611  * Clear the config.
612  */
613 void
614 clear_config(struct ifsd_config *oconf)
615 {
616 	struct ifsd_state *state;
617 
618 	external_evtimer_setup(&conf->always, IFSD_EVTIMER_DEL);
619 	if (conf != NULL && conf->curstate != NULL)
620 		external_evtimer_setup(conf->curstate, IFSD_EVTIMER_DEL);
621 	while ((state = TAILQ_FIRST(&oconf->states)) != NULL) {
622 		TAILQ_REMOVE(&oconf->states, state, entries);
623 		remove_action(state->init, state);
624 		remove_action(state->always, state);
625 		free(state->name);
626 		free(state);
627 	}
628 	remove_action(oconf->always.init, &oconf->always);
629 	remove_action(oconf->always.always, &oconf->always);
630 	free(oconf);
631 }
632 
633 void
634 remove_action(struct ifsd_action *action, struct ifsd_state *state)
635 {
636 	struct ifsd_action *subaction;
637 
638 	if (action == NULL || state == NULL)
639 		return;
640 
641 	switch (action->type) {
642 	case IFSD_ACTION_LOG:
643 		free(action->act.logmessage);
644 		break;
645 	case IFSD_ACTION_COMMAND:
646 		free(action->act.command);
647 		break;
648 	case IFSD_ACTION_CHANGESTATE:
649 		break;
650 	case IFSD_ACTION_CONDITION:
651 		if (action->act.c.expression != NULL)
652 			remove_expression(action->act.c.expression, state);
653 		while ((subaction =
654 		    TAILQ_FIRST(&action->act.c.actions)) != NULL) {
655 			TAILQ_REMOVE(&action->act.c.actions,
656 			    subaction, entries);
657 			remove_action(subaction, state);
658 		}
659 	}
660 	free(action);
661 }
662 
663 void
664 remove_expression(struct ifsd_expression *expression,
665     struct ifsd_state *state)
666 {
667 	switch (expression->type) {
668 	case IFSD_OPER_IFSTATE:
669 		TAILQ_REMOVE(&expression->u.ifstate->expressions, expression,
670 		    entries);
671 		if (--expression->u.ifstate->refcount == 0) {
672 			TAILQ_REMOVE(&state->interface_states,
673 			    expression->u.ifstate, entries);
674 			free(expression->u.ifstate);
675 		}
676 		break;
677 	case IFSD_OPER_EXTERNAL:
678 		TAILQ_REMOVE(&expression->u.external->expressions, expression,
679 		    entries);
680 		if (--expression->u.external->refcount == 0) {
681 			TAILQ_REMOVE(&state->external_tests,
682 			    expression->u.external, entries);
683 			free(expression->u.external->command);
684 			event_del(&expression->u.external->ev);
685 			free(expression->u.external);
686 		}
687 		break;
688 	default:
689 		if (expression->left != NULL)
690 			remove_expression(expression->left, state);
691 		if (expression->right != NULL)
692 			remove_expression(expression->right, state);
693 		break;
694 	}
695 	free(expression);
696 }
697