/* $Id$ */ /* * This file is part of waproamd. * * waproamd 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. * * waproamd 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 waproamd; if not, write to the Free Software Foundation, * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. */ #define _GNU_SOURCE #ifdef HAVE_CONFIG_H #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "iwapi.h" #include "interface.h" #include "nlapi.h" #include "ifmonitor.h" #include "assocwatch.h" #include "waproamd.h" #include "iwhostroam.h" char *interface_name = NULL; int interface_index = -1; int disabled = 0, associated = 0, do_status_check = 0; struct ap_info associated_ap; struct ap_info current_ap; int current_ap_valid = 0; int use_assocwatch = 1, use_ifmonitor = 0, daemonize = 1, wait_on_fork = 0, wait_on_kill = 0, use_syslog = 1, use_userspace_roaming = 1; int poll_interval = 5, scan_interval = 30; char log_ident[32], pid_ident[32]; int get_script_path(char *path, int l, struct ap_info *ai) { assert(path && l); struct stat st; if (!ai) { snprintf(path, l, "%s/default", SCRIPTDIR); return 0; } snprintf(path, l, "%s/%02x:%02x:%02x:%02x:%02x:%02x", SCRIPTDIR, ai->ap.addr[0], ai->ap.addr[1], ai->ap.addr[2], ai->ap.addr[3], ai->ap.addr[4], ai->ap.addr[5]); if (!stat(path, &st)) return st.st_mode & S_IXUSR ? 0 : 1; snprintf(path, l, "%s/%02X:%02X:%02X:%02X:%02X:%02X", SCRIPTDIR, ai->ap.addr[0], ai->ap.addr[1], ai->ap.addr[2], ai->ap.addr[3], ai->ap.addr[4], ai->ap.addr[5]); if (!stat(path, &st)) return st.st_mode & S_IXUSR ? 0 : 1; if (ai->essid[0]) { snprintf(path, l, "%s/essid:%s", SCRIPTDIR, escape_essid(ai->essid)); if (!stat(path, &st)) return st.st_mode & S_IXUSR ? 0 : 1; } return -1; } int total_aps_found_n, aps_found_n; struct ap_info scan_ap; int scan_ap_has_script; static int scan_result_cb(struct ap_info*ai) { int f; char path[PATH_MAX], t[32]; assert(ai); if (total_aps_found_n == 0) daemon_log(LOG_INFO, "Scan results:"); total_aps_found_n++; snprint_hw_addr(t, sizeof(t), &ai->ap); f = get_script_path(path, sizeof(path), ai); daemon_log(LOG_INFO, "%i. Found AP %s, ESSID '%s', script: %s", total_aps_found_n, t, escape_essid(ai->essid), f == 0 ? "yes" : (f < 0 ? "no" : "off")); if (f <= 0) { aps_found_n++; /* Select this AP, if no better was found before */ if ((aps_found_n <= 1 || (!scan_ap_has_script && f == 0))) { memcpy(&scan_ap, ai, sizeof(struct ap_info)); scan_ap_has_script = f == 0; } } return 0; } int read_scan(struct interface *i, struct ap_info **ap) { int r; aps_found_n = total_aps_found_n = 0; scan_ap_has_script = 0; memset(&scan_ap, 0, sizeof(scan_ap)); if ((r = iw_scan_result(i, scan_result_cb)) < 0) return -1; if (r == 1) return 1; daemon_log(LOG_INFO, "Scan completed with %u suitable networks. (total: %u)", aps_found_n, total_aps_found_n); *ap = aps_found_n > 0 ? &scan_ap : NULL; return 0; } int run_script(struct ap_info *ai, const char *arg) { char t[32]; char path[PATH_MAX]; int ret = 0; if (get_script_path(path, sizeof(path), ai) < 0) get_script_path(path, sizeof(path), NULL); setenv("IFACE", interface_name, 1); snprint_hw_addr(t, sizeof(t), &ai->ap); setenv("AP", t, 1); setenv("ESSID", ai->essid, 1); setenv("ESSID_ESCAPED", escape_essid(ai->essid), 1); daemon_log(LOG_INFO, "Running script '%s %s'", path, arg); if (daemon_exec(SCRIPTDIR, &ret, path, path, arg, (char*) 0) < 0) daemon_log(LOG_WARNING, "Script execution failed abnormously."); else if (ret != 0) daemon_log(LOG_WARNING, "Script returned %i as return value.", ret); else daemon_log(LOG_INFO, "Script successfully executed."); return 0; }; int set_current_ap(struct interface *i, struct ap_info *a) { if ((a && current_ap_valid && !iw_ap_info_equal(a, ¤t_ap)) || (!!a != !!current_ap_valid)) { if (current_ap_valid) { if (run_script(¤t_ap, "stop") < 0) return -1; } if ((current_ap_valid = !!a)) { char t[20]; memcpy(¤t_ap, a, sizeof(struct ap_info)); snprint_hw_addr(t, sizeof(t), ¤t_ap.ap); daemon_log(LOG_INFO, "Selected new AP %s with ESSID '%s'", t, escape_essid(a->essid)); if (run_script(¤t_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)) return 0; return 0; } int go(struct interface *i) { time_t next_scan; int scanning = 0; int send_retval = 1; int r = -1, sigfd; int paused = 0; 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, SIGCHLD, SIGUSR1, SIGUSR2, -1) < 0) { daemon_log(LOG_ERR, "Could not register signal handler: %s", strerror(errno)); goto finish; } if (nlapi_open(RTMGRP_LINK) < 0) goto finish; interface_up(i); /* Check whether interface is available */ if (use_ifmonitor) { int b; if ((b = is_iface_available(interface_name)) < 0) goto finish; disabled = !b; } else disabled = 0; /* Try to enable userspace roaming in the driver */ if (!disabled && use_userspace_roaming) { if (iw_set_hostroam(i, 2) < 0) daemon_log(LOG_WARNING, "Enabling user space roaming failed, doing without."); } memset(¤t_ap, 0, sizeof(current_ap)); current_ap_valid = 0; /* Check association status */ if (!disabled) { if ((associated = iw_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(i, associated ? &associated_ap : NULL); 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; time_t now = time(NULL); int a, d; if (next_scan != (time_t) -1 && next_scan <= now) { if (iw_scan(i) < 0) { if (!use_ifmonitor) goto finish; } else scanning = 1; next_scan = (time_t) -1; } if (do_status_check) { tv.tv_sec = 0; tv.tv_usec = 0; } if (scanning) { tv.tv_sec = 0; tv.tv_usec = 100000; } else { tv.tv_sec = poll_interval; tv.tv_usec = 0; } 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 (tv2.tv_sec < tv.tv_sec) tv = tv2; } qfds = fds; if (select(FD_SETSIZE, &qfds, NULL, NULL, &tv) < 0) { if (errno == EINTR) continue; daemon_log(LOG_ERR, "select() failed: %s", strerror(errno)); goto finish; } a = associated; d = disabled; if (FD_ISSET(nlapi_fd, &qfds)) if (nlapi_work(0) < 0) goto finish; if (!disabled && !paused && !scanning) { if ((associated = iw_assoc(i, &associated_ap)) < 0) { if (!use_ifmonitor) goto finish; associated = 0; } do_status_check = 0; } if (paused) { /* If paused ignore new data */ associated = a; disabled = d; } else { /* Changed: enabled -> disabled */ if (!d && disabled) { daemon_log(LOG_INFO, "Interface disabled."); if (associated) if (set_current_ap(i, NULL) < 0) goto finish; associated = scanning = 0; } /* Changed: disabled -> enabled */ if (d && !disabled) { daemon_log(LOG_INFO, "Interface enabled."); associated = scanning = 0; interface_up(i); if (use_userspace_roaming) { if (iw_set_hostroam(i, 2) < 0) daemon_log(LOG_WARNING, "Enabling user space roaming failed, doing without."); } } if (!disabled) { /* Changed: associated -> not associated */ if (a && !associated) { daemon_log(LOG_INFO, "No longer associated."); scanning = 0; } /* Changed: not associated -> associated */ if (!a && associated) { if (set_current_ap(i, &associated_ap) < 0) goto finish; daemon_log(LOG_INFO, "Associated."); scanning = 0; } 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 (set_current_ap(i, ai) < 0) if (!use_ifmonitor) goto finish; if (ai && iw_tune(i, ai) < 0) if (!use_ifmonitor) 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: if (!disabled && !associated && !paused) { next_scan = 0; daemon_log(LOG_INFO, "SIGHUP: Immediate scan requested"); } else daemon_log(LOG_INFO, "SIGHUP: Immediate scan request ignored"); break; case SIGCHLD: 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--); if (!paused) do_status_check = 1; } break; default: daemon_log(LOG_INFO, "Ignoring unknown signal %s", strsignal(sig)); break; } } if (disabled || associated || paused) { scanning = 0; next_scan = (time_t) -1; } else if (next_scan == (time_t) -1 && !scanning) next_scan = time(NULL) + scan_interval; } r = 0; finish: daemon_log(LOG_INFO, "Shutdown"); set_current_ap(i, NULL); if (use_userspace_roaming) iw_set_hostroam(i, 0); 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 WLAN IEEE 802.11\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" " -W --wait-on-kill When run with -k, wait until the daemon died (%s)\n" " -M --monitor Use interface monitoring (%s)\n" " -e --no-event Don't use wireless event API (%s)\n" " -U --no-userspace-roaming Don't enable user space roaming via private ioctl (%s)\n" " -t --scan-interval=SECS Specify scan interval (%i)\n" " -p --poll-interval=SECS 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" " -S --suspend Suspend running daemon\n" " -R --resume Resume running daemon\n" " -r --issue-scan Tell running daemon to issue a new scan immediately\n", p, p, !daemonize ? "on" : "off", !use_syslog ? "on" : "off", interface_name, wait_on_fork ? "on" : "off", wait_on_kill ? "on" : "off", use_ifmonitor ? "on" : "off", use_assocwatch ? "off" : "on", use_userspace_roaming ? "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'}, {"wait-on-kill", no_argument, 0, 'W'}, {"monitor", no_argument, 0, 'M'}, {"no-event", no_argument, 0, 'e'}, {"no-userspace-roaming", no_argument, 0, 'U'}, {"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'}, {"suspend", no_argument, 0, 'S'}, {"resume", no_argument, 0, 'R'}, {"issue-scan", no_argument, 0, 'r'}, {0, 0, 0, 0} }; int option_index = 0; int _help = 0, _kill = 0, _check = 0, _version = 0, _suspend = 0, _resume = 0, _issuescan = 0; for (;;) { int c; if ((c = getopt_long(argc, argv, "nsi:whkcvMet:p:SRrWrU", 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 'W': wait_on_kill = !wait_on_kill; break; case 'S': _suspend = 1; break; case 'R': _resume = 1; break; case 'r': _issuescan = 1; break; case 'M': use_ifmonitor = !use_ifmonitor; break; case 'e': use_assocwatch = !use_assocwatch; break; case 'U': use_userspace_roaming = !use_userspace_roaming; break; case 't': if ((scan_interval = atoi(optarg)) < 0) { daemon_log(LOG_ERR, "Scan interval must be a positive, nonzero integer."); exit(1); } break; case 'p': if ((poll_interval = atoi(optarg)) < 0) { daemon_log(LOG_ERR, "Poll interval must be a positive, nonzero integer."); exit(1); } break; case 'h': _help = 1; break; case 'k': _kill = 1; break; case 'c': _check = 1; break; case 'v': _version = 1; break; default: daemon_log(LOG_ERR, "Unknown parameter."); 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 || _resume || _suspend || _issuescan) { int rv; if (_kill && wait_on_kill) rv = daemon_pid_file_kill_wait(SIGINT, 5); else rv = daemon_pid_file_kill(_kill ? SIGINT : (_resume ? SIGUSR2 : (_issuescan ? SIGHUP : SIGUSR1))); if (rv < 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 || pid == 0) { printf("waproamd not running.\n"); exit(255); } else { printf("waproamd process for device %s running as pid %u.\n", interface_name, pid); exit(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; }