xref: /openbsd-src/usr.sbin/ifstated/ifstated.c (revision 4c1e55dc91edd6e69ccc60ce855900fbc12cf34f)
1 /*	$OpenBSD: ifstated.c,v 1.40 2011/07/04 04:34:14 claudio 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 
140 	event_init();
141 	log_init(debug);
142 
143 	signal_set(&sigchld_ev, SIGCHLD, sigchld_handler, NULL);
144 	signal_add(&sigchld_ev, NULL);
145 
146 	/* Loading the config needs to happen in the event loop */
147 	timerclear(&tv);
148 	evtimer_set(&startup_ev, startup_handler, NULL);
149 	evtimer_add(&startup_ev, &tv);
150 
151 	event_loop(0);
152 	exit(0);
153 }
154 
155 void
156 startup_handler(int fd, short event, void *arg)
157 {
158 	int rt_fd;
159 	unsigned int rtfilter;
160 
161 	if ((rt_fd = socket(PF_ROUTE, SOCK_RAW, 0)) < 0)
162 		err(1, "no routing socket");
163 
164 	if (load_config() != 0) {
165 		log_warnx("unable to load config");
166 		exit(1);
167 	}
168 
169 	rtfilter = ROUTE_FILTER(RTM_IFINFO);
170 	if (setsockopt(rt_fd, PF_ROUTE, ROUTE_MSGFILTER,
171 	    &rtfilter, sizeof(rtfilter)) == -1)         /* not fatal */
172 		log_warn("startup_handler: setsockopt");
173 
174 	event_set(&rt_msg_ev, rt_fd, EV_READ|EV_PERSIST, rt_msg_handler, NULL);
175 	event_add(&rt_msg_ev, NULL);
176 
177 	signal_set(&sighup_ev, SIGHUP, sighup_handler, NULL);
178 	signal_add(&sighup_ev, NULL);
179 
180 	log_info("started");
181 }
182 
183 void
184 sighup_handler(int fd, short event, void *arg)
185 {
186 	log_info("reloading config");
187 	if (load_config() != 0)
188 		log_warnx("unable to reload config");
189 }
190 
191 int
192 load_config(void)
193 {
194 	if ((newconf = parse_config(configfile, opts)) == NULL)
195 		return (-1);
196 	if (conf != NULL)
197 		clear_config(conf);
198 	conf = newconf;
199 	conf->always.entered = time(NULL);
200 	fetch_state();
201 	external_evtimer_setup(&conf->always, IFSD_EVTIMER_ADD);
202 	adjust_external_expressions(&conf->always);
203 	eval_state(&conf->always);
204 	if (conf->curstate != NULL) {
205 		log_info("initial state: %s", conf->curstate->name);
206 		conf->curstate->entered = time(NULL);
207 		conf->nextstate = conf->curstate;
208 		conf->curstate = NULL;
209 		while (state_change())
210 			do_action(conf->curstate->always);
211 	}
212 	return (0);
213 }
214 
215 void
216 rt_msg_handler(int fd, short event, void *arg)
217 {
218 	char msg[2048];
219 	struct rt_msghdr *rtm = (struct rt_msghdr *)&msg;
220 	struct if_msghdr ifm;
221 	int len;
222 
223 	len = read(fd, msg, sizeof(msg));
224 
225 	/* XXX ignore errors? */
226 	if (len < sizeof(struct rt_msghdr))
227 		return;
228 
229 	if (rtm->rtm_version != RTM_VERSION)
230 		return;
231 
232 	if (rtm->rtm_type != RTM_IFINFO)
233 		return;
234 
235 	memcpy(&ifm, rtm, sizeof(ifm));
236 	scan_ifstate(ifm.ifm_index, ifm.ifm_data.ifi_link_state, 1);
237 }
238 
239 void
240 sigchld_handler(int fd, short event, void *arg)
241 {
242 	check_external_status(&conf->always);
243 	if (conf->curstate != NULL)
244 		check_external_status(conf->curstate);
245 }
246 
247 void
248 external_handler(int fd, short event, void *arg)
249 {
250 	struct ifsd_external *external = (struct ifsd_external *)arg;
251 	struct timeval tv;
252 
253 	/* re-schedule */
254 	timerclear(&tv);
255 	tv.tv_sec = external->frequency;
256 	evtimer_set(&external->ev, external_handler, external);
257 	evtimer_add(&external->ev, &tv);
258 
259 	/* execute */
260 	external_exec(external, 1);
261 }
262 
263 void
264 external_exec(struct ifsd_external *external, int async)
265 {
266 	char *argp[] = {"sh", "-c", NULL, NULL};
267 	pid_t pid;
268 	int s;
269 
270 	if (external->pid > 0) {
271 		log_debug("previous command %s [%d] still running, killing it",
272 		    external->command, external->pid);
273 		kill(external->pid, SIGKILL);
274 		waitpid(external->pid, &s, 0);
275 		external->pid = 0;
276 	}
277 
278 	argp[2] = external->command;
279 	log_debug("running %s", external->command);
280 	pid = fork();
281 	if (pid < 0) {
282 		log_warn("fork error");
283 	} else if (pid == 0) {
284 		execv("/bin/sh", argp);
285 		_exit(1);
286 		/* NOTREACHED */
287 	} else {
288 		external->pid = pid;
289 	}
290 	if (!async) {
291 		waitpid(external->pid, &s, 0);
292 		external->pid = 0;
293 		if (WIFEXITED(s))
294 			external->prevstatus = WEXITSTATUS(s);
295 	}
296 }
297 
298 void
299 adjust_external_expressions(struct ifsd_state *state)
300 {
301 	struct ifsd_external *external;
302 	struct ifsd_expression_list expressions;
303 
304 	TAILQ_INIT(&expressions);
305 	TAILQ_FOREACH(external, &state->external_tests, entries) {
306 		struct ifsd_expression *expression;
307 
308 		if (external->prevstatus == -1)
309 			continue;
310 
311 		TAILQ_FOREACH(expression, &external->expressions, entries) {
312 			TAILQ_INSERT_TAIL(&expressions,
313 			    expression, eval);
314 			expression->truth = !external->prevstatus;
315 		}
316 		adjust_expressions(&expressions, conf->maxdepth);
317 	}
318 }
319 
320 void
321 check_external_status(struct ifsd_state *state)
322 {
323 	struct ifsd_external *external, *end = NULL;
324 	int status, s, changed = 0;
325 
326 	/* Do this manually; change ordering so the oldest is first */
327 	external = TAILQ_FIRST(&state->external_tests);
328 	while (external != NULL && external != end) {
329 		struct ifsd_external *newexternal;
330 
331 		newexternal = TAILQ_NEXT(external, entries);
332 
333 		if (external->pid <= 0)
334 			goto loop;
335 
336 		if (wait4(external->pid, &s, WNOHANG, NULL) == 0)
337 			goto loop;
338 
339 		external->pid = 0;
340 		if (end == NULL)
341 			end = external;
342 		if (WIFEXITED(s))
343 			status = WEXITSTATUS(s);
344 		else {
345 			log_warnx("%s exited abnormally", external->command);
346 			goto loop;
347 		}
348 
349 		if (external->prevstatus != status &&
350 		    (external->prevstatus != -1 || !opt_inhibit)) {
351 			changed = 1;
352 			external->prevstatus = status;
353 		}
354 		external->lastexec = time(NULL);
355 		TAILQ_REMOVE(&state->external_tests, external, entries);
356 		TAILQ_INSERT_TAIL(&state->external_tests, external, entries);
357 loop:
358 		external = newexternal;
359 	}
360 
361 	if (changed) {
362 		adjust_external_expressions(state);
363 		eval_state(state);
364 	}
365 }
366 
367 void
368 external_evtimer_setup(struct ifsd_state *state, int action)
369 {
370 	struct ifsd_external *external;
371 	int s;
372 
373 	if (state != NULL) {
374 		switch (action) {
375 		case IFSD_EVTIMER_ADD:
376 			TAILQ_FOREACH(external,
377 			    &state->external_tests, entries) {
378 				struct timeval tv;
379 
380 				/* run it once right away */
381 				external_exec(external, 0);
382 
383 				/* schedule it for later */
384 				timerclear(&tv);
385 				tv.tv_sec = external->frequency;
386 				evtimer_set(&external->ev, external_handler,
387 				    external);
388 				evtimer_add(&external->ev, &tv);
389 			}
390 			break;
391 		case IFSD_EVTIMER_DEL:
392 			TAILQ_FOREACH(external,
393 			    &state->external_tests, entries) {
394 				if (external->pid > 0) {
395 					kill(external->pid, SIGKILL);
396 					waitpid(external->pid, &s, 0);
397 					external->pid = 0;
398 				}
399 				evtimer_del(&external->ev);
400 			}
401 			break;
402 		}
403 	}
404 }
405 
406 #define	LINK_STATE_IS_DOWN(_s)		(!LINK_STATE_IS_UP((_s)))
407 
408 int
409 scan_ifstate_single(int ifindex, int s, struct ifsd_state *state)
410 {
411 	struct ifsd_ifstate *ifstate;
412 	struct ifsd_expression_list expressions;
413 	int changed = 0;
414 
415 	TAILQ_INIT(&expressions);
416 
417 	TAILQ_FOREACH(ifstate, &state->interface_states, entries) {
418 		if (ifstate->ifindex == ifindex) {
419 			if (ifstate->prevstate != s &&
420 			    (ifstate->prevstate != -1 || !opt_inhibit)) {
421 				struct ifsd_expression *expression;
422 				int truth;
423 
424 				truth =
425 				    (ifstate->ifstate == IFSD_LINKUNKNOWN &&
426 				    s == LINK_STATE_UNKNOWN) ||
427 				    (ifstate->ifstate == IFSD_LINKDOWN &&
428 				    LINK_STATE_IS_DOWN(s)) ||
429 				    (ifstate->ifstate == IFSD_LINKUP &&
430 				    LINK_STATE_IS_UP(s));
431 
432 				TAILQ_FOREACH(expression,
433 				    &ifstate->expressions, entries) {
434 					expression->truth = truth;
435 					TAILQ_INSERT_TAIL(&expressions,
436 					    expression, eval);
437 					changed = 1;
438 				}
439 				ifstate->prevstate = s;
440 			}
441 		}
442 	}
443 
444 	if (changed)
445 		adjust_expressions(&expressions, conf->maxdepth);
446 	return (changed);
447 }
448 
449 void
450 scan_ifstate(int ifindex, int s, int do_eval)
451 {
452 	struct ifsd_state *state;
453 	int cur_eval = 0;
454 
455 	if (scan_ifstate_single(ifindex, s, &conf->always) && do_eval)
456 		eval_state(&conf->always);
457 	TAILQ_FOREACH(state, &conf->states, entries) {
458 		if (scan_ifstate_single(ifindex, s, state) &&
459 		    (do_eval && state == conf->curstate))
460 			cur_eval = 1;
461 	}
462 	/* execute actions _after_ all expressions have been adjusted */
463 	if (cur_eval)
464 		eval_state(conf->curstate);
465 }
466 
467 /*
468  * Do a bottom-up ajustment of the expression tree's truth value,
469  * level-by-level to ensure that each expression's subexpressions have been
470  * evaluated.
471  */
472 void
473 adjust_expressions(struct ifsd_expression_list *expressions, int depth)
474 {
475 	struct ifsd_expression_list nexpressions;
476 	struct ifsd_expression *expression;
477 
478 	TAILQ_INIT(&nexpressions);
479 	while ((expression = TAILQ_FIRST(expressions)) != NULL) {
480 		TAILQ_REMOVE(expressions, expression, eval);
481 		if (expression->depth == depth) {
482 			struct ifsd_expression *te;
483 
484 			switch (expression->type) {
485 			case IFSD_OPER_AND:
486 				expression->truth = expression->left->truth &&
487 				    expression->right->truth;
488 				break;
489 			case IFSD_OPER_OR:
490 				expression->truth = expression->left->truth ||
491 				    expression->right->truth;
492 				break;
493 			case IFSD_OPER_NOT:
494 				expression->truth = !expression->right->truth;
495 				break;
496 			default:
497 				break;
498 			}
499 			if (expression->parent != NULL) {
500 				if (TAILQ_EMPTY(&nexpressions))
501 				te = NULL;
502 				TAILQ_FOREACH(te, &nexpressions, eval)
503 					if (expression->parent == te)
504 						break;
505 				if (te == NULL)
506 					TAILQ_INSERT_TAIL(&nexpressions,
507 					    expression->parent, eval);
508 			}
509 		} else
510 			TAILQ_INSERT_TAIL(&nexpressions, expression, eval);
511 	}
512 	if (depth > 0)
513 		adjust_expressions(&nexpressions, depth - 1);
514 }
515 
516 void
517 eval_state(struct ifsd_state *state)
518 {
519 	struct ifsd_external *external = TAILQ_FIRST(&state->external_tests);
520 	if (external == NULL || external->lastexec >= state->entered ||
521 	    external->lastexec == 0) {
522 		do_action(state->always);
523 		while (state_change())
524 			do_action(conf->curstate->always);
525 	}
526 }
527 
528 /*
529  *If a previous action included a state change, process it.
530  */
531 int
532 state_change(void)
533 {
534 	if (conf->nextstate != NULL && conf->curstate != conf->nextstate) {
535 		log_info("changing state to %s", conf->nextstate->name);
536 		if (conf->curstate != NULL) {
537 			evtimer_del(&conf->curstate->ev);
538 			external_evtimer_setup(conf->curstate,
539 			    IFSD_EVTIMER_DEL);
540 		}
541 		conf->curstate = conf->nextstate;
542 		conf->nextstate = NULL;
543 		conf->curstate->entered = time(NULL);
544 		external_evtimer_setup(conf->curstate, IFSD_EVTIMER_ADD);
545 		adjust_external_expressions(conf->curstate);
546 		do_action(conf->curstate->init);
547 		return (1);
548 	}
549 	return (0);
550 }
551 
552 /*
553  * Run recursively through the tree of actions.
554  */
555 void
556 do_action(struct ifsd_action *action)
557 {
558 	struct ifsd_action *subaction;
559 
560 	switch (action->type) {
561 	case IFSD_ACTION_COMMAND:
562 		log_debug("running %s", action->act.command);
563 		system(action->act.command);
564 		break;
565 	case IFSD_ACTION_CHANGESTATE:
566 		conf->nextstate = action->act.nextstate;
567 		break;
568 	case IFSD_ACTION_CONDITION:
569 		if ((action->act.c.expression != NULL &&
570 		    action->act.c.expression->truth) ||
571 		    action->act.c.expression == NULL) {
572 			TAILQ_FOREACH(subaction, &action->act.c.actions,
573 			    entries)
574 				do_action(subaction);
575 		}
576 		break;
577 	default:
578 		log_debug("do_action: unknown action %d", action->type);
579 		break;
580 	}
581 }
582 
583 /*
584  * Fetch the current link states.
585  */
586 void
587 fetch_state(void)
588 {
589 	struct ifaddrs *ifap, *ifa;
590 	char *oname = NULL;
591 	int sock = socket(AF_INET, SOCK_DGRAM, 0);
592 
593 	if (getifaddrs(&ifap) != 0)
594 		err(1, "getifaddrs");
595 
596 	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
597 		struct ifreq ifr;
598 		struct if_data  ifrdat;
599 
600 		if (oname && !strcmp(oname, ifa->ifa_name))
601 			continue;
602 		oname = ifa->ifa_name;
603 
604 		strlcpy(ifr.ifr_name, ifa->ifa_name, sizeof(ifr.ifr_name));
605 		ifr.ifr_data = (caddr_t)&ifrdat;
606 
607 		if (ioctl(sock, SIOCGIFDATA, (caddr_t)&ifr) == -1)
608 			continue;
609 
610 		scan_ifstate(if_nametoindex(ifa->ifa_name),
611 		    ifrdat.ifi_link_state, 0);
612 	}
613 	freeifaddrs(ifap);
614 	close(sock);
615 }
616 
617 
618 
619 /*
620  * Clear the config.
621  */
622 void
623 clear_config(struct ifsd_config *oconf)
624 {
625 	struct ifsd_state *state;
626 
627 	external_evtimer_setup(&conf->always, IFSD_EVTIMER_DEL);
628 	if (conf != NULL && conf->curstate != NULL)
629 		external_evtimer_setup(conf->curstate, IFSD_EVTIMER_DEL);
630 	while ((state = TAILQ_FIRST(&oconf->states)) != NULL) {
631 		TAILQ_REMOVE(&oconf->states, state, entries);
632 		remove_action(state->init, state);
633 		remove_action(state->always, state);
634 		free(state->name);
635 		free(state);
636 	}
637 	remove_action(oconf->always.init, &oconf->always);
638 	remove_action(oconf->always.always, &oconf->always);
639 	free(oconf);
640 }
641 
642 void
643 remove_action(struct ifsd_action *action, struct ifsd_state *state)
644 {
645 	struct ifsd_action *subaction;
646 
647 	if (action == NULL || state == NULL)
648 		return;
649 
650 	switch (action->type) {
651 	case IFSD_ACTION_LOG:
652 		free(action->act.logmessage);
653 		break;
654 	case IFSD_ACTION_COMMAND:
655 		free(action->act.command);
656 		break;
657 	case IFSD_ACTION_CHANGESTATE:
658 		break;
659 	case IFSD_ACTION_CONDITION:
660 		if (action->act.c.expression != NULL)
661 			remove_expression(action->act.c.expression, state);
662 		while ((subaction =
663 		    TAILQ_FIRST(&action->act.c.actions)) != NULL) {
664 			TAILQ_REMOVE(&action->act.c.actions,
665 			    subaction, entries);
666 			remove_action(subaction, state);
667 		}
668 	}
669 	free(action);
670 }
671 
672 void
673 remove_expression(struct ifsd_expression *expression,
674     struct ifsd_state *state)
675 {
676 	switch (expression->type) {
677 	case IFSD_OPER_IFSTATE:
678 		TAILQ_REMOVE(&expression->u.ifstate->expressions, expression,
679 		    entries);
680 		if (--expression->u.ifstate->refcount == 0) {
681 			TAILQ_REMOVE(&state->interface_states,
682 			    expression->u.ifstate, entries);
683 			free(expression->u.ifstate);
684 		}
685 		break;
686 	case IFSD_OPER_EXTERNAL:
687 		TAILQ_REMOVE(&expression->u.external->expressions, expression,
688 		    entries);
689 		if (--expression->u.external->refcount == 0) {
690 			TAILQ_REMOVE(&state->external_tests,
691 			    expression->u.external, entries);
692 			free(expression->u.external->command);
693 			event_del(&expression->u.external->ev);
694 			free(expression->u.external);
695 		}
696 		break;
697 	default:
698 		if (expression->left != NULL)
699 			remove_expression(expression->left, state);
700 		if (expression->right != NULL)
701 			remove_expression(expression->right, state);
702 		break;
703 	}
704 	free(expression);
705 }
706