diff options
Diffstat (limited to 'src/ifplugd.c')
-rw-r--r-- | src/ifplugd.c | 792 |
1 files changed, 792 insertions, 0 deletions
diff --git a/src/ifplugd.c b/src/ifplugd.c new file mode 100644 index 0000000..9445383 --- /dev/null +++ b/src/ifplugd.c @@ -0,0 +1,792 @@ +/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ +/* $Id: ifplugd.c 1.12 Sat, 01 Feb 2003 03:00:07 +0100 lennart $ */ + +/* + * This file is part of ifplugd. + * + * ifplugd is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * ifplugd is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with ifplugd; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + */ + +#define _GNU_SOURCE + +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <signal.h> +#include <unistd.h> +#include <sys/wait.h> +#include <errno.h> +#include <sys/stat.h> +#include <getopt.h> +#include <stdarg.h> +#include <syslog.h> +#include <fcntl.h> +#include <linux/kd.h> +#include <sys/ioctl.h> +#include <limits.h> +#include <net/if.h> +#include <linux/sockios.h> +#include <sys/types.h> +#include <ctype.h> +#include <sys/time.h> +#include <time.h> + +#include <libdaemon/dlog.h> +#include <libdaemon/dpid.h> +#include <libdaemon/dsignal.h> +#include <libdaemon/dfork.h> + +#include "ethtool-local.h" +#include "interface.h" +#include "svn-revision.h" + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#define VARRUN "/var/run" +#define IFPLUGD_ENV_PREVIOUS "IFPLUGD_PREVIOUS" +#define IFPLUGD_ENV_CURRENT "IFPLUGD_CURRENT" + +int interface_auto_up = 1, interface_do_message = 1; + +char *interface = "eth0"; +char *run = SYSCONFDIR"/ifplugd/ifplugd.action"; +char *extra_arg = NULL; +int polltime = 1, delay_up = 0, delay_down = 5; +int daemonize = 1, use_beep = 1, no_shutdown_script = 0, wait_on_fork = 0, use_syslog = 1, ignore_retval = 0, initial_down = 0; +interface_status_t failure_status = IFSTATUS_ERR; + +enum { API_AUTO, API_ETHTOOL, API_MII, API_PRIVATE, API_WLAN } api_mode = API_AUTO; +interface_status_t (*detect_beat_func)(int, char*); + +// 0: high, 1: low, 2: very low +void beep(int b) { + int fd = -1, argp; + + if (!use_beep) + return; + + if ((fd = open("/dev/tty1", O_WRONLY)) < 0) { + use_beep = 0; + daemon_log(LOG_WARNING, "Could not open /dev/tty, cannot beep."); + goto finish; + } + + switch (b) { + case 0: argp = (100<<16) + 0x637; break; + case 1: argp = (100<<16) + 0x937; break; + default: argp = (100<<16) + 0x1237; break; + } + + if (ioctl(fd, KDMKTONE, argp) != 0) { + use_beep = 0; + daemon_log(LOG_WARNING, "Beep failure, disabled."); + goto finish; + } + + usleep((argp >> 16)*1000); + +finish: + + if (fd >= 0) + close(fd); + + return; +} + +const char *pid_file_proc() { + static char fn[PATH_MAX]; + snprintf(fn, sizeof(fn), "%s/ifplugd.%s.pid", VARRUN, interface); + return fn; +} + +int action(interface_status_t status) { + pid_t pid; + int _pipe[2]; + unsigned n = 0; + static char buf[256]; + char *arg = (status == IFSTATUS_UP ? "up" : "down"); + int sigfd, r; + fd_set rfds; + + daemon_log(LOG_INFO, "Executing '%s %s %s'.", run, interface, arg); + + if (pipe(_pipe) < 0) { + daemon_log(LOG_ERR, "pipe() failed: %s", strerror(errno)); + return -1; + } + + if ((pid = fork()) < 0) { + daemon_log(LOG_ERR, "fork() failed: %s", strerror(errno)); + return -1; + + } else if (pid == 0) { + dup2(_pipe[1], 1); + dup2(_pipe[1], 2); + + if (_pipe[0] > 2) + close(_pipe[0]); + + if (_pipe[1] > 2) + close(_pipe[1]); + + umask(0022); // Set up a sane umask + + execl(run, run, interface, arg, extra_arg, 0); + + _exit(EXIT_FAILURE); + } + + close(_pipe[1]); + + FD_ZERO(&rfds); + FD_SET(_pipe[0], &rfds); + FD_SET(sigfd = daemon_signal_fd(), &rfds); + + n = 0; + + for (;;) { + fd_set wrfds = rfds; + + if (select(FD_SETSIZE, &wrfds, NULL, NULL, NULL) < 0) { + + if (errno == EINTR) + continue; + + break; + } + + + if (FD_ISSET(_pipe[0], &wrfds)) { + char c; + + if (read(_pipe[0], &c, 1) != 1) + break; + + buf[n] = c; + + if (c == '\n' || n >= sizeof(buf) - 2) { + if (c != '\n') n++; + buf[n] = 0; + + if (buf[0]) + daemon_log(LOG_WARNING, "client: %s", buf); + + n = 0; + } else + n++; + } + + if (FD_ISSET(sigfd, &wrfds)) { + int sig; + + if ((sig = daemon_signal_next()) < 0) { + daemon_log(LOG_ERR, "daemon_signal_next(): %s", strerror(errno)); + break; + } + + if (sig != SIGCHLD) { + daemon_log(LOG_WARNING, "Killing child."); + kill(pid, SIGTERM); + } + + break; + } + + } + + if (n > 0) { + buf[n] = 0; + daemon_log(LOG_WARNING, "client: %s", buf); + } + + waitpid(pid, &r, 0); + + close(_pipe[0]); + + if (!WIFEXITED(r) || WEXITSTATUS(r) != 0) { + if (status == IFSTATUS_UP) + beep(2); + daemon_log(LOG_ERR, "Program execution failed, return value is %i.", WEXITSTATUS(r)); + + return ignore_retval ? 0 : -1; + } else { + if (status == IFSTATUS_UP) + beep(0); + + daemon_log(LOG_INFO, "Program executed successfully."); + return 0; + } +} + +interface_status_t detect_beat_auto(int fd, char *iface) { + interface_status_t status; + static interface_status_t (*cached_func)(int, char*) = NULL; + + if (cached_func && (status = cached_func(fd, iface)) != IFSTATUS_ERR) + return status; + + if ((status = interface_detect_beat_mii(fd, iface)) != IFSTATUS_ERR) { + cached_func = interface_detect_beat_mii; + daemon_log(LOG_INFO, "Using detection mode: SIOCGMIIPHY"); + return status; + } + + if ((status = interface_detect_beat_ethtool(fd, iface)) != IFSTATUS_ERR) { + cached_func = interface_detect_beat_ethtool; + daemon_log(LOG_INFO, "Using detection mode: SIOCETHTOOL"); + return status; + } + + if ((status = interface_detect_beat_wlan(fd, iface)) != IFSTATUS_ERR) { + cached_func = interface_detect_beat_wlan; + daemon_log(LOG_INFO, "Using detection mode: wireless extension"); + return status; + } + + if ((status = interface_detect_beat_priv(fd, iface)) != IFSTATUS_ERR) { + cached_func = interface_detect_beat_priv; + daemon_log(LOG_INFO, "Using detection mode: SIOCDEVPRIVATE"); + return status; + } + + return IFSTATUS_ERR; +} + +char *strstatus(interface_status_t s) { + switch(s) { + case IFSTATUS_UP: return "up"; + case IFSTATUS_DOWN: return "down"; + case IFSTATUS_ERR: return "error"; + default: return "disabled"; + } +} + +interface_status_t detect_beat(int fd, char*iface) { + interface_status_t status; + static interface_status_t last_status = (interface_status_t) -1; + + if ((status = detect_beat_func(fd, iface)) == IFSTATUS_ERR) + status = failure_status; + + if (status == IFSTATUS_ERR && detect_beat_func == detect_beat_auto) + daemon_log(LOG_INFO, "Failed to find working plug detection mode for %s", interface); + + if (status != last_status) { + setenv(IFPLUGD_ENV_PREVIOUS, strstatus(last_status), 1); + setenv(IFPLUGD_ENV_CURRENT, strstatus(status), 1); + last_status = status; + } + + return status; +} + +int open_iface(char *iface) { + int fd; + struct ifreq ifr; + struct ethtool_drvinfo drvinfo; + char txt[256]; + + if ((fd = interface_open(iface)) < 0) { + daemon_log(LOG_ERR, "Could not create socket: %s", strerror(errno)); + return fd; + } + + if (interface_auto_up) + interface_up(fd, iface); + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name)-1); + + if (ioctl(fd, SIOCGIFHWADDR, &ifr) == -1) + snprintf(txt, sizeof(txt)-1, "Using interface %s", iface); + else + snprintf(txt, sizeof(txt)-1, "Using interface %s/%02X:%02X:%02X:%02X:%02X:%02X", iface, ifr.ifr_hwaddr.sa_data[0] & 0xFF, ifr.ifr_hwaddr.sa_data[1] & 0xFF, ifr.ifr_hwaddr.sa_data[2] & 0xFF, ifr.ifr_hwaddr.sa_data[3] & 0xFF, ifr.ifr_hwaddr.sa_data[4] & 0xFF, ifr.ifr_hwaddr.sa_data[5] & 0xFF); + + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, iface, sizeof(ifr.ifr_name)-1); + + drvinfo.cmd = ETHTOOL_GDRVINFO; + ifr.ifr_data = (caddr_t) &drvinfo; + + if (ioctl(fd, SIOCETHTOOL, &ifr) != -1) + daemon_log(LOG_INFO, "%s with driver <%s> (version: %s)", txt, drvinfo.driver, drvinfo.version); + else + daemon_log(LOG_INFO, "%s", txt); + + return fd; +} + +void work() { + interface_status_t status; + int fd = -1; + fd_set rfds; + int sigfd; + time_t t = 0; + int send_retval = 1; + int paused = 0; + static char log_ident[256]; + + snprintf(log_ident, sizeof(log_ident), "ifplugd(%s)", interface); + + daemon_log_ident = log_ident; + + + if (daemon_pid_file_create() < 0) { + daemon_log(LOG_ERR, "Could not create PID file %s.", daemon_pid_file_proc()); + goto finish; + } + + if (daemon_signal_init(SIGINT, SIGTERM, SIGQUIT, SIGHUP, SIGCHLD, SIGUSR1, SIGUSR2, -1) < 0) { + daemon_log(LOG_ERR, "Could not register signal handler: %s", strerror(errno)); + goto finish; + } + + switch (api_mode) { + case API_ETHTOOL: detect_beat_func = interface_detect_beat_ethtool; break; + case API_MII: detect_beat_func = interface_detect_beat_mii; break; + case API_PRIVATE: detect_beat_func = interface_detect_beat_priv; break; + case API_WLAN: detect_beat_func = interface_detect_beat_wlan;break; + + default: + detect_beat_func = detect_beat_auto; + interface_do_message = 0; + break; + } + + if ((fd = open_iface(interface)) < 0) + goto finish; + + if ((status = detect_beat(fd, interface)) == IFSTATUS_ERR) + goto finish; + + daemon_log(LOG_INFO, "ifplugd "VERSION" successfully initialized, link beat %sdetected.", status == IFSTATUS_UP ? "" : "not "); + beep(status == IFSTATUS_UP ? 0 : 1); + + if (status == IFSTATUS_UP || initial_down) + if (action(status) < 0) + goto finish; + + if (daemonize && wait_on_fork) { + char c = status == IFSTATUS_UP ? 2 : (status == IFSTATUS_DOWN ? 3 : 1); + daemon_retval_send(c); + send_retval = 0; + } + + FD_ZERO(&rfds); + FD_SET(sigfd = daemon_signal_fd(), &rfds); + + for (;;) { + interface_status_t r; + fd_set wrfds = rfds; + struct timeval tv = { polltime, 0 }; + + if (select(FD_SETSIZE, &wrfds, NULL, NULL, &tv) < 0) { + if (errno == EINTR) + continue; + + daemon_log(LOG_ERR, "select(): %s", strerror(errno)); + goto finish; + } + + + if (FD_ISSET(sigfd, &wrfds)) { + int sig; + + if ((sig = daemon_signal_next()) < 0) { + daemon_log(LOG_ERR, "daemon_signal_next(): %s", strerror(errno)); + goto finish; + } + + + switch (sig) { + + case SIGINT: + case SIGTERM: + goto cleanup; + + case SIGQUIT: + goto finish; + + case SIGCHLD: + break; + + case SIGHUP: + daemon_log(LOG_INFO, "SIGHUP: %s, link detected on %s: %s", paused ? "Suspended" : "Running", interface, status == IFSTATUS_DOWN ? "no" : "yes"); + break; + + case SIGUSR1: + daemon_log(LOG_INFO, "SIGUSR1: Daemon suspended (#%i)", ++paused); + break; + + case SIGUSR2: + if (paused > 0) { + daemon_log(LOG_INFO, "SIGUSR2: Daemon resumed (#%i)", paused); + paused --; + } + + break; + + default: + daemon_log(LOG_INFO, "Ignoring unknown signal %s", strsignal(sig)); + break; + } + } + + + if (!paused) { + if ((r = detect_beat(fd, interface)) == IFSTATUS_ERR) + break; + + if (status != r) { + status = r; + + daemon_log(LOG_INFO, "Link beat %s.", status == IFSTATUS_DOWN ? "lost" : "detected"); + beep(status == IFSTATUS_UP ? 0 : 1); + + + if (t) + t = 0; + else { + t = time(NULL); + + if (status == IFSTATUS_UP) + t += delay_up; + + if (status == IFSTATUS_DOWN) + t += delay_down; + } + } + } + + + if (t && t < time(NULL)) { + t = 0; + + if (action(status) < 0) + goto finish; + } + } + +cleanup: + if (!no_shutdown_script && status == IFSTATUS_UP) { + setenv(IFPLUGD_ENV_PREVIOUS, strstatus(status), 1); + setenv(IFPLUGD_ENV_CURRENT, strstatus(-1), 1); + action(IFSTATUS_DOWN); + beep(1); + } + +finish: + + if (fd >= 0) + close(fd); + + if (send_retval && daemonize && wait_on_fork) + daemon_retval_send(1); + + daemon_pid_file_remove(); + daemon_signal_done(); + + daemon_log(LOG_INFO, "Exiting."); +} + +void usage(char *p) { + char *m; + + switch (api_mode) { + case API_ETHTOOL: m = "ethtool"; break; + case API_MII: m = "mii"; break; + case API_PRIVATE: m = "priv"; break; + case API_WLAN: m = "wlan"; break; + default: m = "auto"; + } + + if (strrchr(p, '/')) + p = strchr(p, '/')+1; + + printf("%s [options]\n" + " -a --no-auto Do not enable interface automatically (%s)\n" + " -n --no-daemon Do not daemonize (for debugging) (%s)\n" + " -s --no-syslog Do not use syslog, use stderr instead (for debugging) (%s)\n" + " -b --no-beep Do not beep (%s)\n" + " -f --ignore-fail Ignore detection failure, retry instead (failure is treated as DOWN) (%s)\n" + " -F --ignore-fail-positive Ignore detection failure, retry instead (failure is treated as UP) (%s)\n" + " -i --iface=IFACE Specify ethernet interface (%s)\n" + " -r --run=EXEC Specify program to execute (%s)\n" + " -I --ignore-retval Don't exit on nonzero return value of program executed (%s)\n" + " -t --poll-time=SECS Specify poll time in seconds (%i)\n" + " -u --delay-up=SECS Specify delay for configuring interface (%i)\n" + " -d --delay-down=SECS Specify delay for deconfiguring interface (%i)\n" + " -m --api-mode=MODE Force API mode (mii, priv, ethtool, wlan, auto) (%s)\n" + " -q --no-shutdown Don't run script on daemon quit (%s)\n" + " -l --initial-down Run \"down\" script on startup if now cable is detected (%s)\n" + " -w --wait-on-fork Wait until daemon fork finished (%s)\n" + " -x --extra-arg Specify an extra argument for action script\n" + " -h --help Show this help\n" + " -k --kill Kill a running daemon\n" + " -c --check-running Check if a daemon is currently running\n" + " -v --version Show version\n" + " -S --suspend Suspend running daemon\n" + " -R --resume Resume running daemon\n" + " -z --info Write status of running daemon to syslog\n", + p, + !interface_auto_up ? "on" : "off", + !daemonize ? "on" : "off", + !use_syslog ? "on" : "off", + !use_beep ? "on" : "off", + failure_status == IFSTATUS_DOWN ? "on" : "off", + failure_status == IFSTATUS_UP ? "on" : "off", + interface, + run, + ignore_retval ? "on" : "off", + polltime, + delay_up, + delay_down, + m, + no_shutdown_script ? "on" : "off", + initial_down ? "on" : "off", + wait_on_fork ? "on" : "off"); +} + +void parse_args(int argc, char *argv[]) { + static struct option long_options[] = { + {"no-auto", no_argument, 0, 'a'}, + {"no-daemon", no_argument, 0, 'n'}, + {"no-syslog", no_argument, 0, 's'}, + {"no-beep", no_argument, 0, 'b'}, + {"ignore-fail", no_argument, 0, 'f'}, + {"ignore-fail-positive", no_argument, 0, 'F'}, + {"ignore-retval", no_argument, 0, 'I'}, + {"iface", required_argument, 0, 'i'}, + {"run", required_argument, 0, 'r'}, + {"poll-time", required_argument, 0, 't'}, + {"delay-up", required_argument, 0, 'u'}, + {"delay-down", required_argument, 0, 'd'}, + {"api-mode", required_argument, 0, 'm'}, + {"wait-on-fork", no_argument, 0, 'w'}, + {"no-shutdown", no_argument, 0, 'q'}, + {"help", no_argument, 0, 'h'}, + {"kill", no_argument, 0, 'k'}, + {"check-running", no_argument, 0, 'c'}, + {"version", no_argument, 0, 'v'}, + {"extra-arg", required_argument, 0, 'x'}, + {"suspend", no_argument, 0, 'S'}, + {"resume", no_argument, 0, 'R'}, + {"info", no_argument, 0, 'z'}, + {"inital-down", no_argument, 0, 'l'}, + {0, 0, 0, 0} + }; + int option_index = 0; + int help = 0, _kill = 0, _check = 0, _version = 0, _suspend = 0, _resume = 0, _info = 0; + + for (;;) { + int c; + + if ((c = getopt_long(argc, argv, "asni:r:t:u:d:hkbfFvm:qwx:cISRzl", long_options, &option_index)) < 0) + break; + + switch (c) { + case 'a' : + interface_auto_up = !interface_auto_up; + break; + case 's' : + use_syslog = !use_syslog; + break; + case 'n' : + daemonize = !daemonize; + break; + case 'i' : + interface = strdup(optarg); + break; + case 'r': + run = strdup(optarg); + break; + case 'I': + ignore_retval = !ignore_retval; + break; + case 't': + polltime = atoi(optarg); + if (polltime < 0) polltime = 0; + break; + case 'u': + delay_up = atoi(optarg); + break; + case 'd': + delay_down = atoi(optarg); + break; + case 'h': + help = 1; + break; + case 'k': + _kill = 1; + break; + case 'c': + _check = 1; + break; + case 'v': + _version = 1; + break; + case 'b': + use_beep = !use_beep; + break; + case 'f': + failure_status = IFSTATUS_DOWN; + break; + case 'F': + failure_status = IFSTATUS_UP; + break; + case 'm': + switch (tolower(optarg[0])) { + case 'e': api_mode = API_ETHTOOL; break; + case 'm': api_mode = API_MII; break; + case 'p': api_mode = API_PRIVATE; break; + case 'w': api_mode = API_WLAN; break; + case 'a': api_mode = API_AUTO; break; + default: + fprintf(stderr, "Unknown API mode: %s\n", optarg); + exit(2); + } + break; + case 'q': + no_shutdown_script = !no_shutdown_script; + break; + case 'l': + initial_down = !initial_down; + break; + case 'w': + wait_on_fork = !wait_on_fork; + break; + case 'x': + extra_arg = strdup(optarg); + break; + case 'S': + _suspend = 1; + break; + case 'R': + _resume = 1; + break; + case 'z': + _info = 1; + break; + default: + fprintf(stderr, "Unknown parameter.\n"); + exit(1); + } + } + + + if (!use_syslog) + daemon_log_use = DAEMON_LOG_STDERR; + + + if (help) { + usage(argv[0]); + exit(0); + } + + if (_kill || _resume || _suspend || _info) { + if (daemon_pid_file_kill(_kill ? SIGINT : (_resume ? SIGUSR2 : (_info ? SIGHUP : SIGUSR1))) < 0) { + daemon_log(LOG_ERR, "Failed to kill daemon. (%s)", strerror(errno)); + exit(6); + } + + exit(0); + } + + if (_version) { + +#ifdef SVN_REVISION + printf("ifplugd "VERSION" (SVN: "SVN_REVISION")\n"); +#else + printf("ifplugd "VERSION"\n"); +#endif + + exit(0); + } + + if (_check) { + pid_t pid = daemon_pid_file_is_running(); + + if (pid == (pid_t) -1) + printf("ifplugd not running.\n"); + else + printf("ifplugd process for device %s running as pid %u.\n", interface, pid); + + exit(pid == 0 ? 255 : 0); + } + +} + +static volatile int alarmed = 0; + +void sigalrm() { + alarmed = 1; +} + +int main(int argc, char* argv[]) { + + daemon_pid_file_proc = pid_file_proc; + + if ((daemon_log_ident = strrchr(argv[0], '/'))) + daemon_log_ident++; + else + daemon_log_ident = argv[0]; + + parse_args(argc, argv); + + if (geteuid() != 0) { + daemon_log(LOG_ERR, "Sorry, you need to be root to run this binary."); + return 2; + } + + + if (daemon_pid_file_is_running() >= 0) { + daemon_log(LOG_ERR, "Sorry, there is already an instance of ifplugd for %s running.", interface); + return 4; + } + + if (daemonize) { + pid_t pid; + + if (wait_on_fork) + if (daemon_retval_init() < 0) { + daemon_log(LOG_ERR, "Sorry, could not create pipe: %s", strerror(errno)); + return 4; + } + + if ((pid = daemon_fork()) < 0) + return 3; + + if (pid) { + int c = 0; + + // Parent process + + if (wait_on_fork) + if ((c = daemon_retval_wait(60)) < 0) { + daemon_log(LOG_WARNING, "Killing background process."); + kill(pid, SIGTERM); + } + + + return c; + } + } + + // Let's go + work(); + return 0; +} |