Edit

IABSD.fr/src/usr.sbin/ifstated/ifstated.c

Branch :

  • Show log

    Commit

  • Author : beck
    Date : 2021-07-12 15:09:18
    Hash : bc5a8259
    Message : Change the error reporting pattern throughout the tree when unveil fails to report the path that the failure occured on. Suggested by deraadt@ after some tech discussion. Work done and verified by Ashton Fagg <ashton@fagg.id.au> ok deraadt@ semarie@ claudio@

  • usr.sbin/ifstated/ifstated.c
  • /*	$OpenBSD: ifstated.c,v 1.66 2021/07/12 15:09:21 beck Exp $	*/
    
    /*
     * Copyright (c) 2004 Marco Pfatschbacher <mpf@openbsd.org>
     * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    /*
     * ifstated listens to link_state transitions on interfaces
     * and executes predefined commands.
     */
    
    #include <sys/types.h>
    #include <sys/time.h>
    #include <sys/socket.h>
    #include <sys/wait.h>
    
    #include <net/if.h>
    #include <net/route.h>
    #include <netinet/in.h>
    
    #include <paths.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <signal.h>
    #include <stdint.h>
    #include <syslog.h>
    #include <errno.h>
    #include <event.h>
    #include <unistd.h>
    #include <ifaddrs.h>
    
    #include "ifstated.h"
    #include "log.h"
    
    struct	 ifsd_config *conf, *newconf;
    
    int	 opts;
    int	 opt_inhibit;
    char	*configfile = "/etc/ifstated.conf";
    struct event	rt_msg_ev, sighup_ev, startup_ev, sigchld_ev;
    
    void		startup_handler(int, short, void *);
    void		sighup_handler(int, short, void *);
    int		load_config(void);
    void		sigchld_handler(int, short, void *);
    void		rt_msg_handler(int, short, void *);
    void		external_handler(int, short, void *);
    void		external_exec(struct ifsd_external *, int);
    void		check_external_status(struct ifsd_state *);
    void		check_ifdeparture(void);
    void		external_evtimer_setup(struct ifsd_state *, int);
    void		scan_ifstate(const char *, int, int);
    int		scan_ifstate_single(const char *, int, struct ifsd_state *);
    void		fetch_ifstate(int);
    __dead void	usage(void);
    void		adjust_expressions(struct ifsd_expression_list *, int);
    void		adjust_external_expressions(struct ifsd_state *);
    void		eval_state(struct ifsd_state *);
    int		state_change(void);
    void		do_action(struct ifsd_action *);
    void		remove_action(struct ifsd_action *, struct ifsd_state *);
    void		remove_expression(struct ifsd_expression *,
    		    struct ifsd_state *);
    
    __dead void
    usage(void)
    {
    	extern char *__progname;
    
    	fprintf(stderr, "usage: %s [-dhinv] [-D macro=value] [-f file]\n",
    	    __progname);
    	exit(1);
    }
    
    int
    main(int argc, char *argv[])
    {
    	struct timeval tv;
    	int ch, rt_fd;
    	int debug = 0;
    	unsigned int rtfilter;
    
    	log_init(1, LOG_DAEMON);	/* log to stderr until daemonized */
    	log_setverbose(1);
    
    	while ((ch = getopt(argc, argv, "dD:f:hniv")) != -1) {
    		switch (ch) {
    		case 'd':
    			debug = 1;
    			break;
    		case 'D':
    			if (cmdline_symset(optarg) < 0)
    				fatalx("could not parse macro definition %s",
    				    optarg);
    			break;
    		case 'f':
    			configfile = optarg;
    			break;
    		case 'h':
    			usage();
    			break;
    		case 'n':
    			opts |= IFSD_OPT_NOACTION;
    			break;
    		case 'i':
    			opt_inhibit = 1;
    			break;
    		case 'v':
    			if (opts & IFSD_OPT_VERBOSE)
    				opts |= IFSD_OPT_VERBOSE2;
    			opts |= IFSD_OPT_VERBOSE;
    			break;
    		default:
    			usage();
    		}
    	}
    
    	argc -= optind;
    	argv += optind;
    	if (argc > 0)
    		usage();
    
    	if (opts & IFSD_OPT_NOACTION) {
    		if ((newconf = parse_config(configfile, opts)) == NULL)
    			exit(1);
    		fprintf(stderr, "configuration OK\n");
    		exit(0);
    	}
    
    	if (!debug)
    		daemon(1, 0);
    
    	event_init();
    	log_init(debug, LOG_DAEMON);
    	log_setverbose(opts & IFSD_OPT_VERBOSE);
    
    	if ((rt_fd = socket(AF_ROUTE, SOCK_RAW, 0)) == -1)
    		fatal("no routing socket");
    
    	rtfilter = ROUTE_FILTER(RTM_IFINFO) | ROUTE_FILTER(RTM_IFANNOUNCE);
    	if (setsockopt(rt_fd, AF_ROUTE, ROUTE_MSGFILTER,
    	    &rtfilter, sizeof(rtfilter)) == -1)	/* not fatal */
    		log_warn("%s: setsockopt msgfilter", __func__);
    
    	rtfilter = RTABLE_ANY;
    	if (setsockopt(rt_fd, AF_ROUTE, ROUTE_TABLEFILTER,
    	    &rtfilter, sizeof(rtfilter)) == -1)	/* not fatal */
    		log_warn("%s: setsockopt tablefilter", __func__);
    
    	if (unveil(configfile, "r") == -1)
    		fatal("unveil %s", configfile);
    	if (unveil(_PATH_BSHELL, "x") == -1)
    		fatal("unveil %s", _PATH_BSHELL);
    	if (pledge("stdio rpath route proc exec", NULL) == -1)
    		fatal("pledge");
    
    	signal_set(&sigchld_ev, SIGCHLD, sigchld_handler, NULL);
    	signal_add(&sigchld_ev, NULL);
    
    	/* Loading the config needs to happen in the event loop */
    	timerclear(&tv);
    	evtimer_set(&startup_ev, startup_handler, (void *)(long)rt_fd);
    	evtimer_add(&startup_ev, &tv);
    
    	event_loop(0);
    	exit(0);
    }
    
    void
    startup_handler(int fd, short event, void *arg)
    {
    	int rfd = (int)(long)arg;
    
    	if (load_config() != 0) {
    		log_warnx("unable to load config");
    		exit(1);
    	}
    
    	event_set(&rt_msg_ev, rfd, EV_READ|EV_PERSIST, rt_msg_handler, NULL);
    	event_add(&rt_msg_ev, NULL);
    
    	signal_set(&sighup_ev, SIGHUP, sighup_handler, NULL);
    	signal_add(&sighup_ev, NULL);
    
    	log_info("started");
    }
    
    void
    sighup_handler(int fd, short event, void *arg)
    {
    	log_info("reloading config");
    	if (load_config() != 0)
    		log_warnx("unable to reload config");
    }
    
    int
    load_config(void)
    {
    	if ((newconf = parse_config(configfile, opts)) == NULL)
    		return (-1);
    	if (conf != NULL)
    		clear_config(conf);
    	conf = newconf;
    	conf->initstate.entered = time(NULL);
    	fetch_ifstate(0);
    	external_evtimer_setup(&conf->initstate, IFSD_EVTIMER_ADD);
    	adjust_external_expressions(&conf->initstate);
    	eval_state(&conf->initstate);
    	if (conf->curstate != NULL) {
    		log_info("initial state: %s", conf->curstate->name);
    		conf->curstate->entered = time(NULL);
    		conf->nextstate = conf->curstate;
    		conf->curstate = NULL;
    		while (state_change()) {
    			do_action(conf->curstate->init);
    			do_action(conf->curstate->body);
    		}
    	}
    	return (0);
    }
    
    void
    rt_msg_handler(int fd, short event, void *arg)
    {
    	char msg[2048];
    	struct rt_msghdr *rtm = (struct rt_msghdr *)&msg;
    	struct if_msghdr ifm;
    	struct if_announcemsghdr ifan;
    	char ifnamebuf[IFNAMSIZ];
    	char *ifname;
    	ssize_t len;
    
    	if ((len = read(fd, msg, sizeof(msg))) == -1) {
    		if (errno == EAGAIN || errno == EINTR)
    			return;
    		fatal("%s: routing socket read error", __func__);
    	}
    
    	if (len == 0)
    		fatal("%s: routing socket closed", __func__);
    
    	if (rtm->rtm_version != RTM_VERSION)
    		return;
    
    	switch (rtm->rtm_type) {
    	case RTM_IFINFO:
    		memcpy(&ifm, rtm, sizeof(ifm));
    		ifname = if_indextoname(ifm.ifm_index, ifnamebuf);
    		/* ifname is NULL on interface departure */
    		if (ifname != NULL)
    			scan_ifstate(ifname, ifm.ifm_data.ifi_link_state, 1);
    		break;
    	case RTM_IFANNOUNCE:
    		memcpy(&ifan, rtm, sizeof(ifan));
    		switch (ifan.ifan_what) {
    		case IFAN_DEPARTURE:
    			log_warnx("interface %s departed", ifan.ifan_name);
    			check_ifdeparture();
    			break;
    		case IFAN_ARRIVAL:
    			log_warnx("interface %s arrived", ifan.ifan_name);
    			fetch_ifstate(1);
    			break;
    		}
    		break;
    	case RTM_DESYNC:
    		/* we lost some routing messages so rescan interfaces */
    		check_ifdeparture();
    		fetch_ifstate(1);
    		break;
    	}
    	return;
    }
    
    void
    sigchld_handler(int fd, short event, void *arg)
    {
    	check_external_status(&conf->initstate);
    	if (conf->curstate != NULL)
    		check_external_status(conf->curstate);
    }
    
    void
    external_handler(int fd, short event, void *arg)
    {
    	struct ifsd_external *external = (struct ifsd_external *)arg;
    	struct timeval tv;
    
    	/* re-schedule */
    	timerclear(&tv);
    	tv.tv_sec = external->frequency;
    	evtimer_set(&external->ev, external_handler, external);
    	evtimer_add(&external->ev, &tv);
    
    	/* execute */
    	external_exec(external, 1);
    }
    
    void
    external_exec(struct ifsd_external *external, int async)
    {
    	char *argp[] = {"sh", "-c", NULL, NULL};
    	pid_t pid;
    	int s;
    
    	if (external->pid > 0) {
    		log_debug("previous command %s [%d] still running, killing it",
    		    external->command, external->pid);
    		kill(external->pid, SIGKILL);
    		waitpid(external->pid, &s, 0);
    		external->pid = 0;
    	}
    
    	argp[2] = external->command;
    	log_debug("running %s", external->command);
    	pid = fork();
    	if (pid == -1) {
    		log_warn("fork error");
    	} else if (pid == 0) {
    		execv(_PATH_BSHELL, argp);
    		_exit(1);
    		/* NOTREACHED */
    	} else {
    		external->pid = pid;
    	}
    	if (!async) {
    		waitpid(external->pid, &s, 0);
    		external->pid = 0;
    		if (WIFEXITED(s))
    			external->prevstatus = WEXITSTATUS(s);
    	}
    }
    
    void
    adjust_external_expressions(struct ifsd_state *state)
    {
    	struct ifsd_external *external;
    	struct ifsd_expression_list expressions;
    
    	TAILQ_INIT(&expressions);
    	TAILQ_FOREACH(external, &state->external_tests, entries) {
    		struct ifsd_expression *expression;
    
    		if (external->prevstatus == -1)
    			continue;
    
    		TAILQ_FOREACH(expression, &external->expressions, entries) {
    			TAILQ_INSERT_TAIL(&expressions,
    			    expression, eval);
    			expression->truth = !external->prevstatus;
    		}
    		adjust_expressions(&expressions, conf->maxdepth);
    	}
    }
    
    void
    check_external_status(struct ifsd_state *state)
    {
    	struct ifsd_external *external, *end = NULL;
    	int status, s, changed = 0;
    
    	/* Do this manually; change ordering so the oldest is first */
    	external = TAILQ_FIRST(&state->external_tests);
    	while (external != NULL && external != end) {
    		struct ifsd_external *newexternal;
    
    		newexternal = TAILQ_NEXT(external, entries);
    
    		if (external->pid <= 0)
    			goto loop;
    
    		if (wait4(external->pid, &s, WNOHANG, NULL) == 0)
    			goto loop;
    
    		external->pid = 0;
    		if (end == NULL)
    			end = external;
    		if (WIFEXITED(s))
    			status = WEXITSTATUS(s);
    		else {
    			log_warnx("%s exited abnormally", external->command);
    			goto loop;
    		}
    
    		if (external->prevstatus != status &&
    		    (external->prevstatus != -1 || !opt_inhibit)) {
    			changed = 1;
    			external->prevstatus = status;
    		}
    		external->lastexec = time(NULL);
    		TAILQ_REMOVE(&state->external_tests, external, entries);
    		TAILQ_INSERT_TAIL(&state->external_tests, external, entries);
    loop:
    		external = newexternal;
    	}
    
    	if (changed) {
    		adjust_external_expressions(state);
    		eval_state(state);
    	}
    }
    
    void
    external_evtimer_setup(struct ifsd_state *state, int action)
    {
    	struct ifsd_external *external;
    	int s;
    
    	if (state != NULL) {
    		switch (action) {
    		case IFSD_EVTIMER_ADD:
    			TAILQ_FOREACH(external,
    			    &state->external_tests, entries) {
    				struct timeval tv;
    
    				/* run it once right away */
    				external_exec(external, 0);
    
    				/* schedule it for later */
    				timerclear(&tv);
    				tv.tv_sec = external->frequency;
    				evtimer_set(&external->ev, external_handler,
    				    external);
    				evtimer_add(&external->ev, &tv);
    			}
    			break;
    		case IFSD_EVTIMER_DEL:
    			TAILQ_FOREACH(external,
    			    &state->external_tests, entries) {
    				if (external->pid > 0) {
    					kill(external->pid, SIGKILL);
    					waitpid(external->pid, &s, 0);
    					external->pid = 0;
    				}
    				evtimer_del(&external->ev);
    			}
    			break;
    		}
    	}
    }
    
    #define	LINK_STATE_IS_DOWN(_s)		(!LINK_STATE_IS_UP((_s)))
    
    int
    scan_ifstate_single(const char *ifname, int s, struct ifsd_state *state)
    {
    	struct ifsd_ifstate *ifstate;
    	struct ifsd_expression_list expressions;
    	int changed = 0;
    
    	TAILQ_INIT(&expressions);
    
    	TAILQ_FOREACH(ifstate, &state->interface_states, entries) {
    		if (strcmp(ifstate->ifname, ifname) == 0) {
    			if (ifstate->prevstate != s &&
    			    (ifstate->prevstate != -1 || !opt_inhibit)) {
    				struct ifsd_expression *expression;
    				int truth;
    
    				truth =
    				    (ifstate->ifstate == IFSD_LINKUNKNOWN &&
    				    s == LINK_STATE_UNKNOWN) ||
    				    (ifstate->ifstate == IFSD_LINKDOWN &&
    				    LINK_STATE_IS_DOWN(s)) ||
    				    (ifstate->ifstate == IFSD_LINKUP &&
    				    LINK_STATE_IS_UP(s));
    
    				TAILQ_FOREACH(expression,
    				    &ifstate->expressions, entries) {
    					expression->truth = truth;
    					TAILQ_INSERT_TAIL(&expressions,
    					    expression, eval);
    					changed = 1;
    				}
    				ifstate->prevstate = s;
    			}
    		}
    	}
    
    	if (changed)
    		adjust_expressions(&expressions, conf->maxdepth);
    	return (changed);
    }
    
    void
    scan_ifstate(const char *ifname, int s, int do_eval)
    {
    	struct ifsd_state *state;
    	int cur_eval = 0;
    
    	if (scan_ifstate_single(ifname, s, &conf->initstate) && do_eval)
    		eval_state(&conf->initstate);
    	TAILQ_FOREACH(state, &conf->states, entries) {
    		if (scan_ifstate_single(ifname, s, state) &&
    		    (do_eval && state == conf->curstate))
    			cur_eval = 1;
    	}
    	/* execute actions _after_ all expressions have been adjusted */
    	if (cur_eval)
    		eval_state(conf->curstate);
    }
    
    /*
     * Do a bottom-up ajustment of the expression tree's truth value,
     * level-by-level to ensure that each expression's subexpressions have been
     * evaluated.
     */
    void
    adjust_expressions(struct ifsd_expression_list *expressions, int depth)
    {
    	struct ifsd_expression_list nexpressions;
    	struct ifsd_expression *expression;
    
    	TAILQ_INIT(&nexpressions);
    	while ((expression = TAILQ_FIRST(expressions)) != NULL) {
    		TAILQ_REMOVE(expressions, expression, eval);
    		if (expression->depth == depth) {
    			struct ifsd_expression *te;
    
    			switch (expression->type) {
    			case IFSD_OPER_AND:
    				expression->truth = expression->left->truth &&
    				    expression->right->truth;
    				break;
    			case IFSD_OPER_OR:
    				expression->truth = expression->left->truth ||
    				    expression->right->truth;
    				break;
    			case IFSD_OPER_NOT:
    				expression->truth = !expression->right->truth;
    				break;
    			default:
    				break;
    			}
    			if (expression->parent != NULL) {
    				if (TAILQ_EMPTY(&nexpressions))
    				te = NULL;
    				TAILQ_FOREACH(te, &nexpressions, eval)
    					if (expression->parent == te)
    						break;
    				if (te == NULL)
    					TAILQ_INSERT_TAIL(&nexpressions,
    					    expression->parent, eval);
    			}
    		} else
    			TAILQ_INSERT_TAIL(&nexpressions, expression, eval);
    	}
    	if (depth > 0)
    		adjust_expressions(&nexpressions, depth - 1);
    }
    
    void
    eval_state(struct ifsd_state *state)
    {
    	struct ifsd_external *external;
    
    	external = TAILQ_FIRST(&state->external_tests);
    	if (external == NULL || external->lastexec >= state->entered ||
    	    external->lastexec == 0) {
    		do_action(state->body);
    		while (state_change()) {
    			do_action(conf->curstate->init);
    			do_action(conf->curstate->body);
    		}
    	}
    }
    
    int
    state_change(void)
    {
    	if (conf->nextstate != NULL && conf->curstate != conf->nextstate) {
    		log_info("changing state to %s", conf->nextstate->name);
    		if (conf->curstate != NULL) {
    			evtimer_del(&conf->curstate->ev);
    			external_evtimer_setup(conf->curstate,
    			    IFSD_EVTIMER_DEL);
    		}
    		conf->curstate = conf->nextstate;
    		conf->nextstate = NULL;
    		conf->curstate->entered = time(NULL);
    		external_evtimer_setup(conf->curstate, IFSD_EVTIMER_ADD);
    		adjust_external_expressions(conf->curstate);
    		return (1);
    	}
    	return (0);
    }
    
    /*
     * Run recursively through the tree of actions.
     */
    void
    do_action(struct ifsd_action *action)
    {
    	struct ifsd_action *subaction;
    
    	switch (action->type) {
    	case IFSD_ACTION_COMMAND:
    		log_debug("running %s", action->act.command);
    		system(action->act.command);
    		break;
    	case IFSD_ACTION_CHANGESTATE:
    		conf->nextstate = action->act.nextstate;
    		break;
    	case IFSD_ACTION_CONDITION:
    		if ((action->act.c.expression != NULL &&
    		    action->act.c.expression->truth) ||
    		    action->act.c.expression == NULL) {
    			TAILQ_FOREACH(subaction, &action->act.c.actions,
    			    entries)
    				do_action(subaction);
    		}
    		break;
    	default:
    		log_debug("%s: unknown action %d", __func__, action->type);
    		break;
    	}
    }
    
    /*
     * Fetch the current link states.
     */
    void
    fetch_ifstate(int do_eval)
    {
    	struct ifaddrs *ifap, *ifa;
    
    	if (getifaddrs(&ifap) != 0)
    		fatal("getifaddrs");
    
    	for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
    		if (ifa->ifa_addr != NULL &&
    		    ifa->ifa_addr->sa_family == AF_LINK) {
    			struct if_data *ifdata = ifa->ifa_data;
    			scan_ifstate(ifa->ifa_name, ifdata->ifi_link_state,
    			    do_eval);
    		}
    	}
    
    	freeifaddrs(ifap);
    }
    
    void
    check_ifdeparture(void)
    {
    	struct ifsd_state *state;
    	struct ifsd_ifstate *ifstate;
    
    	TAILQ_FOREACH(state, &conf->states, entries) {
    		TAILQ_FOREACH(ifstate, &state->interface_states, entries) {
    			if (if_nametoindex(ifstate->ifname) == 0)
    				scan_ifstate(ifstate->ifname,
    				    LINK_STATE_DOWN, 1);
    		}
    	}
    }
    
    void
    clear_config(struct ifsd_config *oconf)
    {
    	struct ifsd_state *state;
    
    	external_evtimer_setup(&conf->initstate, IFSD_EVTIMER_DEL);
    	if (conf != NULL && conf->curstate != NULL)
    		external_evtimer_setup(conf->curstate, IFSD_EVTIMER_DEL);
    	while ((state = TAILQ_FIRST(&oconf->states)) != NULL) {
    		TAILQ_REMOVE(&oconf->states, state, entries);
    		remove_action(state->init, state);
    		remove_action(state->body, state);
    		free(state->name);
    		free(state);
    	}
    	remove_action(oconf->initstate.init, &oconf->initstate);
    	remove_action(oconf->initstate.body, &oconf->initstate);
    	free(oconf);
    }
    
    void
    remove_action(struct ifsd_action *action, struct ifsd_state *state)
    {
    	struct ifsd_action *subaction;
    
    	if (action == NULL || state == NULL)
    		return;
    
    	switch (action->type) {
    	case IFSD_ACTION_COMMAND:
    		free(action->act.command);
    		break;
    	case IFSD_ACTION_CHANGESTATE:
    		break;
    	case IFSD_ACTION_CONDITION:
    		if (action->act.c.expression != NULL)
    			remove_expression(action->act.c.expression, state);
    		while ((subaction =
    		    TAILQ_FIRST(&action->act.c.actions)) != NULL) {
    			TAILQ_REMOVE(&action->act.c.actions,
    			    subaction, entries);
    			remove_action(subaction, state);
    		}
    	}
    	free(action);
    }
    
    void
    remove_expression(struct ifsd_expression *expression,
        struct ifsd_state *state)
    {
    	switch (expression->type) {
    	case IFSD_OPER_IFSTATE:
    		TAILQ_REMOVE(&expression->u.ifstate->expressions, expression,
    		    entries);
    		if (--expression->u.ifstate->refcount == 0) {
    			TAILQ_REMOVE(&state->interface_states,
    			    expression->u.ifstate, entries);
    			free(expression->u.ifstate);
    		}
    		break;
    	case IFSD_OPER_EXTERNAL:
    		TAILQ_REMOVE(&expression->u.external->expressions, expression,
    		    entries);
    		if (--expression->u.external->refcount == 0) {
    			TAILQ_REMOVE(&state->external_tests,
    			    expression->u.external, entries);
    			free(expression->u.external->command);
    			event_del(&expression->u.external->ev);
    			free(expression->u.external);
    		}
    		break;
    	default:
    		if (expression->left != NULL)
    			remove_expression(expression->left, state);
    		if (expression->right != NULL)
    			remove_expression(expression->right, state);
    		break;
    	}
    	free(expression);
    }