root/usr.sbin/hostapd/hostapd.c
/*      $OpenBSD: hostapd.c,v 1.42 2023/03/08 04:43:13 guenther Exp $   */

/*
 * Copyright (c) 2004, 2005 Reyk Floeter <reyk@openbsd.org>
 * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
 *
 * 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/ioctl.h>
#include <sys/types.h>
#include <sys/signal.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/queue.h>
#include <sys/stat.h>

#include <net/if.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 <limits.h>
#include <err.h>

#include "hostapd.h"
#include "iapp.h"

void     hostapd_usage(void);
void     hostapd_udp_init(struct hostapd_config *);
void     hostapd_sig_handler(int, short, void *);
static __inline int
         hostapd_entry_cmp(struct hostapd_entry *, struct hostapd_entry *);

struct hostapd_config hostapd_cfg;

extern char *__progname;
char printbuf[BUFSIZ];

void
hostapd_usage(void)
{
        fprintf(stderr, "usage: %s [-dv] [-D macro=value] [-f file]\n",
            __progname);
        exit(EXIT_FAILURE);
}

void
hostapd_log(u_int level, const char *fmt, ...)
{
        char *nfmt = NULL;
        va_list ap;

        if (level > hostapd_cfg.c_verbose)
                return;

        va_start(ap, fmt);
        if (hostapd_cfg.c_debug) {
                if (asprintf(&nfmt, "%s\n", fmt) != -1)
                        vfprintf(stderr, nfmt, ap);
                else {
                        vfprintf(stderr, fmt, ap);
                        fprintf(stderr, "\n");
                }
                fflush(stderr);
        } else
                vsyslog(LOG_INFO, fmt, ap);
        va_end(ap);

        free(nfmt);
}

void
hostapd_printf(const char *fmt, ...)
{
        char newfmt[BUFSIZ];
        va_list ap;
        size_t n;

        if (fmt == NULL)
                goto flush;

        va_start(ap, fmt);
        bzero(newfmt, sizeof(newfmt));
        if ((n = strlcpy(newfmt, printbuf, sizeof(newfmt))) >= sizeof(newfmt))
                goto va_flush;
        if (strlcpy(newfmt + n, fmt, sizeof(newfmt) - n) >= sizeof(newfmt) - n)
                goto va_flush;
        if (vsnprintf(printbuf, sizeof(printbuf), newfmt, ap) < 0)
                goto va_flush;
        va_end(ap);

        return;

 va_flush:
        va_end(ap);
 flush:
        if (strlen(printbuf))
                hostapd_log(HOSTAPD_LOG, "%s", printbuf);
        bzero(printbuf, sizeof(printbuf));
}

void
hostapd_fatal(const char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        if (hostapd_cfg.c_debug) {
                vfprintf(stderr, fmt, ap);
                fflush(stderr);
        } else
                vsyslog(LOG_ERR, fmt, ap);
        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", fname);
                return (-1);
        }

        if (st.st_uid != 0 && st.st_uid != getuid()) {
                hostapd_log(HOSTAPD_LOG,
                    "%s: owner not root or current user", fname);
                return (-1);
        }

        if (st.st_mode & (S_IRWXG | S_IRWXO)) {
                hostapd_log(HOSTAPD_LOG,
                    "%s: group/world readable/writeable", fname);
                return (-1);
        }

        return (0);
}

int
hostapd_bpf_open(u_int flags)
{
        int fd = -1;
        struct bpf_version bpv;

        if ((fd = open("/dev/bpf", flags)) == -1) {
                hostapd_fatal("unable to open BPF device: %s\n",
                    strerror(errno));
        }

        /*
         * 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 hostapd_iapp *iapp = &cfg->c_iapp;
        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 ((iapp->i_udp = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
                hostapd_fatal("unable to open udp socket\n");

        cfg->c_flags |= HOSTAPD_CFG_F_UDP;

        (void)strlcpy(ifr.ifr_name, iapp->i_iface, sizeof(ifr.ifr_name));

        if (ioctl(iapp->i_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;
        iapp->i_addr.sin_family = AF_INET;
        iapp->i_addr.sin_addr.s_addr = addr->sin_addr.s_addr;
        if (iapp->i_addr.sin_port == 0)
                iapp->i_addr.sin_port = htons(IAPP_PORT);

        if (ioctl(iapp->i_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;
        iapp->i_broadcast.sin_family = AF_INET;
        iapp->i_broadcast.sin_addr.s_addr = addr->sin_addr.s_addr;
        iapp->i_broadcast.sin_port = iapp->i_addr.sin_port;

        baddr.sin_family = AF_INET;
        baddr.sin_addr.s_addr = htonl(INADDR_ANY);
        baddr.sin_port = iapp->i_addr.sin_port;

        if (bind(iapp->i_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
         * sent via multicast to the default group 224.0.1.178.
         * Nevertheless, some implementations still use broadcasts
         * for IAPP messages.
         */
        if (cfg->c_flags & HOSTAPD_CFG_F_BRDCAST) {
                /*
                 * Enable broadcast
                 */
                if (setsockopt(iapp->i_udp, SOL_SOCKET, SO_BROADCAST,
                    &brd, sizeof(brd)) == -1)
                        hostapd_fatal("failed to enable broadcast on socket\n");

                hostapd_log(HOSTAPD_LOG_DEBUG, "%s: using broadcast mode "
                    "(address %s)", iapp->i_iface, inet_ntoa(addr->sin_addr));
        } else {
                /*
                 * Enable multicast
                 */
                bzero(&mreq, sizeof(mreq));

                iapp->i_multicast.sin_family = AF_INET;
                if (iapp->i_multicast.sin_addr.s_addr == INADDR_ANY)
                        iapp->i_multicast.sin_addr.s_addr =
                            inet_addr(IAPP_MCASTADDR);
                iapp->i_multicast.sin_port = iapp->i_addr.sin_port;

                mreq.imr_multiaddr.s_addr =
                    iapp->i_multicast.sin_addr.s_addr;
                mreq.imr_interface.s_addr =
                    iapp->i_addr.sin_addr.s_addr;

                if (setsockopt(iapp->i_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));

                if (setsockopt(iapp->i_udp, IPPROTO_IP, IP_MULTICAST_TTL,
                    &iapp->i_ttl, sizeof(iapp->i_ttl)) == -1)
                        hostapd_fatal("failed to set multicast ttl to "
                            "%u: %s\n", iapp->i_ttl, strerror(errno));

                hostapd_log(HOSTAPD_LOG_DEBUG, "%s: using multicast mode "
                    "(ttl %u, group %s)", iapp->i_iface, iapp->i_ttl,
                    inet_ntoa(iapp->i_multicast.sin_addr));
        }
}

void
hostapd_sig_handler(int sig, short event, void *arg)
{
        switch (sig) {
        case SIGALRM:
        case SIGTERM:
        case SIGQUIT:
        case SIGINT:
                (void)event_loopexit(NULL);
        }
}

void
hostapd_cleanup(struct hostapd_config *cfg)
{
        struct hostapd_iapp *iapp = &cfg->c_iapp;
        struct ip_mreq mreq;
        struct hostapd_apme *apme;
        struct hostapd_table *table;
        struct hostapd_entry *entry;

        /* Release all Host APs */
        if (cfg->c_flags & HOSTAPD_CFG_F_APME) {
                while ((apme = TAILQ_FIRST(&cfg->c_apmes)) != NULL)
                        hostapd_apme_term(apme);
        }

        if (cfg->c_flags & HOSTAPD_CFG_F_PRIV &&
            (cfg->c_flags & HOSTAPD_CFG_F_BRDCAST) == 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 =
                    iapp->i_addr.sin_addr.s_addr;

                if (setsockopt(iapp->i_udp, IPPROTO_IP,
                    IP_DROP_MEMBERSHIP, &mreq, sizeof(mreq)) == -1)
                        hostapd_log(HOSTAPD_LOG, "failed to remove multicast"
                            " membership to %s: %s",
                            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);
        }

        /* Cleanup tables */
        while ((table = TAILQ_FIRST(&cfg->c_tables)) != NULL) {
                while ((entry = RB_MIN(hostapd_tree, &table->t_tree)) != NULL) {
                        RB_REMOVE(hostapd_tree, &table->t_tree, entry);
                        free(entry);
                }
                while ((entry = TAILQ_FIRST(&table->t_mask_head)) != NULL) {
                        TAILQ_REMOVE(&table->t_mask_head, entry, e_entries);
                        free(entry);
                }
                TAILQ_REMOVE(&cfg->c_tables, table, t_entries);
                free(table);
        }

        hostapd_log(HOSTAPD_LOG_VERBOSE, "bye!");
}

int
main(int argc, char *argv[])
{
        struct event ev_sigalrm;
        struct event ev_sigterm;
        struct event ev_sigquit;
        struct event ev_sigint;
        struct hostapd_config *cfg = &hostapd_cfg;
        struct hostapd_iapp *iapp;
        struct hostapd_apme *apme;
        char *config = NULL;
        u_int debug = 0, ret;
        int ch;

        /* Set startup logging */
        cfg->c_debug = 1;

        /*
         * Get and parse command line options
         */
        while ((ch = getopt(argc, argv, "f:D:dv")) != -1) {
                switch (ch) {
                case 'f':
                        config = optarg;
                        break;
                case 'D':
                        if (hostapd_parse_symset(optarg) < 0)
                                hostapd_fatal("could not parse macro "
                                    "definition %s\n", optarg);
                        break;
                case 'd':
                        debug++;
                        break;
                case 'v':
                        cfg->c_verbose++;
                        break;
                default:
                        hostapd_usage();
                }
        }

        argc -= optind;
        argv += optind;
        if (argc > 0)
                hostapd_usage();

        if (config == NULL)
                ret = strlcpy(cfg->c_config, HOSTAPD_CONFIG, sizeof(cfg->c_config));
        else
                ret = strlcpy(cfg->c_config, config, sizeof(cfg->c_config));
        if (ret >= sizeof(cfg->c_config))
                hostapd_fatal("invalid configuration file\n");

        if (geteuid())
                hostapd_fatal("need root privileges\n");

        /* Parse the configuration file */
        if (hostapd_parse_file(cfg) != 0)
                hostapd_fatal("invalid configuration in %s\n", cfg->c_config);

        iapp = &cfg->c_iapp;

        if ((cfg->c_flags & HOSTAPD_CFG_F_IAPP) == 0)
                hostapd_fatal("IAPP interface not specified\n");

        if (cfg->c_apme_dlt == 0)
                cfg->c_apme_dlt = HOSTAPD_DLT;

        /*
         * Setup the hostapd handlers
         */
        hostapd_udp_init(cfg);
        hostapd_llc_init(cfg);

        /*
         * Set runtime logging and detach as daemon
         */
        if ((cfg->c_debug = debug) == 0) {
                openlog(__progname, LOG_PID | LOG_NDELAY, LOG_DAEMON);
                tzset();
                if (daemon(0, 0) == -1)
                        hostapd_fatal("failed to daemonize\n");
        }

        if (cfg->c_flags & HOSTAPD_CFG_F_APME) {
                TAILQ_FOREACH(apme, &cfg->c_apmes, a_entries)
                        hostapd_apme_init(apme);
        } else
                hostapd_log(HOSTAPD_LOG, "%s: running without a Host AP",
                    iapp->i_iface);

        /* Drop all privileges in an unprivileged child process */
        hostapd_priv_init(cfg);

        if (cfg->c_flags & HOSTAPD_CFG_F_APME)
                setproctitle("IAPP: %s, Host AP", iapp->i_iface);
        else
                setproctitle("IAPP: %s", iapp->i_iface);

        /*
         * Unprivileged child process
         */

        (void)event_init();

        /*
         * Set signal handlers
         */
        signal_set(&ev_sigalrm, SIGALRM, hostapd_sig_handler, NULL);
        signal_set(&ev_sigterm, SIGTERM, hostapd_sig_handler, NULL);
        signal_set(&ev_sigquit, SIGQUIT, hostapd_sig_handler, NULL);
        signal_set(&ev_sigint, SIGINT, hostapd_sig_handler, NULL);
        signal_add(&ev_sigalrm, NULL);
        signal_add(&ev_sigterm, NULL);
        signal_add(&ev_sigquit, NULL);
        signal_add(&ev_sigint, NULL);
        signal(SIGHUP, SIG_IGN);
        signal(SIGCHLD, SIG_IGN);

        /* Initialize the IAPP protocol handler */
        hostapd_iapp_init(cfg);

        /*
         * Schedule the Host AP listener
         */
        if (cfg->c_flags & HOSTAPD_CFG_F_APME) {
                TAILQ_FOREACH(apme, &cfg->c_apmes, a_entries) {
                        event_set(&apme->a_ev, apme->a_raw,
                            EV_READ | EV_PERSIST, hostapd_apme_input, apme);
                        if (event_add(&apme->a_ev, NULL) == -1)
                                hostapd_fatal("failed to add APME event");
                }
        }

        /*
         * Schedule the IAPP listener
         */
        event_set(&iapp->i_udp_ev, iapp->i_udp, EV_READ | EV_PERSIST,
            hostapd_iapp_input, cfg);
        if (event_add(&iapp->i_udp_ev, NULL) == -1)
                hostapd_fatal("failed to add IAPP event");

        hostapd_log(HOSTAPD_LOG, "starting hostapd with pid %u",
            getpid());

        /* Run event loop */
        if (event_dispatch() == -1)
                hostapd_fatal("failed to dispatch hostapd");

        /* Executed after the event loop has been terminated */
        hostapd_cleanup(cfg);
        return (EXIT_SUCCESS);
}

void
hostapd_randval(u_int8_t *buf, const u_int len)
{
        u_int32_t data = 0;
        u_int i;

        for (i = 0; i < len; i++) {
                if ((i % sizeof(data)) == 0)
                        data = arc4random();
                buf[i] = data & 0xff;
                data >>= 8;
        }
}

struct hostapd_table *
hostapd_table_add(struct hostapd_config *cfg, const char *name)
{
        struct hostapd_table *table;

        if (hostapd_table_lookup(cfg, name) != NULL)
                return (NULL);
        if ((table = (struct hostapd_table *)
            calloc(1, sizeof(struct hostapd_table))) == NULL)
                return (NULL);
        if (strlcpy(table->t_name, name, sizeof(table->t_name)) >=
            sizeof(table->t_name)) {
                free(table);
                return (NULL);
        }
        RB_INIT(&table->t_tree);
        TAILQ_INIT(&table->t_mask_head);
        TAILQ_INSERT_TAIL(&cfg->c_tables, table, t_entries);

        return (table);
}

struct hostapd_table *
hostapd_table_lookup(struct hostapd_config *cfg, const char *name)
{
        struct hostapd_table *table;

        TAILQ_FOREACH(table, &cfg->c_tables, t_entries) {
                if (strcmp(name, table->t_name) == 0)
                        return (table);
        }

        return (NULL);
}

struct hostapd_entry *
hostapd_entry_add(struct hostapd_table *table, u_int8_t *lladdr)
{
        struct hostapd_entry *entry;

        if (hostapd_entry_lookup(table, lladdr) != NULL)
                return (NULL);

        if ((entry = (struct hostapd_entry *)
            calloc(1, sizeof(struct hostapd_entry))) == NULL)
                return (NULL);

        bcopy(lladdr, entry->e_lladdr, IEEE80211_ADDR_LEN);
        RB_INSERT(hostapd_tree, &table->t_tree, entry);

        return (entry);
}

struct hostapd_entry *
hostapd_entry_lookup(struct hostapd_table *table, u_int8_t *lladdr)
{
        struct hostapd_entry *entry, key;

        bcopy(lladdr, key.e_lladdr, IEEE80211_ADDR_LEN);
        if ((entry = RB_FIND(hostapd_tree, &table->t_tree, &key)) != NULL)
                return (entry);

        /* Masked entries can't be handled by the red-black tree */
        TAILQ_FOREACH(entry, &table->t_mask_head, e_entries) {
                if (HOSTAPD_ENTRY_MASK_MATCH(entry, lladdr))
                        return (entry);
        }

        return (NULL);
}

void
hostapd_entry_update(struct hostapd_table *table, struct hostapd_entry *entry)
{
        RB_REMOVE(hostapd_tree, &table->t_tree, entry);

        /* Apply mask to entry */
        if (entry->e_flags & HOSTAPD_ENTRY_F_MASK) {
                HOSTAPD_ENTRY_MASK_ADD(entry->e_lladdr, entry->e_mask);
                TAILQ_INSERT_TAIL(&table->t_mask_head, entry, e_entries);
        } else {
                RB_INSERT(hostapd_tree, &table->t_tree, entry);
        }
}

static __inline int
hostapd_entry_cmp(struct hostapd_entry *a, struct hostapd_entry *b)
{
        return (memcmp(a->e_lladdr, b->e_lladdr, IEEE80211_ADDR_LEN));
}

RB_GENERATE(hostapd_tree, hostapd_entry, e_nodes, hostapd_entry_cmp);