#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #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 #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, ¤t_ap)) { if (!hw_addr_equal(¤t_ap, &null_ap)) { if (run_script(¤t_ap, "stop") < 0) return -1; } memcpy(¤t_ap, a, sizeof(struct hw_addr)); snprint_hw_addr(t, sizeof(t), ¤t_ap); setenv("AP", t, 1); setenv("IFACE", interface_name, 1); if (!hw_addr_equal(¤t_ap, &null_ap)) { fprintf(stderr, "Selected AP "); print_hw_addr(stderr, ¤t_ap); fprintf(stderr, "\n"); 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)) { 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(¤t_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; }