1/*	$OpenBSD: ifstated.c,v 1.68 2024/04/23 13:34:51 jsg 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/socket.h>
28#include <sys/wait.h>
29
30#include <net/if.h>
31#include <net/route.h>
32#include <netinet/in.h>
33
34#include <paths.h>
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <signal.h>
39#include <stdint.h>
40#include <syslog.h>
41#include <errno.h>
42#include <event.h>
43#include <unistd.h>
44#include <ifaddrs.h>
45
46#include "ifstated.h"
47#include "log.h"
48
49struct	 ifsd_config *conf, *newconf;
50
51int	 opts;
52int	 opt_inhibit;
53char	*configfile = "/etc/ifstated.conf";
54struct event	rt_msg_ev, sighup_ev, startup_ev, sigchld_ev;
55
56void		startup_handler(int, short, void *);
57void		sighup_handler(int, short, void *);
58int		load_config(void);
59void		sigchld_handler(int, short, void *);
60void		rt_msg_handler(int, short, void *);
61void		external_handler(int, short, void *);
62void		external_exec(struct ifsd_external *, int);
63void		check_external_status(struct ifsd_state *);
64void		check_ifdeparture(void);
65void		external_evtimer_setup(struct ifsd_state *, int);
66void		scan_ifstate(const char *, int, int);
67int		scan_ifstate_single(const char *, int, struct ifsd_state *);
68void		fetch_ifstate(int);
69__dead void	usage(void);
70void		adjust_expressions(struct ifsd_expression_list *, int);
71void		adjust_external_expressions(struct ifsd_state *);
72void		eval_state(struct ifsd_state *);
73int		state_change(void);
74void		do_action(struct ifsd_action *);
75void		remove_action(struct ifsd_action *, struct ifsd_state *);
76void		remove_expression(struct ifsd_expression *,
77		    struct ifsd_state *);
78
79__dead void
80usage(void)
81{
82	extern char *__progname;
83
84	fprintf(stderr, "usage: %s [-dhinv] [-D macro=value] [-f file]\n",
85	    __progname);
86	exit(1);
87}
88
89int
90main(int argc, char *argv[])
91{
92	struct timeval tv;
93	int ch, rt_fd;
94	int debug = 0;
95	unsigned int rtfilter;
96
97	log_init(1, LOG_DAEMON);	/* log to stderr until daemonized */
98	log_setverbose(1);
99
100	while ((ch = getopt(argc, argv, "dD:f:hniv")) != -1) {
101		switch (ch) {
102		case 'd':
103			debug = 1;
104			break;
105		case 'D':
106			if (cmdline_symset(optarg) < 0)
107				fatalx("could not parse macro definition %s",
108				    optarg);
109			break;
110		case 'f':
111			configfile = optarg;
112			break;
113		case 'h':
114			usage();
115			break;
116		case 'n':
117			opts |= IFSD_OPT_NOACTION;
118			break;
119		case 'i':
120			opt_inhibit = 1;
121			break;
122		case 'v':
123			if (opts & IFSD_OPT_VERBOSE)
124				opts |= IFSD_OPT_VERBOSE2;
125			opts |= IFSD_OPT_VERBOSE;
126			break;
127		default:
128			usage();
129		}
130	}
131
132	argc -= optind;
133	argv += optind;
134	if (argc > 0)
135		usage();
136
137	if (opts & IFSD_OPT_NOACTION) {
138		if ((newconf = parse_config(configfile, opts)) == NULL)
139			exit(1);
140		fprintf(stderr, "configuration OK\n");
141		exit(0);
142	}
143
144	if (!debug)
145		daemon(1, 0);
146
147	event_init();
148	log_init(debug, LOG_DAEMON);
149	log_setverbose(opts & IFSD_OPT_VERBOSE);
150
151	if ((rt_fd = socket(AF_ROUTE, SOCK_RAW, 0)) == -1)
152		fatal("no routing socket");
153
154	rtfilter = ROUTE_FILTER(RTM_IFINFO) | ROUTE_FILTER(RTM_IFANNOUNCE);
155	if (setsockopt(rt_fd, AF_ROUTE, ROUTE_MSGFILTER,
156	    &rtfilter, sizeof(rtfilter)) == -1)	/* not fatal */
157		log_warn("%s: setsockopt msgfilter", __func__);
158
159	rtfilter = RTABLE_ANY;
160	if (setsockopt(rt_fd, AF_ROUTE, ROUTE_TABLEFILTER,
161	    &rtfilter, sizeof(rtfilter)) == -1)	/* not fatal */
162		log_warn("%s: setsockopt tablefilter", __func__);
163
164	if (unveil(configfile, "r") == -1)
165		fatal("unveil %s", configfile);
166	if (unveil(_PATH_BSHELL, "x") == -1)
167		fatal("unveil %s", _PATH_BSHELL);
168	if (pledge("stdio rpath route proc exec", NULL) == -1)
169		fatal("pledge");
170
171	signal_set(&sigchld_ev, SIGCHLD, sigchld_handler, NULL);
172	signal_add(&sigchld_ev, NULL);
173
174	/* Loading the config needs to happen in the event loop */
175	timerclear(&tv);
176	evtimer_set(&startup_ev, startup_handler, (void *)(long)rt_fd);
177	evtimer_add(&startup_ev, &tv);
178
179	event_loop(0);
180	exit(0);
181}
182
183void
184startup_handler(int fd, short event, void *arg)
185{
186	int rfd = (int)(long)arg;
187
188	if (load_config() != 0) {
189		log_warnx("unable to load config");
190		exit(1);
191	}
192
193	event_set(&rt_msg_ev, rfd, EV_READ|EV_PERSIST, rt_msg_handler, NULL);
194	event_add(&rt_msg_ev, NULL);
195
196	signal_set(&sighup_ev, SIGHUP, sighup_handler, NULL);
197	signal_add(&sighup_ev, NULL);
198
199	log_info("started");
200}
201
202void
203sighup_handler(int fd, short event, void *arg)
204{
205	log_info("reloading config");
206	if (load_config() != 0)
207		log_warnx("unable to reload config");
208}
209
210int
211load_config(void)
212{
213	if ((newconf = parse_config(configfile, opts)) == NULL)
214		return (-1);
215	if (conf != NULL)
216		clear_config(conf);
217	conf = newconf;
218	conf->initstate.entered = time(NULL);
219	fetch_ifstate(0);
220	external_evtimer_setup(&conf->initstate, IFSD_EVTIMER_ADD);
221	adjust_external_expressions(&conf->initstate);
222	eval_state(&conf->initstate);
223	if (conf->curstate != NULL) {
224		log_info("initial state: %s", conf->curstate->name);
225		conf->curstate->entered = time(NULL);
226		conf->nextstate = conf->curstate;
227		conf->curstate = NULL;
228		while (state_change()) {
229			do_action(conf->curstate->init);
230			do_action(conf->curstate->body);
231		}
232	}
233	return (0);
234}
235
236void
237rt_msg_handler(int fd, short event, void *arg)
238{
239	char msg[2048];
240	struct rt_msghdr *rtm = (struct rt_msghdr *)&msg;
241	struct if_msghdr ifm;
242	struct if_announcemsghdr ifan;
243	char ifnamebuf[IFNAMSIZ];
244	char *ifname;
245	ssize_t len;
246
247	if ((len = read(fd, msg, sizeof(msg))) == -1) {
248		if (errno == EAGAIN || errno == EINTR)
249			return;
250		fatal("%s: routing socket read error", __func__);
251	}
252
253	if (len == 0)
254		fatal("%s: routing socket closed", __func__);
255
256	if (rtm->rtm_version != RTM_VERSION)
257		return;
258
259	switch (rtm->rtm_type) {
260	case RTM_IFINFO:
261		memcpy(&ifm, rtm, sizeof(ifm));
262		ifname = if_indextoname(ifm.ifm_index, ifnamebuf);
263		/* ifname is NULL on interface departure */
264		if (ifname != NULL)
265			scan_ifstate(ifname, ifm.ifm_data.ifi_link_state, 1);
266		break;
267	case RTM_IFANNOUNCE:
268		memcpy(&ifan, rtm, sizeof(ifan));
269		switch (ifan.ifan_what) {
270		case IFAN_DEPARTURE:
271			log_warnx("interface %s departed", ifan.ifan_name);
272			check_ifdeparture();
273			break;
274		case IFAN_ARRIVAL:
275			log_warnx("interface %s arrived", ifan.ifan_name);
276			fetch_ifstate(1);
277			break;
278		}
279		break;
280	case RTM_DESYNC:
281		/* we lost some routing messages so rescan interfaces */
282		check_ifdeparture();
283		fetch_ifstate(1);
284		break;
285	}
286	return;
287}
288
289void
290sigchld_handler(int fd, short event, void *arg)
291{
292	check_external_status(&conf->initstate);
293	if (conf->curstate != NULL)
294		check_external_status(conf->curstate);
295}
296
297void
298external_handler(int fd, short event, void *arg)
299{
300	struct ifsd_external *external = (struct ifsd_external *)arg;
301	struct timeval tv;
302
303	/* re-schedule */
304	timerclear(&tv);
305	tv.tv_sec = external->frequency;
306	evtimer_set(&external->ev, external_handler, external);
307	evtimer_add(&external->ev, &tv);
308
309	/* execute */
310	external_exec(external, 1);
311}
312
313void
314external_exec(struct ifsd_external *external, int async)
315{
316	char *argp[] = {"sh", "-c", NULL, NULL};
317	pid_t pid;
318	int s;
319
320	if (external->pid > 0) {
321		log_debug("previous command %s [%d] still running, killing it",
322		    external->command, external->pid);
323		kill(external->pid, SIGKILL);
324		waitpid(external->pid, &s, 0);
325		external->pid = 0;
326	}
327
328	argp[2] = external->command;
329	log_debug("running %s", external->command);
330	pid = fork();
331	if (pid == -1) {
332		log_warn("fork error");
333	} else if (pid == 0) {
334		execv(_PATH_BSHELL, argp);
335		_exit(1);
336		/* NOTREACHED */
337	} else {
338		external->pid = pid;
339	}
340	if (!async) {
341		waitpid(external->pid, &s, 0);
342		external->pid = 0;
343		if (WIFEXITED(s))
344			external->prevstatus = WEXITSTATUS(s);
345	}
346}
347
348void
349adjust_external_expressions(struct ifsd_state *state)
350{
351	struct ifsd_external *external;
352	struct ifsd_expression_list expressions;
353
354	TAILQ_INIT(&expressions);
355	TAILQ_FOREACH(external, &state->external_tests, entries) {
356		struct ifsd_expression *expression;
357
358		if (external->prevstatus == -1)
359			continue;
360
361		TAILQ_FOREACH(expression, &external->expressions, entries) {
362			TAILQ_INSERT_TAIL(&expressions,
363			    expression, eval);
364			expression->truth = !external->prevstatus;
365		}
366		adjust_expressions(&expressions, conf->maxdepth);
367	}
368}
369
370void
371check_external_status(struct ifsd_state *state)
372{
373	struct ifsd_external *external, *end = NULL;
374	int status, s, changed = 0;
375
376	/* Do this manually; change ordering so the oldest is first */
377	external = TAILQ_FIRST(&state->external_tests);
378	while (external != NULL && external != end) {
379		struct ifsd_external *newexternal;
380
381		newexternal = TAILQ_NEXT(external, entries);
382
383		if (external->pid <= 0)
384			goto loop;
385
386		if (wait4(external->pid, &s, WNOHANG, NULL) == 0)
387			goto loop;
388
389		external->pid = 0;
390		if (end == NULL)
391			end = external;
392		if (WIFEXITED(s))
393			status = WEXITSTATUS(s);
394		else {
395			log_warnx("%s exited abnormally", external->command);
396			goto loop;
397		}
398
399		if (external->prevstatus != status &&
400		    (external->prevstatus != -1 || !opt_inhibit)) {
401			changed = 1;
402			external->prevstatus = status;
403		}
404		external->lastexec = time(NULL);
405		TAILQ_REMOVE(&state->external_tests, external, entries);
406		TAILQ_INSERT_TAIL(&state->external_tests, external, entries);
407loop:
408		external = newexternal;
409	}
410
411	if (changed) {
412		adjust_external_expressions(state);
413		eval_state(state);
414	}
415}
416
417void
418external_evtimer_setup(struct ifsd_state *state, int action)
419{
420	struct ifsd_external *external;
421	int s;
422
423	if (state != NULL) {
424		switch (action) {
425		case IFSD_EVTIMER_ADD:
426			TAILQ_FOREACH(external,
427			    &state->external_tests, entries) {
428				struct timeval tv;
429
430				/* run it once right away */
431				external_exec(external, 0);
432
433				/* schedule it for later */
434				timerclear(&tv);
435				tv.tv_sec = external->frequency;
436				evtimer_set(&external->ev, external_handler,
437				    external);
438				evtimer_add(&external->ev, &tv);
439			}
440			break;
441		case IFSD_EVTIMER_DEL:
442			TAILQ_FOREACH(external,
443			    &state->external_tests, entries) {
444				if (external->pid > 0) {
445					kill(external->pid, SIGKILL);
446					waitpid(external->pid, &s, 0);
447					external->pid = 0;
448				}
449				evtimer_del(&external->ev);
450			}
451			break;
452		}
453	}
454}
455
456#define	LINK_STATE_IS_DOWN(_s)		(!LINK_STATE_IS_UP((_s)))
457
458int
459scan_ifstate_single(const char *ifname, int s, struct ifsd_state *state)
460{
461	struct ifsd_ifstate *ifstate;
462	struct ifsd_expression_list expressions;
463	int changed = 0;
464
465	TAILQ_INIT(&expressions);
466
467	TAILQ_FOREACH(ifstate, &state->interface_states, entries) {
468		if (strcmp(ifstate->ifname, ifname) == 0) {
469			if (ifstate->prevstate != s &&
470			    (ifstate->prevstate != -1 || !opt_inhibit)) {
471				struct ifsd_expression *expression;
472				int truth;
473
474				truth =
475				    (ifstate->ifstate == IFSD_LINKUNKNOWN &&
476				    s == LINK_STATE_UNKNOWN) ||
477				    (ifstate->ifstate == IFSD_LINKDOWN &&
478				    LINK_STATE_IS_DOWN(s)) ||
479				    (ifstate->ifstate == IFSD_LINKUP &&
480				    LINK_STATE_IS_UP(s));
481
482				TAILQ_FOREACH(expression,
483				    &ifstate->expressions, entries) {
484					expression->truth = truth;
485					TAILQ_INSERT_TAIL(&expressions,
486					    expression, eval);
487					changed = 1;
488				}
489				ifstate->prevstate = s;
490			}
491		}
492	}
493
494	if (changed)
495		adjust_expressions(&expressions, conf->maxdepth);
496	return (changed);
497}
498
499void
500scan_ifstate(const char *ifname, int s, int do_eval)
501{
502	struct ifsd_state *state;
503	int cur_eval = 0;
504
505	if (scan_ifstate_single(ifname, s, &conf->initstate) && do_eval)
506		eval_state(&conf->initstate);
507	TAILQ_FOREACH(state, &conf->states, entries) {
508		if (scan_ifstate_single(ifname, s, state) &&
509		    (do_eval && state == conf->curstate))
510			cur_eval = 1;
511	}
512	/* execute actions _after_ all expressions have been adjusted */
513	if (cur_eval)
514		eval_state(conf->curstate);
515}
516
517/*
518 * Do a bottom-up adjustment of the expression tree's truth value,
519 * level-by-level to ensure that each expression's subexpressions have been
520 * evaluated.
521 */
522void
523adjust_expressions(struct ifsd_expression_list *expressions, int depth)
524{
525	struct ifsd_expression_list nexpressions;
526	struct ifsd_expression *expression;
527
528	TAILQ_INIT(&nexpressions);
529	while ((expression = TAILQ_FIRST(expressions)) != NULL) {
530		TAILQ_REMOVE(expressions, expression, eval);
531		if (expression->depth == depth) {
532			struct ifsd_expression *te;
533
534			switch (expression->type) {
535			case IFSD_OPER_AND:
536				expression->truth = expression->left->truth &&
537				    expression->right->truth;
538				break;
539			case IFSD_OPER_OR:
540				expression->truth = expression->left->truth ||
541				    expression->right->truth;
542				break;
543			case IFSD_OPER_NOT:
544				expression->truth = !expression->right->truth;
545				break;
546			default:
547				break;
548			}
549			if (expression->parent != NULL) {
550				if (TAILQ_EMPTY(&nexpressions))
551					te = NULL;
552				TAILQ_FOREACH(te, &nexpressions, eval)
553					if (expression->parent == te)
554						break;
555				if (te == NULL)
556					TAILQ_INSERT_TAIL(&nexpressions,
557					    expression->parent, eval);
558			}
559		} else
560			TAILQ_INSERT_TAIL(&nexpressions, expression, eval);
561	}
562	if (depth > 0)
563		adjust_expressions(&nexpressions, depth - 1);
564}
565
566void
567eval_state(struct ifsd_state *state)
568{
569	struct ifsd_external *external;
570
571	external = TAILQ_FIRST(&state->external_tests);
572	if (external == NULL || external->lastexec >= state->entered ||
573	    external->lastexec == 0) {
574		do_action(state->body);
575		while (state_change()) {
576			do_action(conf->curstate->init);
577			do_action(conf->curstate->body);
578		}
579	}
580}
581
582int
583state_change(void)
584{
585	if (conf->nextstate != NULL && conf->curstate != conf->nextstate) {
586		log_info("changing state to %s", conf->nextstate->name);
587		if (conf->curstate != NULL) {
588			evtimer_del(&conf->curstate->ev);
589			external_evtimer_setup(conf->curstate,
590			    IFSD_EVTIMER_DEL);
591		}
592		conf->curstate = conf->nextstate;
593		conf->nextstate = NULL;
594		conf->curstate->entered = time(NULL);
595		external_evtimer_setup(conf->curstate, IFSD_EVTIMER_ADD);
596		adjust_external_expressions(conf->curstate);
597		return (1);
598	}
599	return (0);
600}
601
602/*
603 * Run recursively through the tree of actions.
604 */
605void
606do_action(struct ifsd_action *action)
607{
608	struct ifsd_action *subaction;
609
610	switch (action->type) {
611	case IFSD_ACTION_COMMAND:
612		log_debug("running %s", action->act.command);
613		system(action->act.command);
614		break;
615	case IFSD_ACTION_CHANGESTATE:
616		conf->nextstate = action->act.nextstate;
617		break;
618	case IFSD_ACTION_CONDITION:
619		if ((action->act.c.expression != NULL &&
620		    action->act.c.expression->truth) ||
621		    action->act.c.expression == NULL) {
622			TAILQ_FOREACH(subaction, &action->act.c.actions,
623			    entries)
624				do_action(subaction);
625		}
626		break;
627	default:
628		log_debug("%s: unknown action %d", __func__, action->type);
629		break;
630	}
631}
632
633/*
634 * Fetch the current link states.
635 */
636void
637fetch_ifstate(int do_eval)
638{
639	struct ifaddrs *ifap, *ifa;
640
641	if (getifaddrs(&ifap) != 0)
642		fatal("getifaddrs");
643
644	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
645		if (ifa->ifa_addr != NULL &&
646		    ifa->ifa_addr->sa_family == AF_LINK) {
647			struct if_data *ifdata = ifa->ifa_data;
648			scan_ifstate(ifa->ifa_name, ifdata->ifi_link_state,
649			    do_eval);
650		}
651	}
652
653	freeifaddrs(ifap);
654}
655
656void
657check_ifdeparture(void)
658{
659	struct ifsd_state *state;
660	struct ifsd_ifstate *ifstate;
661
662	TAILQ_FOREACH(state, &conf->states, entries) {
663		TAILQ_FOREACH(ifstate, &state->interface_states, entries) {
664			if (if_nametoindex(ifstate->ifname) == 0)
665				scan_ifstate(ifstate->ifname,
666				    LINK_STATE_DOWN, 1);
667		}
668	}
669}
670
671void
672clear_config(struct ifsd_config *oconf)
673{
674	struct ifsd_state *state;
675
676	external_evtimer_setup(&conf->initstate, IFSD_EVTIMER_DEL);
677	if (conf != NULL && conf->curstate != NULL)
678		external_evtimer_setup(conf->curstate, IFSD_EVTIMER_DEL);
679	while ((state = TAILQ_FIRST(&oconf->states)) != NULL) {
680		TAILQ_REMOVE(&oconf->states, state, entries);
681		remove_action(state->init, state);
682		remove_action(state->body, state);
683		free(state->name);
684		free(state);
685	}
686	remove_action(oconf->initstate.init, &oconf->initstate);
687	remove_action(oconf->initstate.body, &oconf->initstate);
688	free(oconf);
689}
690
691void
692remove_action(struct ifsd_action *action, struct ifsd_state *state)
693{
694	struct ifsd_action *subaction;
695
696	if (action == NULL || state == NULL)
697		return;
698
699	switch (action->type) {
700	case IFSD_ACTION_COMMAND:
701		free(action->act.command);
702		break;
703	case IFSD_ACTION_CHANGESTATE:
704		break;
705	case IFSD_ACTION_CONDITION:
706		if (action->act.c.expression != NULL)
707			remove_expression(action->act.c.expression, state);
708		while ((subaction =
709		    TAILQ_FIRST(&action->act.c.actions)) != NULL) {
710			TAILQ_REMOVE(&action->act.c.actions,
711			    subaction, entries);
712			remove_action(subaction, state);
713		}
714	}
715	free(action);
716}
717
718void
719remove_expression(struct ifsd_expression *expression,
720    struct ifsd_state *state)
721{
722	switch (expression->type) {
723	case IFSD_OPER_IFSTATE:
724		TAILQ_REMOVE(&expression->u.ifstate->expressions, expression,
725		    entries);
726		if (--expression->u.ifstate->refcount == 0) {
727			TAILQ_REMOVE(&state->interface_states,
728			    expression->u.ifstate, entries);
729			free(expression->u.ifstate);
730		}
731		break;
732	case IFSD_OPER_EXTERNAL:
733		TAILQ_REMOVE(&expression->u.external->expressions, expression,
734		    entries);
735		if (--expression->u.external->refcount == 0) {
736			TAILQ_REMOVE(&state->external_tests,
737			    expression->u.external, entries);
738			free(expression->u.external->command);
739			event_del(&expression->u.external->ev);
740			free(expression->u.external);
741		}
742		break;
743	default:
744		if (expression->left != NULL)
745			remove_expression(expression->left, state);
746		if (expression->right != NULL)
747			remove_expression(expression->right, state);
748		break;
749	}
750	free(expression);
751}
752