summaryrefslogtreecommitdiffstats
path: root/src/waproamd.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/waproamd.c')
-rw-r--r--src/waproamd.c667
1 files changed, 667 insertions, 0 deletions
diff --git a/src/waproamd.c b/src/waproamd.c
new file mode 100644
index 0000000..c2af3af
--- /dev/null
+++ b/src/waproamd.c
@@ -0,0 +1,667 @@
+#define _GNU_SOURCE
+
+#include <assert.h>
+#include <string.h>
+#include <time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <getopt.h>
+
+#include <libdaemon/dpid.h>
+#include <libdaemon/dlog.h>
+#include <libdaemon/dfork.h>
+#include <libdaemon/dsignal.h>
+
+#include "iwapi.h"
+#include "interface.h"
+#include "exec.h"
+#include "nlapi.h"
+#include "ifmonitor.h"
+#include "assocwatch.h"
+
+#ifndef SYSCONFDIR
+#define SYSCONFDIR "/etc/waproamd"
+#endif
+
+#ifndef SCRIPTDIR
+#define SCRIPTDIR SYSCONFDIR"/scripts"
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+char *interface_name = NULL;
+int interface_index = -1;
+
+int disabled = 0,
+ associated = 0;
+
+struct hw_addr associated_ap;
+struct hw_addr current_ap;
+
+int use_assocwatch = 1,
+ use_ifmonitor = 0,
+ daemonize = 1,
+ wait_on_fork = 0,
+ use_syslog = 1;
+
+int poll_interval = 5,
+ scan_interval = 10;
+
+char log_ident[32], pid_ident[32];
+
+int issue_scan(struct interface *i) {
+
+ //fprintf(stderr, "Scanning...\n");
+
+ if (iw_set_mode(i, IW_MODE_INFRA) < 0)
+ return -1;
+
+ if (iw_set_ap(i, &null_ap) < 0)
+ return -1;
+
+ if (iw_set_essid(i, NULL) < 0)
+ return -1;
+
+ if (iw_scan(i) < 0)
+ return -1;
+
+ return 0;
+}
+
+void get_script_path(char *path, int l, struct hw_addr *ap) {
+ assert(path && l);
+
+ if (!ap) {
+ snprintf(path, l, "%s/default", SCRIPTDIR);
+ return;
+ }
+
+ snprintf(path, l, "%s/%02x:%02x:%02x:%02x:%02x:%02x",
+ SCRIPTDIR,
+ ap->addr[0], ap->addr[1], ap->addr[2],
+ ap->addr[3], ap->addr[4], ap->addr[5]);
+}
+
+struct ap_info selected_ap;
+int selected_ap_has_script;
+int selected_ap_valid;
+
+static int scan_result_cb(struct ap_info*ap) {
+ int b;
+ char path[PATH_MAX];
+
+ assert(ap);
+ get_script_path(path, sizeof(path), &ap->ap);
+ b = access(path, X_OK) == 0;
+
+ if (selected_ap_valid) {
+
+ if (!b || selected_ap_has_script)
+ return 0;
+ }
+
+ memcpy(&selected_ap, ap, sizeof(struct ap_info));
+ selected_ap_valid = 1;
+ selected_ap_has_script = 1;
+ return 0;
+}
+
+int read_scan(struct interface *i, struct ap_info **ap) {
+ int r;
+ selected_ap_valid = 0;
+ selected_ap_has_script = 0;
+
+ if ((r = iw_scan_result(i, scan_result_cb)) < 0)
+ return -1;
+
+ if (r == 1)
+ return 1;
+
+ *ap = selected_ap_valid ? &selected_ap : NULL;
+
+ return 0;
+}
+
+int run_script(struct hw_addr *ap, const char *arg) {
+ char path[PATH_MAX];
+
+ get_script_path(path, sizeof(path), ap);
+
+ if (access(path, X_OK) < 0)
+ get_script_path(path, sizeof(path), NULL);
+
+ return log_exec(SCRIPTDIR, path, arg);
+};
+
+
+int set_current_ap(struct hw_addr *a) {
+ char t[32];
+
+ if (!a)
+ a = &null_ap;
+
+ if (!hw_addr_equal(a, &current_ap)) {
+
+ if (!hw_addr_equal(&current_ap, &null_ap)) {
+ if (run_script(&current_ap, "stop") < 0)
+ return -1;
+ }
+
+ memcpy(&current_ap, a, sizeof(struct hw_addr));
+
+ snprint_hw_addr(t, sizeof(t), &current_ap);
+ setenv("AP", t, 1);
+ setenv("IFACE", interface_name, 1);
+
+ if (!hw_addr_equal(&current_ap, &null_ap)) {
+ fprintf(stderr, "Selected AP ");
+ print_hw_addr(stderr, &current_ap);
+ fprintf(stderr, "\n");
+
+ if (run_script(&current_ap, "start") < 0)
+ return -1;
+ }
+ }
+
+
+ return 0;
+}
+
+int ifmonitor_cb(int b, int index, unsigned short type, const char *name) {
+ if (!name)
+ return 0;
+
+ if (!strcmp(name, interface_name)) {
+ interface_index = index;
+ disabled = !b;
+ }
+ return 0;
+}
+
+int assocwatch_cb(int index, struct hw_addr *a) {
+ char name[IFNAMSIZ+1];
+
+ if (get_ifname(index, name, sizeof(name)) < 0)
+ return -1;
+
+ if (!strcmp(name, interface_name)) {
+ interface_index = index;
+ disabled = 0;
+ if ((associated = !!a))
+ memcpy(&associated_ap, a, sizeof(struct hw_addr));
+ }
+
+ return 0;
+}
+
+int go(struct interface *i) {
+ time_t next_scan;
+ int scanning = 0;
+ int send_retval = 1;
+ int r = -1, sigfd;
+ fd_set fds;
+
+ daemon_log(LOG_INFO, "waproamd "VERSION" initializing%s%s.", use_ifmonitor ? ", using NETLINK device monitoring" : "", use_assocwatch ? ", using wireless event notifications" : "");
+
+ 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, -1) < 0) {
+ daemon_log(LOG_ERR, "Could not register signal handler: %s", strerror(errno));
+ goto finish;
+ }
+
+ if (nlapi_open(RTMGRP_LINK) < 0)
+ goto finish;
+
+ if (use_ifmonitor) {
+ int b;
+
+ if ((b = is_iface_available(interface_name)) < 0)
+ goto finish;
+
+ disabled = !b;
+ } else
+ disabled = 0;
+
+ memset(&current_ap, 0, sizeof(current_ap));
+
+ if ((associated = interface_is_assoc(i, &associated_ap)) < 0) {
+ if (!use_ifmonitor)
+ goto finish;
+
+ associated = 0;
+ }
+
+ daemon_log(LOG_INFO, "Currently %sassociated, interface %s.", associated ? "" : "not ", disabled ? "disabled" : "enabled");
+
+ if (use_assocwatch)
+ if (assocwatch_init(assocwatch_cb) < 0)
+ goto finish;
+
+ if (use_ifmonitor)
+ if (ifmonitor_init(ifmonitor_cb) < 0)
+ goto finish;
+
+ daemon_log(LOG_INFO, "Initialization complete.");
+
+ set_current_ap(&associated_ap);
+ next_scan = associated || disabled ? (time_t) -1 : 0;
+
+ if (daemonize && wait_on_fork) {
+ daemon_retval_send(0);
+ send_retval = 0;
+ }
+
+ FD_ZERO(&fds);
+ FD_SET(sigfd = daemon_signal_fd(), &fds);
+ FD_SET(nlapi_fd, &fds);
+
+ for (;;) {
+ fd_set qfds;
+ struct timeval tv, *ptv;
+ time_t now = time(NULL);
+ int a, d;
+
+ if (next_scan != (time_t) -1 && next_scan <= now) {
+ if (issue_scan(i) < 0) {
+ if (!use_ifmonitor)
+ goto finish;
+ } else
+ scanning = 1;
+
+ next_scan = (time_t) -1;
+ }
+
+ ptv = NULL;
+
+ if (scanning) {
+ tv.tv_sec = 0;
+ tv.tv_usec = 100000;
+ ptv = &tv;
+ } else if (!use_assocwatch) {
+ tv.tv_sec = poll_interval;
+ tv.tv_usec = 0;
+ ptv = &tv;
+ }
+
+ if (next_scan != (time_t) -1) {
+ struct timeval tv2;
+ now = time(NULL);
+ tv2.tv_sec = next_scan > now ? next_scan - now : 0;
+ tv2.tv_usec = 0;
+
+ if (!ptv || tv2.tv_sec < tv.tv_sec) {
+ tv.tv_sec = tv2.tv_sec;
+ tv.tv_usec = tv2.tv_usec;
+ ptv = &tv;
+ }
+ }
+
+ qfds = fds;
+
+ if (select(FD_SETSIZE, &qfds, NULL, NULL, ptv) < 0) {
+ if (errno == EINTR)
+ continue;
+
+ fprintf(stderr, "select() failed: %s\n", strerror(errno));
+ goto finish;
+ }
+
+ a = associated;
+ d = disabled;
+
+ if (FD_ISSET(nlapi_fd, &qfds))
+ if (nlapi_work(0) < 0)
+ goto finish;
+
+ if (!use_assocwatch) {
+ if ((associated = interface_is_assoc(i, &associated_ap)) < 0) {
+ if (!use_ifmonitor)
+ goto finish;
+
+ associated = 0;
+ }
+ }
+
+ /* Changed: enabled -> disabled */
+ if (!d && disabled) {
+ fprintf(stderr, "Interface disabled\n");
+
+ if (associated)
+ if (set_current_ap(&null_ap) < 0)
+ goto finish;
+
+ associated = 0;
+ }
+
+ /* Changed: disabled -> enabled */
+ if (d && !disabled) {
+ fprintf(stderr, "Interface enabled\n");
+ associated = 0;
+ }
+
+ if (!disabled) {
+ /* Changed: associated -> not associated */
+ if (a && !associated)
+ fprintf(stderr, "No longer associated.\n");
+
+ /* Changed: not associated -> associated */
+ if (!a && associated) {
+ if (set_current_ap(&associated_ap) < 0)
+ goto finish;
+
+ fprintf(stderr, "Associated.\n");
+ next_scan = (time_t) -1;
+ }
+
+ if (scanning) {
+ int r;
+ struct ap_info *ai = NULL;
+
+ if ((r = read_scan(i, &ai)) < 0) {
+ if (!use_ifmonitor)
+ goto finish;
+
+ scanning = 0;
+
+ } else if (!r) {
+
+ scanning = 0;
+
+ if (!associated) {
+
+ if (set_current_ap(ai ? &ai->ap : NULL) < 0)
+ goto finish;
+ }
+ }
+ }
+ }
+
+ if (FD_ISSET(sigfd, &qfds)) {
+ 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:
+ case SIGQUIT:
+ r = 0;
+ goto finish;
+
+ case SIGHUP:
+ next_scan = 0;
+ break;
+
+ default:
+ daemon_log(LOG_INFO, "Ignoring unknown signal %s", strsignal(sig));
+ break;
+
+ }
+ }
+
+ if (next_scan == (time_t) -1 && !scanning)
+ if (!disabled && !associated)
+ next_scan = time(NULL) + scan_interval;
+
+ if (disabled || associated) {
+ scanning = 0;
+ next_scan = (time_t) -1;
+ }
+ }
+
+ r = 0;
+
+finish:
+
+ if (send_retval && daemonize && wait_on_fork)
+ daemon_retval_send(1);
+
+ nlapi_close();
+
+ daemon_pid_file_remove();
+ daemon_signal_done();
+
+ daemon_log(LOG_INFO, "Exiting.");
+
+ return r;
+}
+
+void usage(char *p) {
+ if (strrchr(p, '/'))
+ p = strchr(p, '/')+1;
+
+ printf("%s -- Wireless Access Point Roaming Daemon for 802.11b\n\n"
+ "Usage: %s [options]\n\n"
+ "Options:\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"
+ " -i --iface=IFACE Specify network interface (%s)\n"
+ " -w --wait-on-fork Wait until daemon fork finished (%s)\n"
+ " -M --monitor Use interface monitoring (%s)\n"
+ " -e --no-event Don't use wireless event API (%s)\n"
+ " -t --scan-interval Specify scan interval (%i)\n"
+ " -p --poll-interval Specify association poll interval, unless using -e (%i)\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",
+ p, p,
+ !daemonize ? "on" : "off",
+ !use_syslog ? "on" : "off",
+ interface_name,
+ wait_on_fork ? "on" : "off",
+ use_ifmonitor ? "on" : "off",
+ use_assocwatch ? "off" : "on",
+ scan_interval,
+ poll_interval);
+}
+
+void parse_args(int argc, char *argv[]) {
+ static struct option long_options[] = {
+ {"no-daemon", no_argument, 0, 'n'},
+ {"no-syslog", no_argument, 0, 's'},
+ {"iface", required_argument, 0, 'i'},
+ {"wait-on-fork", no_argument, 0, 'w'},
+ {"monitor", no_argument, 0, 'M'},
+ {"no-event", no_argument, 0, 'e'},
+ {"scan-interval", required_argument, 0, 't'},
+ {"poll-interval", required_argument, 0, 'p'},
+ {"help", no_argument, 0, 'h'},
+ {"kill", no_argument, 0, 'k'},
+ {"check-running", no_argument, 0, 'c'},
+ {"version", no_argument, 0, 'v'},
+ {0, 0, 0, 0}
+ };
+ int option_index = 0;
+ int _help = 0, _kill = 0, _check = 0, _version = 0;
+
+ for (;;) {
+ int c;
+
+ if ((c = getopt_long(argc, argv, "nsi:whkcvMet:p:", long_options, &option_index)) < 0)
+ break;
+
+ switch (c) {
+ case 'n' :
+ daemonize = !daemonize;
+ break;
+
+ case 's' :
+ use_syslog = !use_syslog;
+ break;
+
+ case 'i' :
+ if (interface_name)
+ free(interface_name);
+ interface_name = strdup(optarg);
+ break;
+
+ case 'w':
+ wait_on_fork = !wait_on_fork;
+ break;
+
+ case 'M':
+ use_ifmonitor = !use_ifmonitor;
+ break;
+
+ case 'e':
+ use_assocwatch = !use_assocwatch;
+ break;
+
+ case 't':
+ if ((scan_interval = atoi(optarg)) < 0) {
+ daemon_log(LOG_ERR, "Scan interval must be a positive, nonzero integer.");
+ exit(1);
+ }
+
+ case 'p':
+ if ((poll_interval = atoi(optarg)) < 0) {
+ daemon_log(LOG_ERR, "Poll interval must be a positive, nonzero integer.");
+ exit(1);
+ }
+
+ case 'h':
+ _help = 1;
+ break;
+
+ case 'k':
+ _kill = 1;
+ break;
+
+ case 'c':
+ _check = 1;
+ break;
+
+ case 'v':
+ _version = 1;
+ break;
+
+ default:
+ fprintf(stderr, "Unknown parameter.\n");
+ exit(1);
+ }
+ }
+
+ if (!interface_name)
+ interface_name = strdup("wlan0");
+
+ snprintf(pid_ident, sizeof(pid_ident), "waproamd.%s", interface_name);
+ daemon_pid_file_ident = pid_ident;
+ snprintf(log_ident, sizeof(log_ident), "waproamd(%s)", interface_name);
+ daemon_log_ident = log_ident;
+
+
+ if (_help) {
+ usage(argv[0]);
+ exit(0);
+ }
+
+ if (_kill) {
+ if (daemon_pid_file_kill(SIGINT) < 0) {
+ daemon_log(LOG_ERR, "Failed to kill daemon. (%s)", strerror(errno));
+ exit(6);
+ }
+
+ exit(0);
+ }
+
+ if (_version) {
+ printf("waproamd "VERSION"\n");
+ exit(0);
+ }
+
+ if (_check) {
+ pid_t pid = daemon_pid_file_is_running();
+
+ if (pid == (pid_t) -1)
+ printf("waproamd not running.\n");
+ else
+ printf("waproamd process for device %s running as pid %u.\n", interface_name, pid);
+
+ exit(pid == 0 ? 255 : 0);
+ }
+
+ if (!use_syslog)
+ daemon_log_use = DAEMON_LOG_STDERR;
+
+}
+
+int main(int argc, char *argv[]) {
+ struct interface *i = NULL;
+ int r = 1;
+ pid_t pid;
+
+ daemon_pid_file_ident = daemon_log_ident = daemon_ident_from_argv0(argv[0]);
+
+ parse_args(argc, argv);
+
+ if (geteuid() != 0) {
+ daemon_log(LOG_ERR, "Sorry, you need to be root to run this binary.");
+ goto finish;
+ }
+
+ if ((pid = daemon_pid_file_is_running()) >= 0) {
+ daemon_log(LOG_ERR, "Daemon already running on PID file %u", pid);
+ goto finish;
+
+ }
+
+ 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));
+ goto finish;
+ }
+
+ if ((pid = daemon_fork()) < 0)
+ goto finish;
+
+ 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);
+ }
+
+ r = c;
+ goto finish;
+ }
+ }
+
+ if (!(i = interface_open(interface_name)) < 0)
+ goto finish;
+
+ if (go(i) < 0)
+ goto finish;
+
+ r = 0;
+
+finish:
+
+ if (i)
+ interface_close(i);
+
+ if (interface_name)
+ free(interface_name);
+
+ return r;
+}