File: [local] / src / usr.sbin / ifstated / ifstated.c (download)
Revision 1.16, Mon Mar 15 19:42:33 2004 UTC (20 years, 2 months ago) by markus
Branch: MAIN
CVS Tags: OPENBSD_3_5_BASE, OPENBSD_3_5 Changes since 1.15: +4 -5 lines
call daemon(3) early; from tholo@; ok mcbride@
|
/* $OpenBSD: ifstated.c,v 1.16 2004/03/15 19:42:33 markus 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/ioctl.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <err.h>
#include <event.h>
#include <util.h>
#include <unistd.h>
#include <syslog.h>
#include <stdarg.h>
#include <ifaddrs.h>
#include "ifstated.h"
struct ifsd_config *conf = NULL, *newconf = NULL;
int opts = 0;
int opt_debug = 0;
int opt_inhibit = 0;
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_async_exec(struct ifsd_external *);
void check_external_status(struct ifsd_state *);
void external_evtimer_setup(struct ifsd_state *, int);
int scan_ifstate(int, int, struct ifsd_state *);
void fetch_state(void);
void usage(void);
void doconfig(const char*);
void adjust_expressions(struct ifsd_expression_list *, int);
void eval_state(struct ifsd_state *);
void 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 *);
void log_init(int);
void logit(int level, const char *fmt, ...);
void
usage(void)
{
extern char* __progname;
fprintf(stderr, "usage: %s [-dhinv] [-D macro=value] [-f config]\n",
__progname);
exit(1);
}
int
main(int argc, char *argv[])
{
int ch;
struct timeval tv;
while ((ch = getopt(argc, argv, "dD:f:hniv")) != -1) {
switch (ch) {
case 'd':
opt_debug = 1;
break;
case 'D':
if (cmdline_symset(optarg) < 0)
errx(1, "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();
}
}
if (opts & IFSD_OPT_NOACTION) {
if ((newconf = parse_config(configfile, opts)) == NULL)
exit(1);
warnx("configuration OK");
exit(0);
}
if (!opt_debug) {
daemon(0, 0);
setproctitle(NULL);
}
event_init();
log_init(opt_debug);
signal_set(&sigchld_ev, SIGCHLD, sigchld_handler, &sigchld_ev);
signal_add(&sigchld_ev, NULL);
/* Loading the config needs to happen in the event loop */
tv.tv_usec = 0;
tv.tv_sec = 0;
evtimer_set(&startup_ev, startup_handler, &startup_ev);
evtimer_add(&startup_ev, &tv);
event_loop(0);
exit(0);
}
void
startup_handler(int fd, short event, void *arg)
{
int rt_fd;
if (load_config() != 0) {
logit(IFSD_LOG_NORMAL, "unable to load config");
exit(1);
}
if ((rt_fd = socket(PF_ROUTE, SOCK_RAW, 0)) < 0)
err(1, "no routing socket");
event_set(&rt_msg_ev, rt_fd, EV_READ|EV_PERSIST,
rt_msg_handler, &rt_msg_ev);
event_add(&rt_msg_ev, NULL);
signal_set(&sighup_ev, SIGHUP, sighup_handler, &sighup_ev);
signal_add(&sighup_ev, NULL);
logit(IFSD_LOG_NORMAL, "started");
}
void
sighup_handler(int fd, short event, void *arg)
{
logit(IFSD_LOG_NORMAL, "reloading config");
if (load_config() != 0)
logit(IFSD_LOG_NORMAL, "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->always.entered = time(NULL);
fetch_state();
eval_state(&conf->always);
if (conf->curstate != NULL) {
logit(IFSD_LOG_NORMAL,
"initial state: %s", conf->curstate->name);
conf->curstate->entered = time(NULL);
eval_state(conf->curstate);
}
external_evtimer_setup(&conf->always, IFSD_EVTIMER_ADD);
return (0);
}
void
rt_msg_handler(int fd, short event, void *arg)
{
struct if_msghdr ifm;
char msg[2048];
struct rt_msghdr *rtm = (struct rt_msghdr *)&msg;
int len;
len = read(fd, msg, sizeof(msg));
/* XXX ignore errors? */
if (len < sizeof(struct rt_msghdr))
return;
if (rtm->rtm_version != RTM_VERSION)
return;
if (rtm->rtm_type != RTM_IFINFO)
return;
memcpy(&ifm, rtm, sizeof(ifm));
if (scan_ifstate(ifm.ifm_index, ifm.ifm_data.ifi_link_state,
&conf->always))
eval_state(&conf->always);
if ((conf->curstate != NULL) && scan_ifstate(ifm.ifm_index,
ifm.ifm_data.ifi_link_state, conf->curstate))
eval_state(conf->curstate);
}
void
sigchld_handler(int fd, short event, void *arg)
{
check_external_status(&conf->always);
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 */
tv.tv_usec = 0;
tv.tv_sec = external->frequency;
evtimer_set(&external->ev, external_handler, external);
evtimer_add(&external->ev, &tv);
/* execute */
external_async_exec(external);
}
void
external_async_exec(struct ifsd_external *external)
{
pid_t pid;
char *argp[] = {"sh", "-c", NULL, NULL};
if (external->pid > 0) {
logit(IFSD_LOG_NORMAL,
"previous command %s still running, killing it",
external->command);
kill(external->pid, SIGKILL);
external->pid = 0;
}
argp[2] = external->command;
logit(IFSD_LOG_VERBOSE, "running %s", external->command);
pid = fork();
if (pid < 0) {
logit(IFSD_LOG_QUIET, "fork error");
} else if (pid == 0) {
execv("/bin/sh", argp);
_exit(1);
/* NOTREACHED */
} else {
external->pid = pid;
}
}
void
check_external_status(struct ifsd_state *state)
{
struct ifsd_external *external, *end = NULL;
struct ifsd_expression_list expressions;
int status, s, changed = 0;
TAILQ_INIT(&expressions);
/* 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 {
logit(IFSD_LOG_QUIET,
"%s exited abnormally", external->command);
goto loop;
}
if (external->prevstatus != status &&
(external->prevstatus != -1 || !opt_inhibit)) {
struct ifsd_expression *expression;
changed = 1;
TAILQ_FOREACH(expression,
&external->expressions, entries) {
TAILQ_INSERT_TAIL(&expressions,
expression, eval);
if (status == 0)
expression->truth = 1;
else
expression->truth = 0;
}
}
external->lastexec = time(NULL);
TAILQ_REMOVE(&state->external_tests, external, entries);
TAILQ_INSERT_TAIL(&state->external_tests, external, entries);
external->prevstatus = status;
loop:
external = newexternal;
}
if (changed) {
adjust_expressions(&expressions, conf->maxdepth);
eval_state(state);
}
}
void
external_evtimer_setup(struct ifsd_state *state, int action)
{
struct ifsd_external *external;
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_async_exec(external);
/* schedule it for later */
tv.tv_usec = 0;
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);
external->pid = 0;
}
evtimer_del(&external->ev);
}
break;
}
}
}
int
scan_ifstate(int ifindex, 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 (ifstate->ifindex == ifindex) {
if (ifstate->prevstate != s &&
(ifstate->prevstate != -1 || !opt_inhibit)) {
struct ifsd_expression *expression;
int truth;
if (ifstate->ifstate == s)
truth = 1;
else
truth = 0;
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);
}
/*
* 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:
if (expression->left->truth &&
expression->right->truth)
expression->truth = 1;
else
expression->truth = 0;
break;
case IFSD_OPER_OR:
if (expression->left->truth ||
expression->right->truth)
expression->truth = 1;
else
expression->truth = 0;
break;
case IFSD_OPER_NOT:
if (expression->right->truth)
expression->truth = 0;
else
expression->truth = 1;
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 = TAILQ_FIRST(&state->external_tests);
if (external == NULL || external->lastexec >= state->entered) {
do_action(state->always);
state_change();
}
}
/*
*If a previous action included a state change, process it.
*/
void
state_change(void)
{
if (conf->nextstate != NULL && conf->curstate != conf->nextstate) {
logit(IFSD_LOG_NORMAL, "changing state to %s",
conf->nextstate->name);
evtimer_del(&conf->curstate->ev);
if (conf->curstate != NULL)
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);
fetch_state();
do_action(conf->curstate->init);
fetch_state();
}
}
/*
* 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:
logit(IFSD_LOG_NORMAL, "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:
logit(IFSD_LOG_DEBUG, "do_action: unknown action %d",
action->type);
break;
}
}
/*
* Fetch the current link states.
*/
void
fetch_state(void)
{
struct ifaddrs *ifap, *ifa;
char *oname = NULL;
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (getifaddrs(&ifap) != 0)
err(1, "getifaddrs");
for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
struct ifreq ifr;
struct if_data ifrdat;
if (oname && !strcmp(oname, ifa->ifa_name))
continue;
oname = ifa->ifa_name;
strlcpy(ifr.ifr_name, ifa->ifa_name, sizeof(ifr.ifr_name));
ifr.ifr_data = (caddr_t)&ifrdat;
if (ioctl(sock, SIOCGIFDATA, (caddr_t)&ifr) == -1)
continue;
scan_ifstate(if_nametoindex(ifa->ifa_name),
ifrdat.ifi_link_state, &conf->always);
if (conf->curstate != NULL)
scan_ifstate(if_nametoindex(ifa->ifa_name),
ifrdat.ifi_link_state, conf->curstate);
}
freeifaddrs(ifap);
close(sock);
}
/*
* Clear the config.
*/
void
clear_config(struct ifsd_config *oconf)
{
struct ifsd_state *state;
external_evtimer_setup(&conf->always, 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->always, state);
free(state->name);
free(state);
}
remove_action(oconf->always.init, &oconf->always);
remove_action(oconf->always.always, &oconf->always);
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_LOG:
free(action->act.logmessage);
break;
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);
}
void
log_init(int n_debug)
{
extern char *__progname;
if (!n_debug)
openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
}
void
logit(int level, const char *fmt, ...)
{
va_list ap;
char *nfmt;
if (conf == NULL || level > conf->loglevel)
return;
va_start(ap, fmt);
if (opt_debug) {
/* best effort in out of mem situations */
if (asprintf(&nfmt, "ifstated: %s\n", fmt) != -1) {
vfprintf(stderr, nfmt, ap);
free(nfmt);
} else {
vfprintf(stderr, fmt, ap);
fprintf(stderr, "\n");
}
} else
vsyslog(LOG_DAEMON, fmt, ap);
va_end(ap);
}