diff options
-rw-r--r-- | usr.sbin/hostapd/Makefile | 14 | ||||
-rw-r--r-- | usr.sbin/hostapd/apme.c | 168 | ||||
-rw-r--r-- | usr.sbin/hostapd/hostapd.8 | 163 | ||||
-rw-r--r-- | usr.sbin/hostapd/hostapd.c | 453 | ||||
-rw-r--r-- | usr.sbin/hostapd/hostapd.conf.5 | 95 | ||||
-rw-r--r-- | usr.sbin/hostapd/hostapd.h | 191 | ||||
-rw-r--r-- | usr.sbin/hostapd/iapp.c | 209 | ||||
-rw-r--r-- | usr.sbin/hostapd/llc.c | 102 | ||||
-rw-r--r-- | usr.sbin/hostapd/parse.y | 562 | ||||
-rw-r--r-- | usr.sbin/hostapd/privsep.c | 484 |
10 files changed, 2441 insertions, 0 deletions
diff --git a/usr.sbin/hostapd/Makefile b/usr.sbin/hostapd/Makefile new file mode 100644 index 00000000000..b34d30b6606 --- /dev/null +++ b/usr.sbin/hostapd/Makefile @@ -0,0 +1,14 @@ +# $OpenBSD: Makefile,v 1.1 2005/04/13 18:12:23 reyk Exp $ + +PROG= hostapd +SRCS= privsep.c apme.c iapp.c llc.c hostapd.c parse.y +MAN= hostapd.8 hostapd.conf.5 +LDADD= ${LIBEVENT} +CFLAGS+= -Wall -pedantic -I${.CURDIR} +CFLAGS+= -Wstrict-prototypes -Wmissing-prototypes +CFLAGS+= -Wmissing-declarations +CFLAGS+= -Wshadow -Wpointer-arith -Wcast-qual +CFLAGS+= -Wsign-compare +CLEANFILES= y.tab.h + +.include <bsd.prog.mk> diff --git a/usr.sbin/hostapd/apme.c b/usr.sbin/hostapd/apme.c new file mode 100644 index 00000000000..e131924b471 --- /dev/null +++ b/usr.sbin/hostapd/apme.c @@ -0,0 +1,168 @@ +/* $OpenBSD: apme.c,v 1.1 2005/04/13 18:12:23 reyk Exp $ */ + +/* + * Copyright (c) 2004, 2005 Reyk Floeter <reyk@vantronix.net> + * + * 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. + */ + +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_arp.h> +#include <net/if_llc.h> +#include <net/bpf.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <arpa/inet.h> + +#include <fcntl.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <unistd.h> + +#include "hostapd.h" + +void hostapd_apme_frame(struct hostapd_config *, u_int8_t *, u_int); + +void +hostapd_apme_input(int fd, short sig, void *arg) +{ + struct hostapd_config *cfg = (struct hostapd_config *)arg; + u_int8_t buf[IAPP_MAXSIZE], *bp, *ep; + struct bpf_hdr *bph; + ssize_t len; + + /* Ignore invalid signals */ + if (sig != EV_READ) + return; + + bzero(&buf, sizeof(buf)); + + if ((len = read(fd, buf, sizeof(buf))) < + (ssize_t)sizeof(struct ieee80211_frame)) + return; + + /* + * Loop through each frame. + */ + + bp = (u_int8_t *)&buf; + bph = (struct bpf_hdr *)bp; + ep = bp + len; + + while (bp < ep) { + register int caplen, hdrlen; + caplen = bph->bh_caplen; + hdrlen = bph->bh_hdrlen; + + /* Process frame */ + hostapd_apme_frame(cfg, bp + hdrlen, caplen); + + bp += BPF_WORDALIGN(caplen + hdrlen); + } +} + +void +hostapd_apme_frame(struct hostapd_config *cfg, u_int8_t *buf, u_int len) +{ + struct hostapd_node node; + struct ieee80211_frame *wh = (struct ieee80211_frame *)buf; + + /* Ignore short frames or fragments */ + if (len < sizeof(struct ieee80211_frame)) + return; + + /* + * Only accept local association response frames, ... + */ + if (!((wh->i_fc[1] & IEEE80211_FC1_DIR_MASK) == + IEEE80211_FC1_DIR_NODS && + (wh->i_fc[0] & IEEE80211_FC0_TYPE_MASK) == + IEEE80211_FC0_TYPE_MGT && + (wh->i_fc[0] & IEEE80211_FC0_SUBTYPE_MASK) == + IEEE80211_FC0_SUBTYPE_ASSOC_RESP)) + return; + + /* + * ...sent by the Host AP (addr2) to our BSSID (addr3) + */ + if (bcmp(wh->i_addr2, cfg->c_apme_bssid, IEEE80211_ADDR_LEN) != 0 || + bcmp(wh->i_addr3, cfg->c_apme_bssid, IEEE80211_ADDR_LEN) != 0) + return; + + cfg->c_stats.cn_rx_apme++; + + /* + * Double-check if the station got associated to our Host AP + */ + bcopy(wh->i_addr1, node.ni_macaddr, IEEE80211_ADDR_LEN); + if (hostapd_priv_apme_getnode(cfg, &node) != 0) { + hostapd_log(HOSTAPD_LOG_DEBUG, + "%s/%s: invalid association from %s on the Host AP\n", + cfg->c_apme_iface, cfg->c_iapp_iface, + ether_ntoa((struct ether_addr*)wh->i_addr1)); + return; + } + + /* Call ADD.notify handler */ + hostapd_iapp_add_notify(cfg, &node); +} + +void +hostapd_apme_init(struct hostapd_config *cfg) +{ + u_int i, dlt; + struct ifreq ifr; + + cfg->c_apme_raw = hostapd_bpf_open(O_RDONLY); + + cfg->c_apme_rawlen = IAPP_MAXSIZE; + if (ioctl(cfg->c_apme_raw, BIOCSBLEN, &cfg->c_apme_rawlen) == -1) + hostapd_fatal("failed to set BPF buffer len \"%s\": %s\n", + cfg->c_apme_iface, strerror(errno)); + + i = 1; + if (ioctl(cfg->c_apme_raw, BIOCIMMEDIATE, &i) == -1) + hostapd_fatal("failed to set BPF immediate mode on \"%s\": %s\n", + cfg->c_apme_iface, strerror(errno)); + + bzero(&ifr, sizeof(struct ifreq)); + strlcpy(ifr.ifr_name, cfg->c_apme_iface, sizeof(ifr.ifr_name)); + + /* This may fail, ignore it */ + ioctl(cfg->c_apme_raw, BIOCPROMISC, NULL); + + /* Associate the wireless network interface to the BPF descriptor */ + if (ioctl(cfg->c_apme_raw, BIOCSETIF, &ifr) == -1) + hostapd_fatal("failed to set BPF interface \"%s\": %s\n", + cfg->c_apme_iface, strerror(errno)); + + dlt = IAPP_DLT; + if (ioctl(cfg->c_apme_raw, BIOCSDLT, &dlt) == -1) + hostapd_fatal("failed to set BPF link type on \"%s\": %s\n", + cfg->c_apme_iface, strerror(errno)); + + /* Lock the BPF descriptor, no further configuration */ + if (ioctl(cfg->c_apme_raw, BIOCLOCK, NULL) == -1) + hostapd_fatal("failed to lock BPF interface on \"%s\": %s\n", + cfg->c_apme_iface, strerror(errno)); +} diff --git a/usr.sbin/hostapd/hostapd.8 b/usr.sbin/hostapd/hostapd.8 new file mode 100644 index 00000000000..89f7ab43cdb --- /dev/null +++ b/usr.sbin/hostapd/hostapd.8 @@ -0,0 +1,163 @@ +.\" $OpenBSD: hostapd.8,v 1.1 2005/04/13 18:12:23 reyk Exp $ +.\" +.\" Copyright (c) 2004, 2005 Reyk Floeter <reyk@vantronix.net> +.\" +.\" 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. +.\" +.Dd November 13, 2004 +.Dt HOSTAPD 8 +.Os +.Sh NAME +.Nm hostapd +.Nd Host Access Point daemon +.Sh SYNOPSIS +.Nm hostapd +.Op Fl dvb +.Op Fl a Ar interface +.Oo Xo +.Fl D Ar macro Ns = Ns Ar value Oc +.Xc +.Op Fl f Ar file +.Op Fl i Ar interface +.Sh DESCRIPTION +.Nm +is an daemon which allows communication between different 802.11 +wireless access points running in +.Pa Host AP +mode. +.Pp +.Nm +implements the Inter Access Points Protocol (IAPP). +It's purpose is to exchange station association updates between access +points in large wireless networks. +IAPP has been designed to speed up roaming between different access +points in the same +.Pa BSS . +IAPP is described in the +.Pa IEEE 802.11f +standard. +.Pp +.Nm +additionally allows to monitor and to log stations associations on a +non-hostap host which is receiving IAPP messages. +.Pp +.Nm +uses two network interfaces as options on startup. +The first interface +.Pa ( Fl a Ar interface ) +is used to access the +.Pa Host AP , +which is a wireless interface running in Host AP mode. Please use +.Xr ifconfig 4 +to enable Host AP mode. The second interface +.Pa ( Fl i Ar interface ) +is used to communicate with other +.Nm +in the same broadcast domain or multicast group. +Usually a wired interface is used to communicate with other +.Nm . +.Pp +The +.Nm +will send an +.Pa ADD.notify +IAPP message if a new station has been associated successfully to the +.Pa Host AP . +If the +.Nm +receives +.Pa ADD.notify +messages it will request the +.Pa Host AP +to remove a station which has been associated to another access point. +.Pp +The options are as follows: +.Bl -tag -width Ds +.It Fl a Ar interface +Specify the 802.11 wireless network interface running in +.Pa Host AP +mode. +This this option could be omitted to use the +.Nm +to log received +.Pa IAPP +messages. +.It Fl b +Use UDP broadcast instead of multicast group 224.0.1.178 for IAPP +messages. +.It Fl D Ar macro Ns = Ns Ar value +Define +.Ar macro +to be set to +.Ar value +on the command line. +Overrides the definition of +.Ar macro +in the configuration file. +.It Fl d +Do not daemonize and log to +.Em stderr . +.It Fl f Ar file +Use +.Ar file +as the configuration file, instead of the default +.Pa /etc/hostapd.conf . +.It Fl i Ar interface +Specify the network interface used to send +.Pa IAPP +messages. +It is important that the IAPP interface is on a trusted +network because there is no authentication and an attacker could force +disassociation of selected stations on all listening access points. +.It Fl v +Produce more verbose output. +.El +.Sh FILES +.Bl -tag -width "/etc/hostapd.confXXX" -compact +.It Pa /etc/hostapd.conf +default +.Nm +configuration file +.El +.Sh SEE ALSO +.Xr hostapd.conf 5 +.Xr ifconfig 4 +.Rs +.%R IEEE 802.11F +.%T Inter Access Point Protocol +.%D March 2001 +.Re +.Sh CAVEATS +.Nm +depends on drivers using the +.Pa net80211 +kernel wireless layer with support of +.Pa Host AP mode. +For traditional reasons, +the +.Xr wi 4 +driver still uses it's own Host AP code in +.Pa if_wi_hostap +which is not supported by +.Nm . +.Sh HISTORY +The +.Nm +program first appeared on the 21th Chaos Communication Congress +(http://www.ccc.de/congress/2004/) and later in +.Ox 3.8 . +.Sh AUTHORS +The +.Nm +program was written by +.An Reyk Floeter Aq reyk@openbsd.org . diff --git a/usr.sbin/hostapd/hostapd.c b/usr.sbin/hostapd/hostapd.c new file mode 100644 index 00000000000..05b2d8f71ae --- /dev/null +++ b/usr.sbin/hostapd/hostapd.c @@ -0,0 +1,453 @@ +/* $OpenBSD: hostapd.c,v 1.1 2005/04/13 18:12:23 reyk Exp $ */ + +/* + * Copyright (c) 2004, 2005 Reyk Floeter <reyk@vantronix.net> + * + * 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. + */ + +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/queue.h> +#include <sys/stat.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_arp.h> +#include <net/if_llc.h> +#include <net/bpf.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> + +#include "hostapd.h" + +void hostapd_usage(void); +void hostapd_udp_init(struct hostapd_config *); +void hostapd_sig_handler(int); + +struct hostapd_config hostapd_cfg; + +extern char *__progname; + +/* defined in event(3) to terminate the event loop */ +extern volatile sig_atomic_t event_gotterm; + +void +hostapd_usage(void) +{ + fprintf(stderr, "usage: %s [-dvb] [-a interface] [-D macro=value] " + "[-f file] [-i interface]\n", + __progname); + exit(EXIT_FAILURE); +} + +void +hostapd_log(u_int level, const char *fmt, ...) +{ + va_list ap; + + if (level > hostapd_cfg.c_verbose) + return; + + va_start(ap, fmt); + if (hostapd_cfg.c_debug) + vfprintf(stderr, fmt, ap); + else + vsyslog(LOG_INFO, fmt, ap); + fflush(stderr); + va_end(ap); +} + +void +hostapd_fatal(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + if (hostapd_cfg.c_debug) + vfprintf(stderr, fmt, ap); + else + vsyslog(LOG_ERR, fmt, ap); + fflush(stderr); + va_end(ap); + + hostapd_cleanup(&hostapd_cfg); + + exit(EXIT_FAILURE); +} + +int +hostapd_check_file_secrecy(int fd, const char *fname) +{ + struct stat st; + + if (fstat(fd, &st)) { + hostapd_log(HOSTAPD_LOG, + "cannot stat %s\n", fname); + return (-1); + } + + if (st.st_uid != 0 && st.st_uid != getuid()) { + hostapd_log(HOSTAPD_LOG, + "%s: owner not root or current user\n", fname); + return (-1); + } + + if (st.st_mode & (S_IRWXG | S_IRWXO)) { + hostapd_log(HOSTAPD_LOG, + "%s: group/world readable/writeable\n", fname); + return (-1); + } + + return (0); +} + +int +hostapd_bpf_open(u_int flags) +{ + u_int i; + int fd = -1; + char *dev; + struct bpf_version bpv; + + /* + * Try to open the next available BPF device + */ + for (i = 0; i < 255; i++) { + if (asprintf(&dev, "/dev/bpf%u", i) == -1) + hostapd_fatal("failed to allocate buffer\n"); + + if ((fd = open(dev, flags)) != -1) + break; + + free(dev); + } + + if (fd == -1) { + free(dev); + hostapd_fatal("unable to open BPF device\n"); + } + + free(dev); + + /* + * Get and validate the BPF version + */ + + if (ioctl(fd, BIOCVERSION, &bpv) == -1) + hostapd_fatal("failed to get BPF version: %s\n", + strerror(errno)); + + if (bpv.bv_major != BPF_MAJOR_VERSION || + bpv.bv_minor < BPF_MINOR_VERSION) + hostapd_fatal("invalid BPF version\n"); + + return (fd); +} + +void +hostapd_udp_init(struct hostapd_config *cfg) +{ + struct ifreq ifr; + struct sockaddr_in *addr, baddr; + struct ip_mreq mreq; + int brd = 1; + + bzero(&ifr, sizeof(ifr)); + + /* + * Open a listening UDP socket + */ + + if ((cfg->c_iapp_udp = socket(AF_INET, SOCK_DGRAM, 0)) == -1) + hostapd_fatal("unable to open udp socket\n"); + + cfg->c_flags |= HOSTAPD_CFG_F_UDP; + + strlcpy(ifr.ifr_name, cfg->c_iapp_iface, sizeof(ifr.ifr_name)); + + if (ioctl(cfg->c_iapp_udp, SIOCGIFADDR, &ifr) == -1) + hostapd_fatal("UDP ioctl %s on \"%s\" failed: %s\n", + "SIOCGIFADDR", ifr.ifr_name, strerror(errno)); + + addr = (struct sockaddr_in *)&ifr.ifr_addr; + cfg->c_iapp_addr.sin_family = AF_INET; + cfg->c_iapp_addr.sin_addr.s_addr = addr->sin_addr.s_addr; + cfg->c_iapp_addr.sin_port = htons(IAPP_PORT); + + if (ioctl(cfg->c_iapp_udp, SIOCGIFBRDADDR, &ifr) == -1) + hostapd_fatal("UDP ioctl %s on \"%s\" failed: %s\n", + "SIOCGIFBRDADDR", ifr.ifr_name, strerror(errno)); + + addr = (struct sockaddr_in *)&ifr.ifr_addr; + cfg->c_iapp_broadcast.sin_family = AF_INET; + cfg->c_iapp_broadcast.sin_addr.s_addr = addr->sin_addr.s_addr; + cfg->c_iapp_broadcast.sin_port = htons(IAPP_PORT); + + baddr.sin_family = AF_INET; + baddr.sin_addr.s_addr = htonl(INADDR_ANY); + baddr.sin_port = htons(IAPP_PORT); + + if (bind(cfg->c_iapp_udp, (struct sockaddr *)&baddr, + sizeof(baddr)) == -1) + hostapd_fatal("failed to bind UDP socket: %s\n", + strerror(errno)); + + /* + * The revised 802.11F standard requires IAPP messages to be + * send via multicast to the group 224.0.1.178. Nevertheless, + * some implementations still use broadcasts for IAPP + * messages. + */ + if (cfg->c_flags & HOSTAPD_CFG_F_BRDCAST) { + /* + * Enable broadcast + */ + + hostapd_log(HOSTAPD_LOG_DEBUG, "using broadcast mode\n"); + + if (setsockopt(cfg->c_iapp_udp, SOL_SOCKET, SO_BROADCAST, + &brd, sizeof(brd)) == -1) + hostapd_fatal("failed to enable broadcast on socket\n"); + } else { + /* + * Enable multicast + */ + + hostapd_log(HOSTAPD_LOG_DEBUG, "using multicast mode\n"); + + bzero(&mreq, sizeof(mreq)); + + cfg->c_iapp_multicast.sin_family = AF_INET; + cfg->c_iapp_multicast.sin_addr.s_addr = + inet_addr(IAPP_MCASTADDR); + cfg->c_iapp_multicast.sin_port = htons(IAPP_PORT); + + mreq.imr_multiaddr.s_addr = + cfg->c_iapp_multicast.sin_addr.s_addr; + mreq.imr_interface.s_addr = + cfg->c_iapp_addr.sin_addr.s_addr; + + if (setsockopt(cfg->c_iapp_udp, IPPROTO_IP, + IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) + hostapd_fatal("failed to add multicast membership to %s:" + " %s\n", + IAPP_MCASTADDR, strerror(errno)); + } +} + +void +hostapd_sig_handler(int sig) +{ + switch (sig) { + case SIGALRM: + case SIGTERM: + case SIGQUIT: + case SIGINT: + /* This will terminate libevent's main loop */ + event_gotterm = 1; + } +} + +void +hostapd_cleanup(struct hostapd_config *cfg) +{ + struct ip_mreq mreq; + + if (cfg->c_flags & HOSTAPD_CFG_F_PRIV && + (cfg->c_flags & HOSTAPD_CFG_F_BRDCAST) == 0 && + cfg->c_apme_n == 0) { + /* + * Disable multicast and let the kernel unsubscribe + * from the multicast group. + */ + + bzero(&mreq, sizeof(mreq)); + + mreq.imr_multiaddr.s_addr = + inet_addr(IAPP_MCASTADDR); + mreq.imr_interface.s_addr = + cfg->c_iapp_addr.sin_addr.s_addr; + + if (setsockopt(cfg->c_iapp_udp, IPPROTO_IP, + IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) + hostapd_log(HOSTAPD_LOG, "failed to remove multicast" + " membership to %s: %s\n", + IAPP_MCASTADDR, strerror(errno)); + } + + if ((cfg->c_flags & HOSTAPD_CFG_F_PRIV) == 0 && + cfg->c_flags & HOSTAPD_CFG_F_APME) { + /* Shutdown the Host AP protocol handler */ + hostapd_iapp_term(&hostapd_cfg); + } + + hostapd_log(HOSTAPD_LOG_VERBOSE, "bye!\n"); + + if (!cfg->c_debug) + closelog(); + + /* Close all open file descriptors and sockets */ + closefrom(0); +} + +int +main(int argc, char *argv[]) +{ + struct hostapd_config *cfg = &hostapd_cfg; + char *iapp_iface = NULL, *hostap_iface = NULL, *config = NULL; + int ch; + + bzero(cfg, sizeof(struct hostapd_config)); + + /* + * Get and parse command line options + */ + while ((ch = getopt(argc, argv, "a:bf:D:di:v")) != -1) { + switch (ch) { + case 'a': + hostap_iface = optarg; + cfg->c_flags |= HOSTAPD_CFG_F_APME; + break; + case 'b': + cfg->c_flags |= HOSTAPD_CFG_F_BRDCAST; + break; + case 'f': + config = optarg; + break; + case 'D': + if (hostapd_parse_symset(optarg) < 0) + hostapd_fatal("could not parse macro definition %s", + optarg); + break; + case 'd': + cfg->c_debug++; + break; + case 'i': + iapp_iface = optarg; + cfg->c_flags |= HOSTAPD_CFG_F_IAPP; + break; + case 'v': + cfg->c_verbose++; + break; + default: + hostapd_usage(); + } + } + + if (config == NULL) + strlcpy(cfg->c_config, HOSTAPD_CONFIG, sizeof(cfg->c_config)); + else + strlcpy(cfg->c_config, config, sizeof(cfg->c_config)); + + if (iapp_iface != NULL) + strlcpy(cfg->c_iapp_iface, iapp_iface, + sizeof(cfg->c_iapp_iface)); + + if (hostap_iface != NULL) + strlcpy(cfg->c_apme_iface, hostap_iface, + sizeof(cfg->c_apme_iface)); + + if (!cfg->c_debug) { + openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON); + daemon(0, 0); + } + + /* Parse the configuration file */ + hostapd_parse_file(cfg); + + if ((cfg->c_flags & HOSTAPD_CFG_F_IAPP) == 0) + hostapd_usage(); + + if ((cfg->c_flags & HOSTAPD_CFG_F_APME) == 0) + strlcpy(cfg->c_apme_iface, "<none>", sizeof(cfg->c_apme_iface)); + + /* + * Setup the hostapd handlers + */ + hostapd_udp_init(cfg); + hostapd_llc_init(cfg); + + if (cfg->c_flags & HOSTAPD_CFG_F_APME) + hostapd_apme_init(cfg); + else + hostapd_log(HOSTAPD_LOG, "%s/%s: running without a Host AP\n", + cfg->c_apme_iface, cfg->c_iapp_iface); + + /* Drop all privileges in an unprivileged child process */ + hostapd_priv_init(cfg); + + setproctitle("Host AP: %s, IAPP: %s", + cfg->c_apme_iface, cfg->c_iapp_iface); + + /* + * Unprivileged child process + */ + + event_init(); + + /* + * Set signal handlers + */ + signal(SIGALRM, hostapd_sig_handler); + signal(SIGTERM, hostapd_sig_handler); + signal(SIGQUIT, hostapd_sig_handler); + signal(SIGINT, hostapd_sig_handler); + signal(SIGHUP, SIG_IGN); + signal(SIGCHLD, SIG_IGN); + + /* Initialize the IAPP protcol handler */ + hostapd_iapp_init(cfg); + + /* + * Schedule the Host AP listener + */ + if (cfg->c_flags & HOSTAPD_CFG_F_APME) { + event_set(&cfg->c_apme_ev, cfg->c_apme_raw, + EV_READ | EV_PERSIST, hostapd_apme_input, cfg); + event_add(&cfg->c_apme_ev, NULL); + } + + /* + * Schedule the IAPP listener + */ + event_set(&cfg->c_iapp_udp_ev, cfg->c_iapp_udp, EV_READ | EV_PERSIST, + hostapd_iapp_input, cfg); + event_add(&cfg->c_iapp_udp_ev, NULL); + + hostapd_log(HOSTAPD_LOG, "%s/%s: starting hostapd with pid %u\n", + cfg->c_apme_iface, cfg->c_iapp_iface, getpid()); + + /* Run event loop */ + event_dispatch(); + + /* Executed after the event loop has been terminated */ + hostapd_cleanup(cfg); + + return(EXIT_SUCCESS); +} diff --git a/usr.sbin/hostapd/hostapd.conf.5 b/usr.sbin/hostapd/hostapd.conf.5 new file mode 100644 index 00000000000..6e3b1c9ba26 --- /dev/null +++ b/usr.sbin/hostapd/hostapd.conf.5 @@ -0,0 +1,95 @@ +.\" $OpenBSD: hostapd.conf.5,v 1.1 2005/04/13 18:12:23 reyk Exp $ +.\" +.\" Copyright (c) 2004, 2005 Reyk Floeter <reyk@vantronix.net> +.\" +.\" 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. +.\" +.Dd April 13, 2004 +.Dt HOSTAPD.CONF 5 +.Os +.Sh NAME +.Nm hostapd.conf +.Nd configuration file for the Host Access Point daemon +.Sh DESCRIPTION +.Nm +is the configuration file for the +.Xr hostapd 8 +daemon. +.Sh SECTIONS +The +.Nm +file is divided into two main sections. +.Bl -tag -width xxxx +.It Sy Macros +User-defined variables may be defined and used later, simplifying the +configuration file. +.It Sy Global Configuration +Global settings for +.Xr hostapd 8 . +.El +.Pp +Comments can be put anywhere in the file using a hash mark +.Pq Sq # , +and extends to the end of the current line. +.Sh MACROS +Much like +.Xr cpp 1 +or +.Xr m4 1 , +macros can be defined that will later be expanded in context. +Macro names must start with a letter, and may contain letters, digits +and underscores. +Macro names may not be reserved words (for example, +.Ic set , +.Ic interface , +or +.Ic hostap ) . +Macros are not expanded inside quotes. +.Pp +For example, +.Bd -literal -offset indent +wlan="ath0" +set iapp interface $wlan +.Ed +.Sh GLOBAL CONFIGURATION +The following configuration settings are understood: +.Bl -tag -width Ds +.It Ic set hostap interface Ar interface +Specify the wireless interface running in +.Pa Host AP +mode. +This this option could be omitted to use the +.Xr hostapd 8 +to log received +.Pa IAPP +messages. +.It Ic set iapp interface Ar interface +Specify the mandatory Inter-Access-Point (IAPP) interface. +.It Ic set iapp mode Ar mode +Specify the IAPP mode, if not using the default one. +IAPP could be used in multicast (default) or in broadcast mode. +The used multicast group is 224.0.1.178. +.Pp +Possible options: +.Bd -literal -offset indent +set iapp mode multicast +set iapp mode broadcast +.Ed +.El +.Sh SEE ALSO +.Xr hostapd 8 +.Sh AUTHORS +The +.Xr hostapd 8 +program was written by +.An Reyk Floeter Aq reyk@openbsd.org . diff --git a/usr.sbin/hostapd/hostapd.h b/usr.sbin/hostapd/hostapd.h new file mode 100644 index 00000000000..ec8040824c4 --- /dev/null +++ b/usr.sbin/hostapd/hostapd.h @@ -0,0 +1,191 @@ +/* $OpenBSD: hostapd.h,v 1.1 2005/04/13 18:12:23 reyk Exp $ */ + +/* + * Copyright (c) 2004, 2005 Reyk Floeter <reyk@vantronix.net> + * + * 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. + */ + +#ifndef _HOSTAPD_H +#define _HOSTAPD_H + +#include <sys/param.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <arpa/inet.h> + +#include <errno.h> +#include <event.h> +#include <syslog.h> + +#include <net80211/ieee80211.h> +#include <net80211/ieee80211_ioctl.h> + +#define IEEE80211_IAPP_VERSION 0 + +/* + * hostapd (IAPP) <-> Host AP (APME) + */ + +#define SIOCS80211IAPP 0 +#define SIOCG80211IAPP 1 + +struct hostapd_node { + u_int8_t ni_macaddr[IEEE80211_ADDR_LEN]; + u_int8_t ni_bssid[IEEE80211_ADDR_LEN]; + u_int32_t ni_associd; + u_int16_t ni_capinfo; + u_int16_t ni_flags; + u_int16_t ni_rxseq; + u_int16_t ni_rssi; +}; + +/* + * IAPP <-> IAPP + */ + +struct ieee80211_iapp_frame { + u_int8_t i_version; + u_int8_t i_command; + u_int16_t i_identifier; + u_int16_t i_length; +} __packed; + +enum ieee80211_iapp_frame_type { + IEEE80211_IAPP_FRAME_ADD_NOTIFY = 0, + IEEE80211_IAPP_FRAME_MOVE_NOTIFY = 1, + IEEE80211_IAPP_FRAME_MOVE_RESPONSE = 2, + IEEE80211_IAPP_FRAME_SEND_SECURITY_BLOCK = 3, + IEEE80211_IAPP_FRAME_ACK_SECURITY_BLOCK = 4, + IEEE80211_IAPP_FRAME_CACHE_NOTIFY = 5, + IEEE80211_IAPP_FRAME_CACHE_RESPONSE = 6 +}; + +struct ieee80211_iapp_add_notify { + u_int8_t a_length; + u_int8_t a_reserved; + u_int8_t a_macaddr[IEEE80211_ADDR_LEN]; + u_int16_t a_seqnum; +} __packed; + +/* + * IAPP -> switches (LLC) + */ + +struct hostapd_llc { + struct ether_header x_hdr; + struct llc x_llc; +} __packed; + +#define IAPP_LLC LLC_XID +#define IAPP_LLC_XID 0x81 +#define IAPP_LLC_CLASS 1 +#define IAPP_LLC_WINDOW 1 << 1 + +/* + * hostapd configuration + */ + +struct hostapd_counter { + u_int64_t cn_tx_llc; /* sent LLC messages */ + u_int64_t cn_rx_iapp; /* received IAPP messages */ + u_int64_t cn_tx_iapp; /* sent IAPP messages */ + u_int64_t cn_rx_apme; /* received Host AP messages */ + u_int64_t cn_tx_apme; /* sent Host AP messages */ +}; + +struct hostapd_config { + int c_apme; + int c_apme_raw; + u_int c_apme_rawlen; + struct event c_apme_ev; + char c_apme_iface[IFNAMSIZ]; + int c_apme_n; + u_int8_t c_apme_bssid[IEEE80211_ADDR_LEN]; + + u_int16_t c_iapp; + int c_iapp_raw; + char c_iapp_iface[IFNAMSIZ]; + int c_iapp_udp; + struct event c_iapp_udp_ev; + u_int16_t c_iapp_udp_port; + struct sockaddr_in c_iapp_addr; + struct sockaddr_in c_iapp_broadcast; + struct sockaddr_in c_iapp_multicast; + + u_int8_t c_flags; + +#define HOSTAPD_CFG_F_APME 0x01 +#define HOSTAPD_CFG_F_IAPP 0x02 +#define HOSTAPD_CFG_F_RAW 0x04 +#define HOSTAPD_CFG_F_UDP 0x08 +#define HOSTAPD_CFG_F_BRDCAST 0x10 +#define HOSTAPD_CFG_F_PRIV 0x20 + + struct event c_priv_ev; + + char c_config[MAXPATHLEN]; + + u_int c_verbose; + u_int c_debug; + + struct hostapd_counter c_stats; +}; + +#define IAPP_PORT 3517 /* XXX this should be added to /etc/services */ +#define IAPP_MCASTADDR "224.0.1.178" +#define IAPP_DLT DLT_IEEE802_11 +#define IAPP_MAXSIZE 512 + +#define HOSTAPD_USER "_hostapd" + +#define HOSTAPD_CONFIG "/etc/hostapd.conf" + +#define HOSTAPD_LOG 0 +#define HOSTAPD_LOG_VERBOSE 1 +#define HOSTAPD_LOG_DEBUG 2 + +__BEGIN_DECLS + +void hostapd_log(u_int, const char *, ...); +void hostapd_fatal(const char *, ...); +int hostapd_bpf_open(u_int); +void hostapd_cleanup(struct hostapd_config *); +int hostapd_check_file_secrecy(int, const char *); + +int hostapd_parse_file(struct hostapd_config *); +int hostapd_parse_symset(char *); + +void hostapd_priv_init(struct hostapd_config *); +int hostapd_priv_llc_xid(struct hostapd_config *, struct hostapd_node *); +void hostapd_priv_apme_bssid(struct hostapd_config *); +int hostapd_priv_apme_getnode(struct hostapd_config *, struct hostapd_node *); +int hostapd_priv_apme_delnode(struct hostapd_config *, struct hostapd_node *); + +void hostapd_apme_init(struct hostapd_config *); +void hostapd_apme_input(int, short, void *); + +void hostapd_iapp_init(struct hostapd_config *); +void hostapd_iapp_term(struct hostapd_config *); +int hostapd_iapp_add_notify(struct hostapd_config *, struct hostapd_node *); +void hostapd_iapp_input(int, short, void *); + +void hostapd_llc_init(struct hostapd_config *); +int hostapd_llc_send_xid(struct hostapd_config *, struct hostapd_node *); + +__END_DECLS + +#endif /* _HOSTAPD_H */ diff --git a/usr.sbin/hostapd/iapp.c b/usr.sbin/hostapd/iapp.c new file mode 100644 index 00000000000..60c64c1b438 --- /dev/null +++ b/usr.sbin/hostapd/iapp.c @@ -0,0 +1,209 @@ +/* $OpenBSD: iapp.c,v 1.1 2005/04/13 18:12:23 reyk Exp $ */ + +/* + * Copyright (c) 2004, 2005 Reyk Floeter <reyk@vantronix.net> + * + * 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. + */ + +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_arp.h> +#include <net/if_llc.h> +#include <net/bpf.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <arpa/inet.h> + +#include <fcntl.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "hostapd.h" + +void +hostapd_iapp_init(struct hostapd_config *cfg) +{ + if ((cfg->c_flags & HOSTAPD_CFG_F_APME) == 0) + return; + + /* Get Host AP's BSSID */ + hostapd_priv_apme_bssid(cfg); + + hostapd_log(HOSTAPD_LOG_VERBOSE, + "%s/%s: attaching to Host AP with BSSID \"%s\"\n", + cfg->c_apme_iface, cfg->c_iapp_iface, + ether_ntoa((struct ether_addr *)cfg->c_apme_bssid)); +} + +void +hostapd_iapp_term(struct hostapd_config *cfg) +{ + if ((cfg->c_flags & HOSTAPD_CFG_F_APME) == 0) + return; + + /* XXX not yet used but inspired by the APME TERMINATE action */ + hostapd_log(HOSTAPD_LOG_VERBOSE, "%s/%s: detaching from Host AP\n", + cfg->c_apme_iface, cfg->c_iapp_iface); +} + +int +hostapd_iapp_add_notify(struct hostapd_config *cfg, struct hostapd_node *node) +{ + struct sockaddr_in *addr; + struct { + struct ieee80211_iapp_frame hdr; + struct ieee80211_iapp_add_notify add; + } __packed frame; + + /* + * Send an ADD.notify message to other accesspoints to notify + * about a new association on our Host AP. + */ + + bzero(&frame, sizeof(frame)); + + frame.hdr.i_version = IEEE80211_IAPP_VERSION; + frame.hdr.i_command = IEEE80211_IAPP_FRAME_ADD_NOTIFY; + frame.hdr.i_identifier = htons(cfg->c_iapp++); + + frame.add.a_length = IEEE80211_ADDR_LEN; + frame.add.a_seqnum = htons(node->ni_rxseq); + bcopy(node->ni_macaddr, frame.add.a_macaddr, IEEE80211_ADDR_LEN); + + if (cfg->c_flags & HOSTAPD_CFG_F_BRDCAST) + addr = &cfg->c_iapp_broadcast; + else + addr = &cfg->c_iapp_multicast; + + if (sendto(cfg->c_iapp_udp, &frame, sizeof(frame), + 0, (struct sockaddr *)addr, sizeof(struct sockaddr_in)) == -1) { + hostapd_log(HOSTAPD_LOG, + "%s/%s: failed to send ADD notification: %s\n", + cfg->c_apme_iface, cfg->c_iapp_iface, + strerror(errno)); + return (errno); + } + + hostapd_log(HOSTAPD_LOG, "%s/%s: sent ADD notification for %s\n", + cfg->c_apme_iface, cfg->c_iapp_iface, + ether_ntoa((struct ether_addr*)frame.add.a_macaddr)); + + /* Send a LLC XID frame, see llc.c for details */ + return (hostapd_priv_llc_xid(cfg, node)); +} + +void +hostapd_iapp_input(int fd, short sig, void *arg) +{ + struct hostapd_config *cfg = (struct hostapd_config *)arg; + struct sockaddr_in addr; + socklen_t addr_len; + u_int8_t buf[IAPP_MAXSIZE]; + struct hostapd_node node; + struct ieee80211_iapp_recv { + struct ieee80211_iapp_frame hdr; + union { + struct ieee80211_iapp_add_notify add; + } u; + } __packed *frame; + int ret = 0; + + /* Ignore invalid signals */ + if (sig != EV_READ) + return; + + /* + * Listen to possible messages from other IAPP + */ + + bzero(buf, sizeof(buf)); + + if (recvfrom(fd, buf, sizeof(buf), 0, + (struct sockaddr*)&addr, &addr_len) < 1) + return; + + if (memcmp(&cfg->c_iapp_addr.sin_addr, &addr.sin_addr, + sizeof(addr.sin_addr)) == 0) + return; + + frame = (struct ieee80211_iapp_recv*)buf; + + /* Validate the IAPP version */ + if (frame->hdr.i_version != IEEE80211_IAPP_VERSION || + addr_len < sizeof(struct sockaddr_in)) + return; + + cfg->c_stats.cn_rx_iapp++; + + /* + * Process the IAPP frame + */ + switch (frame->hdr.i_command) { + case IEEE80211_IAPP_FRAME_ADD_NOTIFY: + /* Don't support non-48bit MAC addresses, yet */ + if (frame->u.add.a_length != IEEE80211_ADDR_LEN) + return; + + node.ni_rxseq = frame->u.add.a_seqnum; + bcopy(frame->u.add.a_macaddr, node.ni_macaddr, + IEEE80211_ADDR_LEN); + + /* + * Try to remove a node from our Host AP and to free + * any allocated resources. Otherwise the received + * ADD.notify message will be ignored. + */ + if (cfg->c_flags & HOSTAPD_CFG_F_APME) + ret = hostapd_priv_apme_delnode(cfg, &node); + else + ret = 0; + + hostapd_log(HOSTAPD_LOG, "%s/%s: %s ADD notification " + "for %s at %s (%s)\n", + ret == 0 ? "received" : "ignored", + cfg->c_apme_iface, cfg->c_iapp_iface, + ether_ntoa((struct ether_addr*)node.ni_macaddr), + inet_ntoa(addr.sin_addr)); + break; + + case IEEE80211_IAPP_FRAME_MOVE_NOTIFY: + case IEEE80211_IAPP_FRAME_MOVE_RESPONSE: + case IEEE80211_IAPP_FRAME_SEND_SECURITY_BLOCK: + case IEEE80211_IAPP_FRAME_ACK_SECURITY_BLOCK: + case IEEE80211_IAPP_FRAME_CACHE_NOTIFY: + case IEEE80211_IAPP_FRAME_CACHE_RESPONSE: + + /* + * XXX TODO + */ + + hostapd_log(HOSTAPD_LOG_VERBOSE, + "%s/%s: received unsupported IAPP message %d\n", + cfg->c_apme_iface, cfg->c_iapp_iface, + frame->hdr.i_command); + return; + + default: + return; + } +} diff --git a/usr.sbin/hostapd/llc.c b/usr.sbin/hostapd/llc.c new file mode 100644 index 00000000000..e15460b4252 --- /dev/null +++ b/usr.sbin/hostapd/llc.c @@ -0,0 +1,102 @@ +/* $OpenBSD: llc.c,v 1.1 2005/04/13 18:12:23 reyk Exp $ */ + +/* + * Copyright (c) 2004, 2005 Reyk Floeter <reyk@vantronix.net> + * + * 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. + */ + +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_arp.h> +#include <net/if_llc.h> +#include <net/bpf.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <arpa/inet.h> + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "hostapd.h" + +void +hostapd_llc_init(struct hostapd_config *cfg) +{ + struct ifreq ifr; + u_int i; + + cfg->c_iapp_raw = hostapd_bpf_open(O_WRONLY); + + cfg->c_flags |= HOSTAPD_CFG_F_RAW; + + bzero(&ifr, sizeof(struct ifreq)); + strlcpy(ifr.ifr_name, cfg->c_iapp_iface, sizeof(ifr.ifr_name)); + + /* Associate the wired network interface to the BPF descriptor */ + if (ioctl(cfg->c_iapp_raw, BIOCSETIF, &ifr) == -1) + hostapd_fatal("failed to set BPF interface \"%s\": %s\n", + cfg->c_iapp_iface, strerror(errno)); + + i = 1; + if (ioctl(cfg->c_iapp_raw, BIOCSHDRCMPLT, &i) == -1) + hostapd_fatal("failed to set BPF header completion: %s\n", + strerror(errno)); + + /* Lock the BPF descriptor, no further configuration */ + if (ioctl(cfg->c_iapp_raw, BIOCLOCK, NULL) == -1) + hostapd_fatal("failed to lock BPF interface on \"%s\": %s\n", + cfg->c_iapp_iface, strerror(errno)); +} + +int +hostapd_llc_send_xid(struct hostapd_config *cfg, struct hostapd_node *node) +{ + struct hostapd_llc *llc; + u_int8_t buf[ETHER_HDR_LEN + (LLC_UFRAMELEN * 2)]; + + /* + * Send an IEEE 802.3 LLC XID frame which will possibly force + * our switch port on the wired network to learn the station's + * new MAC address. + */ + + bzero(&buf, sizeof(buf)); + + llc = (struct hostapd_llc *)&buf; + memset(&llc->x_hdr.ether_dhost, 0xff, + sizeof(llc->x_hdr.ether_dhost)); + bcopy(&node->ni_macaddr, &llc->x_hdr.ether_shost, + sizeof(llc->x_hdr.ether_shost)); + llc->x_hdr.ether_type = htons(sizeof(buf)); + llc->x_llc.llc_control = IAPP_LLC; + llc->x_llc.llc_fid = IAPP_LLC_XID; + llc->x_llc.llc_class = IAPP_LLC_CLASS; + llc->x_llc.llc_window = IAPP_LLC_WINDOW; + + if (write(cfg->c_iapp_raw, &buf, sizeof(buf)) == -1) + return (errno); + + return (0); +} diff --git a/usr.sbin/hostapd/parse.y b/usr.sbin/hostapd/parse.y new file mode 100644 index 00000000000..5a420f86ee1 --- /dev/null +++ b/usr.sbin/hostapd/parse.y @@ -0,0 +1,562 @@ +/* $OpenBSD: parse.y,v 1.1 2005/04/13 18:12:23 reyk Exp $ */ + +/* + * Copyright (c) 2004, 2005 Reyk Floeter <reyk@vantronix.net> + * + * 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. + */ + +%{ +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> +#include <sys/queue.h> +#include <sys/stat.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_arp.h> +#include <net/if_llc.h> +#include <net/bpf.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <arpa/inet.h> + +#include <ctype.h> +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> + +#include "hostapd.h" + +extern struct hostapd_config hostapd_cfg; + +static FILE *fin = NULL; +static int lineno = 1; +static int errors = 0; +char *infile; + +TAILQ_HEAD(symhead, sym) symhead = TAILQ_HEAD_INITIALIZER(symhead); +struct sym { + TAILQ_ENTRY(sym) entry; + int used; + int persist; + char *nam; + char *val; +}; + +int yyerror(const char *, ...); +int yyparse(void); +int kw_cmp(const void *, const void *); +int lookup(char *); +int lgetc(FILE *); +int lungetc(int); +int findeol(void); +int yylex(void); +int symset(const char *, const char *, int); +char *symget(const char *); + +typedef struct { + union { + char *string; + int val; + } v; + int lineno; +} YYSTYPE; + +%} + +%token MODE INTERFACE IAPP HOSTAP MULTICAST BROADCAST SET +%token ERROR +%token <v.string> STRING +%token <v.val> VALUE +%type <v.string> string + +%% + +/* + * Configuration grammar + */ + +grammar : /* empty */ + | grammar '\n' + | grammar option '\n' + | grammar varset '\n' + | grammar error '\n' { errors++; } + ; + +option : SET HOSTAP INTERFACE STRING + { + strlcpy(hostapd_cfg.c_apme_iface, $4, + sizeof(hostapd_cfg.c_apme_iface)); + + hostapd_cfg.c_flags |= HOSTAPD_CFG_F_APME; + + hostapd_log(HOSTAPD_LOG_DEBUG, + "parse %s: Host AP interface %s\n", + hostapd_cfg.c_config, $4); + + free($4); + } + | SET IAPP INTERFACE STRING + { + strlcpy(hostapd_cfg.c_iapp_iface, $4, + sizeof(hostapd_cfg.c_iapp_iface)); + + hostapd_cfg.c_flags |= HOSTAPD_CFG_F_IAPP; + + hostapd_log(HOSTAPD_LOG_DEBUG, "parse %s: " + "IAPP interface %s\n", hostapd_cfg.c_config, $4); + + free($4); + } + | SET IAPP MODE MULTICAST + { + hostapd_cfg.c_flags &= ~HOSTAPD_CFG_F_BRDCAST; + } + | SET IAPP MODE BROADCAST + { + hostapd_cfg.c_flags |= HOSTAPD_CFG_F_BRDCAST; + } + ; + +string : string STRING { + if (asprintf(&$$, "%s %s", $1, $2) == -1) + hostapd_fatal("string: asprintf"); + free($1); + free($2); + } + | STRING + ; + +varset : STRING '=' string + { + if (symset($1, $3, 0) == -1) + hostapd_fatal("cannot store variable"); + free($1); + free($3); + } + ; + +%% + +/* + * Parser and lexer + */ + +struct keywords { + char *k_name; + int k_val; +}; + +int +kw_cmp(const void *a, const void *b) +{ + return strcmp(a, ((const struct keywords *)b)->k_name); +} + +int +lookup(char *token) +{ + /* Keep this list sorted */ + static const struct keywords keywords[] = { + { "broadcast", BROADCAST }, + { "hostap", HOSTAP }, + { "iapp", IAPP }, + { "interface", INTERFACE }, + { "mode", MODE }, + { "multicast", MULTICAST }, + { "set", SET }, + }; + const struct keywords *p; + + p = bsearch(token, keywords, sizeof(keywords) / sizeof(keywords[0]), + sizeof(keywords[0]), kw_cmp); + + return (p == NULL ? STRING : p->k_val); +} + +#define MAXPUSHBACK 128 + +char *parsebuf; +int parseindex; +char pushback_buffer[MAXPUSHBACK]; +int pushback_index = 0; + +int +lgetc(FILE *f) +{ + int c, next; + + if (parsebuf) { + /* Read character from the parsebuffer instead of input. */ + if (parseindex >= 0) { + c = parsebuf[parseindex++]; + if (c != '\0') + return (c); + parsebuf = NULL; + } else + parseindex++; + } + + if (pushback_index) + return (pushback_buffer[--pushback_index]); + + while ((c = getc(f)) == '\\') { + next = getc(f); + if (next != '\n') { + if (isspace(next)) + yyerror("whitespace after \\"); + ungetc(next, f); + break; + } + yylval.lineno = lineno; + lineno++; + } + if (c == '\t' || c == ' ') { + /* Compress blanks to a single space. */ + do { + c = getc(f); + } while (c == '\t' || c == ' '); + ungetc(c, f); + c = ' '; + } + + return (c); +} + +int +lungetc(int c) +{ + if (c == EOF) + return (EOF); + if (parsebuf) { + parseindex--; + if (parseindex >= 0) + return (c); + } + if (pushback_index < MAXPUSHBACK-1) + return (pushback_buffer[pushback_index++] = c); + else + return (EOF); +} + +int +findeol(void) +{ + int c; + + parsebuf = NULL; + pushback_index = 0; + + /* skip to either EOF or the first real EOL */ + while (1) { + c = lgetc(fin); + if (c == '\n') { + lineno++; + break; + } + if (c == EOF) + break; + } + return (ERROR); +} + +int +yylex(void) +{ + char buf[8096]; + char *p, *val; + int endc, c; + int token; + +top: + p = buf; + while ((c = lgetc(fin)) == ' ') + ; /* nothing */ + + yylval.lineno = lineno; + if (c == '#') + while ((c = lgetc(fin)) != '\n' && c != EOF) + ; /* nothing */ + if (c == '$' && parsebuf == NULL) { + while (1) { + if ((c = lgetc(fin)) == EOF) + return (0); + + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + if (isalnum(c) || c == '_') { + *p++ = (char)c; + continue; + } + *p = '\0'; + lungetc(c); + break; + } + val = symget(buf); + if (val == NULL) { + yyerror("macro \"%s\" not defined", buf); + return (findeol()); + } + parsebuf = val; + parseindex = 0; + goto top; + } + + switch (c) { + case '\'': + case '"': + endc = c; + while (1) { + if ((c = lgetc(fin)) == EOF) + return (0); + if (c == endc) { + *p = '\0'; + break; + } + if (c == '\n') { + lineno++; + continue; + } + if (p + 1 >= buf + sizeof(buf) - 1) { + yyerror("string too long"); + return (findeol()); + } + *p++ = (char)c; + } + yylval.v.string = strdup(buf); + if (yylval.v.string == NULL) + hostapd_fatal("yylex: strdup"); + return (STRING); + } + +#define allowed_in_string(x) \ + (isalnum(x) || (ispunct(x) && x != '(' && x != ')' && \ + x != '{' && x != '}' && x != '<' && x != '>' && \ + x != '!' && x != '=' && x != '/' && x != '#' && \ + x != ',')) + + if (isalnum(c) || c == ':' || c == '_' || c == '*') { + do { + *p++ = c; + if ((unsigned)(p-buf) >= sizeof(buf)) { + yyerror("string too long"); + return (findeol()); + } + } while ((c = lgetc(fin)) != EOF && (allowed_in_string(c))); + lungetc(c); + *p = '\0'; + if ((token = lookup(buf)) == STRING) + if ((yylval.v.string = strdup(buf)) == NULL) + hostapd_fatal("yylex: strdup"); + return (token); + } + if (c == '\n') { + yylval.lineno = lineno; + lineno++; + } + if (c == EOF) + return (0); + return (c); +} + +int +symset(const char *nam, const char *val, int persist) +{ + struct sym *sym; + + for (sym = TAILQ_FIRST(&symhead); sym && strcmp(nam, sym->nam); + sym = TAILQ_NEXT(sym, entry)) + ; /* nothing */ + + if (sym != NULL) { + if (sym->persist == 1) + return (0); + else { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + if ((sym = calloc(1, sizeof(*sym))) == NULL) + return (-1); + + sym->nam = strdup(nam); + if (sym->nam == NULL) { + free(sym); + return (-1); + } + sym->val = strdup(val); + if (sym->val == NULL) { + free(sym->nam); + free(sym); + return (-1); + } + sym->used = 0; + sym->persist = persist; + TAILQ_INSERT_TAIL(&symhead, sym, entry); + + hostapd_log(HOSTAPD_LOG_DEBUG, "%s = \"%s\"\n", sym->nam, sym->val); + + return (0); +} + +int +hostapd_parse_symset(char *s) +{ + char *sym, *val; + int ret; + size_t len; + + if ((val = strrchr(s, '=')) == NULL) + return (-1); + + len = strlen(s) - strlen(val) + 1; + if ((sym = malloc(len)) == NULL) + hostapd_fatal("cmdline_symset: malloc"); + + strlcpy(sym, s, len); + + ret = symset(sym, val + 1, 1); + + free(sym); + + return (ret); +} + +char * +symget(const char *nam) +{ + struct sym *sym; + + TAILQ_FOREACH(sym, &symhead, entry) + if (strcmp(nam, sym->nam) == 0) { + sym->used = 1; + return (sym->val); + } + return (NULL); +} + +#if 0 +int +yylex(void) +{ + char *p; + int v; + + /* Locate next token */ + if (confptr == NULL) { + confptr = confbuf; + } else { + for (p = confptr; *p && p < confbuf + conflen; p++) + ; + *p++; + if (!*p) + return 0; + confptr = p; + } + + /* Numerical token? */ + if (isdigit(*confptr)) { + for (p = confptr; *p; p++) + if (*p == '.') /* IP-address, or bad input */ + goto is_string; + v = (int)strtol(confptr, (char **)NULL, 10); + yylval.val = v; + return VALUE; + } + + is_string: + if ((v = lookup(confptr)) == STRING) { + yylval.string = strdup(confptr); + if (yylval.string == NULL) + hostapd_fatal("yylex: strdup()"); + } + return v; +} +#endif + +int +hostapd_parse_file(struct hostapd_config *cfg) +{ + struct sym *sym, *next; + int ret; + + if ((fin = fopen(cfg->c_config, "r")) == NULL) { + hostapd_log(HOSTAPD_LOG, "unable to find %s, using defaults\n", + cfg->c_config); + return (0); + } + infile = cfg->c_config; + + if (hostapd_check_file_secrecy(fileno(fin), cfg->c_config)) { + hostapd_fatal("invalid permissions for %s\n", cfg->c_config); + fclose(fin); + return (-1); + } + + lineno = 1; + errors = 0; + + ret = yyparse(); + + fclose(fin); + + /* Free macros and check which have not been used. */ + for (sym = TAILQ_FIRST(&symhead); sym != NULL; sym = next) { + next = TAILQ_NEXT(sym, entry); + if (!sym->used) + hostapd_log(HOSTAPD_LOG_VERBOSE, + "warning: macro \"%s\" not used\n", sym->nam); + if (!sym->persist) { + free(sym->nam); + free(sym->val); + TAILQ_REMOVE(&symhead, sym, entry); + free(sym); + } + } + + return (ret); +} + +int +yyerror(const char *fmt, ...) +{ + va_list ap; + char *nfmt; + + errors = 1; + + va_start(ap, fmt); + if (asprintf(&nfmt, "%s:%d: %s\n", infile, yylval.lineno, fmt) == -1) + hostapd_fatal("yyerror asprintf"); + hostapd_log(HOSTAPD_LOG, nfmt, ap); + va_end(ap); + free(nfmt); + + return (0); +} + diff --git a/usr.sbin/hostapd/privsep.c b/usr.sbin/hostapd/privsep.c new file mode 100644 index 00000000000..5a395b82991 --- /dev/null +++ b/usr.sbin/hostapd/privsep.c @@ -0,0 +1,484 @@ +/* $OpenBSD: privsep.c,v 1.1 2005/04/13 18:12:23 reyk Exp $ */ + +/* + * Copyright (c) 2004, 2005 Reyk Floeter <reyk@vantronix.net> + * + * 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. + */ + +#include <sys/param.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/time.h> + +#include <net/if.h> +#include <net/if_dl.h> +#include <net/if_media.h> +#include <net/if_arp.h> +#include <net/if_llc.h> +#include <net/bpf.h> + +#include <netinet/in.h> +#include <netinet/if_ether.h> +#include <arpa/inet.h> + +#include <dev/ic/if_wi_ieee.h> +#include <dev/ic/if_wireg.h> +#include <dev/ic/if_wi_hostap.h> + +#include <errno.h> +#include <event.h> +#include <fcntl.h> +#include <netdb.h> +#include <pwd.h> +#include <signal.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "hostapd.h" + +enum hostapd_cmd_types { + PRIV_APME_BSSID, /* Get the Host AP's BSSID */ + PRIV_APME_GETNODE, /* Get a node from the Host AP */ + PRIV_APME_DELNODE, /* Delete a node from the Host AP */ + PRIV_LLC_SEND_XID /* Send IEEE 802.3 LLC XID frame */ +}; + +void hostapd_priv(int, short, void *); +void hostapd_sig_relay(int); +void hostapd_sig_chld(int); +int hostapd_may_read(int, void *, size_t); +void hostapd_must_read(int, void *, size_t); +void hostapd_must_write(int, void *, size_t); + +static int priv_fd = -1; +static volatile pid_t child_pid = -1; + +/* defined in event(3) to terminate the event loop */ +extern volatile sig_atomic_t event_gotterm; + +/* + * Main privsep functions + */ + +void +hostapd_priv_init(struct hostapd_config *cfg) +{ + int i, socks[2]; + struct passwd *pw; + struct servent *se; + + for (i = 1; i < _NSIG; i++) + signal(i, SIG_DFL); + + if ((se = getservbyname("iapp", "udp")) == NULL) { + cfg->c_iapp_udp_port = IAPP_PORT; + } else + cfg->c_iapp_udp_port = se->s_port; + + if ((pw = getpwnam(HOSTAPD_USER)) == NULL) + hostapd_fatal("failed to get user \"%s\"\n", HOSTAPD_USER); + + endpwent(); + endservent(); + + /* Create sockets */ + if (socketpair(AF_LOCAL, SOCK_STREAM, PF_UNSPEC, socks) == -1) + hostapd_fatal("failed to get socket pair"); + + if ((child_pid = fork()) < 0) + hostapd_fatal("failed to fork child process"); + + /* + * Unprivileged child process + */ + if (child_pid == 0) { + cfg->c_flags &= ~HOSTAPD_CFG_F_PRIV; + + /* + * Change the child's root directory to the unprivileged + * user's home directory + */ + if (chroot(pw->pw_dir) == -1) + hostapd_fatal("failed to change root directory\n"); + if (chdir("/") == -1) + hostapd_fatal("failed to change directory"); + + /* + * Drop privileges and clear the group access list + */ + if ((setgroups(1, &pw->pw_gid) || + setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid) || + setresgid(pw->pw_gid, pw->pw_gid, pw->pw_gid)) == -1) + hostapd_fatal("can't drop privileges"); + + close(socks[0]); + priv_fd = socks[1]; + return; + } + + + /* + * Privileged mother process + */ + cfg->c_flags |= HOSTAPD_CFG_F_PRIV; + + event_init(); + + /* Pass ALR/TERM/HUP through to child, and accept CHLD */ + signal(SIGALRM, hostapd_sig_relay); + signal(SIGTERM, hostapd_sig_relay); + signal(SIGINT, hostapd_sig_relay); + signal(SIGHUP, hostapd_sig_relay); + signal(SIGCHLD, hostapd_sig_chld); + + close(socks[1]); + + if (cfg->c_flags & HOSTAPD_CFG_F_APME) { + if ((cfg->c_apme = socket(AF_INET, SOCK_DGRAM, 0)) == -1) { + hostapd_fatal("unable to open ioctl socket\n"); + } + } + + setproctitle("[priv]"); + + /* Start a new event listener */ + event_set(&cfg->c_priv_ev, socks[0], EV_READ, hostapd_priv, cfg); + event_add(&cfg->c_priv_ev, NULL); + + /* Run privileged event loop */ + event_dispatch(); + + /* Executed after the event loop has been terminated */ + hostapd_cleanup(cfg); + + _exit(EXIT_SUCCESS); +} + +void +hostapd_priv(int fd, short sig, void *arg) +{ + struct hostapd_config *cfg = (struct hostapd_config *)arg; + struct hostapd_node node; + struct ieee80211_bssid bssid; + struct hostap_sta sta; + struct ifreq ifr; + int ret, cmd; + + /* Terminate the event if we got an invalid signal */ + if (sig != EV_READ) + return; + + bzero(&node, sizeof(struct hostapd_node)); + bzero(&sta, sizeof(struct hostap_sta)); + + /* Get privsep command */ + if (hostapd_may_read(fd, &cmd, sizeof(int))) + return; + + switch (cmd) { + case PRIV_APME_BSSID: + hostapd_log(HOSTAPD_LOG_DEBUG, + "[priv]: msg PRIV_APME_BSSID received\n"); + + strlcpy(bssid.i_name, cfg->c_apme_iface, sizeof(bssid.i_name)); + + /* Try to get the APME's BSSID */ + if (cfg->c_flags & HOSTAPD_CFG_F_APME) { + if ((ret = ioctl(cfg->c_apme, + SIOCG80211BSSID, &bssid)) != 0) + ret = errno; + } else + ret = ENXIO; + + hostapd_must_write(fd, &ret, sizeof(int)); + if (ret == 0) { + hostapd_must_write(fd, &bssid.i_bssid, + IEEE80211_ADDR_LEN); + } + break; + + case PRIV_APME_GETNODE: + hostapd_log(HOSTAPD_LOG_DEBUG, + "[priv]: msg PRIV_APME_GETNODE received\n"); + + hostapd_must_read(fd, &node, sizeof(struct hostapd_node)); + bcopy(node.ni_macaddr, sta.addr, IEEE80211_ADDR_LEN); + + strlcpy(ifr.ifr_name, cfg->c_apme_iface, sizeof(ifr.ifr_name)); + ifr.ifr_data = (caddr_t)&sta; + + /* Try to get a station from the APME */ + if (cfg->c_flags & HOSTAPD_CFG_F_APME) { + if ((ret = ioctl(cfg->c_apme, + SIOCHOSTAP_GET, &ifr)) != 0) + ret = errno; + } else + ret = ENXIO; + + hostapd_must_write(fd, &ret, sizeof(int)); + if (ret == 0) { + node.ni_associd = sta.asid; + node.ni_flags = sta.flags; + node.ni_rssi = sta.sig_info; + node.ni_capinfo = sta.capinfo; + + hostapd_must_write(fd, &node, sizeof(struct hostapd_node)); + } + break; + + case PRIV_APME_DELNODE: + hostapd_log(HOSTAPD_LOG_DEBUG, + "[priv]: msg PRIV_APME_DELNODE received\n"); + + hostapd_must_read(fd, &node, sizeof(struct hostapd_node)); + bcopy(node.ni_macaddr, sta.addr, IEEE80211_ADDR_LEN); + + /* Try to delete a station from the APME */ + if (cfg->c_flags & HOSTAPD_CFG_F_APME) { + if ((ret = ioctl(cfg->c_apme, + SIOCHOSTAP_DEL, &sta)) != 0) + ret = errno; + } else + ret = ENXIO; + + hostapd_must_write(fd, &ret, sizeof(int)); + break; + + case PRIV_LLC_SEND_XID: + hostapd_log(HOSTAPD_LOG_DEBUG, + "[priv]: msg PRIV_LLC_SEND_XID received\n"); + + hostapd_must_read(fd, &node, sizeof(struct hostapd_node)); + + /* Send a LLC XID frame to reset possible switch ports */ + ret = hostapd_llc_send_xid(cfg, &node); + + hostapd_must_write(fd, &ret, sizeof(int)); + break; + + default: + hostapd_fatal("[priv]: unknown command %d\n", cmd); + } + + event_add(&cfg->c_priv_ev, NULL); +} + +/* + * Unprivileged callers + */ + +int +hostapd_priv_apme_getnode(struct hostapd_config *cfg, struct hostapd_node *node) +{ + int ret, cmd; + + if (priv_fd < 0) + hostapd_fatal("%s: called from privileged portion\n", __func__); + + if ((cfg->c_flags & HOSTAPD_CFG_F_APME) == 0) + hostapd_fatal("%s: Host AP is not available\n", __func__); + + cmd = PRIV_APME_GETNODE; + hostapd_must_write(priv_fd, &cmd, sizeof(int)); + hostapd_must_write(priv_fd, node, sizeof(struct hostapd_node)); + + hostapd_must_read(priv_fd, &ret, sizeof(int)); + if (ret != 0) + return (ret); + + hostapd_must_read(priv_fd, node, sizeof(struct hostapd_node)); + + cfg->c_stats.cn_tx_apme++; + + return (ret); +} + +int +hostapd_priv_apme_delnode(struct hostapd_config *cfg, struct hostapd_node *node) +{ + int ret, cmd; + + if (priv_fd < 0) + hostapd_fatal("%s: called from privileged portion\n", __func__); + + if ((cfg->c_flags & HOSTAPD_CFG_F_APME) == 0) + hostapd_fatal("%s: Host AP is not available\n", __func__); + + cmd = PRIV_APME_DELNODE; + hostapd_must_write(priv_fd, &cmd, sizeof(int)); + hostapd_must_write(priv_fd, node, sizeof(struct hostapd_node)); + + hostapd_must_read(priv_fd, &ret, sizeof(int)); + + if (ret == 0) + cfg->c_stats.cn_tx_apme++; + + return (ret); +} + +void +hostapd_priv_apme_bssid(struct hostapd_config *cfg) +{ + int ret, cmd; + + if (priv_fd < 0) + hostapd_fatal("%s: called from privileged portion\n", __func__); + + if ((cfg->c_flags & HOSTAPD_CFG_F_APME) == 0) + hostapd_fatal("%s: Host AP is not available\n", __func__); + + cmd = PRIV_APME_BSSID; + hostapd_must_write(priv_fd, &cmd, sizeof(int)); + + hostapd_must_read(priv_fd, &ret, sizeof(int)); + if (ret != 0) + hostapd_fatal("%s: failed to get Host AP's BSSID on" + " \"%s\": %s\n", + cfg->c_apme_iface, strerror(errno)); + + hostapd_must_read(priv_fd, &cfg->c_apme_bssid, IEEE80211_ADDR_LEN); + + cfg->c_stats.cn_tx_apme++; +} + +int +hostapd_priv_llc_xid(struct hostapd_config *cfg, struct hostapd_node *node) +{ + int ret, cmd; + + if (priv_fd < 0) + hostapd_fatal("%s: called from privileged portion\n", __func__); + + cmd = PRIV_LLC_SEND_XID; + hostapd_must_write(priv_fd, &cmd, sizeof(int)); + hostapd_must_write(priv_fd, node, sizeof(struct hostapd_node)); + hostapd_must_read(priv_fd, &ret, sizeof(int)); + + if (ret == 0) + cfg->c_stats.cn_tx_llc++; + + return (ret); +} + +/* + * Signal handlers + */ + +void +hostapd_sig_relay(int sig) +{ + int oerrno = errno; + + /* + * If priv parent gets a TERM or HUP, pass it through to child + * instead. + */ + + if (child_pid != -1) + kill(child_pid, sig); + + errno = oerrno; +} + +void +hostapd_sig_chld(int sig) +{ + /* + * If parent gets a SIGCHLD, it will exit. + */ + + if (sig == SIGCHLD) { + /* This will terminate libevent's main loop */ + event_gotterm = 1; + } +} + +/* + * privsep I/O functions + */ + +/* Read all data or return 1 for error. */ +int +hostapd_may_read(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res, pos = 0; + + while ((ssize_t)n > pos) { + res = read(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + return (1); + default: + pos += res; + } + } + return (0); +} + +/* + * Read data with the assertion that it all must come through, or + * else abort the process. Based on atomicio() from openssh. + */ +void +hostapd_must_read(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res, pos = 0; + + while ((ssize_t)n > pos) { + res = read(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + _exit(0); + default: + pos += res; + } + } +} + +/* + * Write data with the assertion that it all has to be written, or + * else abort the process. Based on atomicio() from openssh. + */ +void +hostapd_must_write(int fd, void *buf, size_t n) +{ + char *s = buf; + ssize_t res, pos = 0; + + while ((ssize_t)n > pos) { + res = write(fd, s + pos, n - pos); + switch (res) { + case -1: + if (errno == EINTR || errno == EAGAIN) + continue; + case 0: + _exit(0); + default: + pos += res; + } + } +} + |