/*-
 * Copyright (c) 2005 Sam Leffler, Errno Consulting
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer,
 *    without modification.
 * 2. Redistributions in binary form must reproduce at minimum a disclaimer
 *    similar to the "NO WARRANTY" disclaimer below ("Disclaimer") and any
 *    redistribution must be conditioned upon including a substantially
 *    similar Disclaimer requirement for further binary redistribution.
 * 3. Neither the names of the above-listed copyright holders nor the names
 *    of any contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * Alternatively, this software may be distributed under the terms of the
 * GNU General Public License ("GPL") version 2 as published by the Free
 * Software Foundation.
 *
 * NO WARRANTY
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF NONINFRINGEMENT, MERCHANTIBILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY,
 * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGES.
 *
 * $Id: wlanconfig.c 4008 2009-04-22 22:05:57Z proski $
 */

/*
 * wlanconfig athX create wlandev wifiX
 *	wlanmode station | adhoc | ibss | ap | monitor [bssid | -bssid]
 * wlanconfig athX destroy
 */
#include <sys/types.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/socket.h>

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <ctype.h>
#include <getopt.h>
#include <err.h>

#include <include/compat.h>

#include "wireless_copy.h"
#include "net80211/ieee80211.h"
#include "net80211/ieee80211_crypto.h"
#include "net80211/ieee80211_ioctl.h"

/*
 * These are taken from ieee80211_node.h
 */
#define IEEE80211_NODE_TURBOP	0x0001		/* Turbo prime enable */
#define IEEE80211_NODE_COMP	0x0002		/* Compresssion enable */
#define IEEE80211_NODE_FF	0x0004          /* Fast Frame capable */
#define IEEE80211_NODE_XR	0x0008		/* Atheros WME enable */
#define IEEE80211_NODE_AR	0x0010		/* AR capable */
#define IEEE80211_NODE_BOOST	0x0080

#define	streq(a,b)	(strncasecmp(a, b, sizeof(b) - 1) == 0)

#undef ARRAY_SIZE
#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))

static int if_split_name(const char *, char **, unsigned int *);
static void vap_create(struct ifreq *);
static void vap_destroy(const char *);
static void list_stations(const char *);
static void list_scan(const char *);
static void list_channels(const char *, int);
static void list_keys(const char *);
static void list_capabilities(const char *);
static void list_wme(const char *);
static void ieee80211_status(const char *);

static void usage(void);
static int getopmode(const char *);
static int getflag(const char *);
static int get80211param(const char *, int, void *, size_t);
static int get80211priv(const char *, int, void *, size_t);
static const char *getstamode(u_int8_t);

size_t strlcat(char *, const char *, size_t);

static int verbose = 0;

int
main(int argc, char *argv[])
{
	const char *ifname, *cmd;
	unsigned char bnounit = 0;
	char *if_base = NULL;
	unsigned int unit_res = -1;
	int res = 0;

	if (argc < 2 ||
	  strncmp(argv[1], "-h", 2) == 0 || strncmp(argv[1], "--h", 3) == 0)
		usage();

	ifname = argv[1];

	if (argc == 2) {
		ieee80211_status(ifname);
		return 0;
	}

	cmd = argv[2];
	if (streq(cmd, "create")) {
		struct ieee80211_clone_params cp;
		struct ifreq ifr;

		memset(&ifr, 0, sizeof(ifr));

		memset(&cp, 0, sizeof(cp));
		strncpy(cp.icp_name, ifname, IFNAMSIZ);
		/* NB: station mode is the default */
		cp.icp_opmode = IEEE80211_M_STA;
		/* NB: default is to request a unique bssid/mac */
		cp.icp_flags = IEEE80211_CLONE_BSSID;

		while (argc > 3) {
			if (strcmp(argv[3], "wlanmode") == 0) {
				if (argc < 5)
					usage();
				cp.icp_opmode = (u_int16_t) getopmode(argv[4]);
				argc--;
				argv++;
			} else if (strcmp(argv[3], "wlandev") == 0) {
				if (argc < 5)
					usage();
				strncpy(ifr.ifr_name, argv[4], IFNAMSIZ);
				argc--;
				argv++;
			} else if (strcmp(argv[3], "nounit") == 0) {
				bnounit = 1;
			} else {
				int flag = getflag(argv[3]);
				if (flag < 0)
					cp.icp_flags &= ~(-flag);
				else
					cp.icp_flags |= flag;
			}
			argc--;
			argv++;
		}
		if (ifr.ifr_name[0] == '\0')
			errx(1, "no device specified with wlandev");

		res = if_split_name(cp.icp_name, &if_base, &unit_res);
		if (res < 0) {
			err(1, "if_split_name() - malloc");
		} else if ((res == 0) && (bnounit == 0)) {
			/* user gave a string only and using a unit */
			snprintf(cp.icp_name + strlen(if_base),
				IFNAMSIZ - strlen(if_base), "%%d");
		}

		free(if_base);
		if_base = NULL;

		ifr.ifr_data = (void *)&cp;
		vap_create(&ifr);
		printf("%s\n", ifr.ifr_name);
	} else if (streq(cmd, "destroy")) {
		vap_destroy(ifname);
	} else if (streq(cmd, "list")) {
		if (argc > 3) {
			const char *arg = argv[3];

			if (streq(arg, "sta"))
				list_stations(ifname);
			else if (streq(arg, "scan") || streq(arg, "ap"))
				list_scan(ifname);
			else if (streq(arg, "chan") || streq(arg, "freq"))
				list_channels(ifname, 1);
			else if (streq(arg, "active"))
				list_channels(ifname, 0);
			else if (streq(arg, "keys"))
				list_keys(ifname);
			else if (streq(arg, "caps"))
				list_capabilities(ifname);
			else if (streq(arg, "wme"))
				list_wme(ifname);
			else
				err(1, "unknown 'list' option: %s", arg);
		} else				/* NB: for compatibility */
			list_stations(ifname);
	} else
		usage();

	return 0;
}

// if_split_name - takes a name and splits it into the longest non-numeric only string
// 		   including the first character plus the value of the longest numeric 
// 		   string including the last character
// 	returns  : < 0 on error
// 		   0 on finding only a string
// 		   1 on finding a numeric/unit at the end of the string
//
// 		   NOTE Allocates memory.
static int if_split_name(const char *if_name, char **if_base_name, unsigned int *unit) {
	int status = -1;
	const char *p = NULL;
	const char *l = NULL;

	/*
	 * Look for the unit from end to start
	 */
	l = if_name + strlen(if_name) - 1;
	for (p = l; p >= if_name; --p) {
		if (!isdigit(*p))
			break;
	}

	if (p < l) {
		if (unit != NULL)
			*unit = atoi(p + 1);
		status = 1;
	} else
		status = 0;

	/*
	 * Wherever the unit began, one index before it is the ending of the string
	 */
	if (if_base_name != NULL) {
		*if_base_name = malloc(p - if_name + 2);
		if (*if_base_name != NULL) {
			memset(*if_base_name, 0, p - if_name + 2);
			strncpy(*if_base_name, if_name, p - if_name + 1);
		} else
			status = -1;
	}

	return status;
}

static void
vap_create(struct ifreq *ifr)
{
	int s;
	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0)
		err(1, "socket(SOCK_DGRAM)");

	if (ioctl(s, SIOC80211IFCREATE, ifr) < 0)
		err(1, "ioctl");

	close(s);
}

static void
vap_destroy(const char *ifname)
{
	struct ifreq ifr;
	int s;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0)
		err(1, "socket(SOCK_DGRAM)");
	memset(&ifr, 0, sizeof(ifr));
	strncpy(ifr.ifr_name, ifname, IFNAMSIZ);
	if (ioctl(s, SIOC80211IFDESTROY, &ifr) < 0)
		err(1, "ioctl");
	close(s);
}

static void
usage(void)
{
	fprintf(stderr, "usage: wlanconfig athX create [nounit] wlandev wifiY\n");
	fprintf(stderr, "            wlanmode [sta|adhoc|ap|monitor|wds|ahdemo] [uniquebssid]\n");
	fprintf(stderr, "usage: wlanconfig athX destroy\n");
	fprintf(stderr, "usage: wlanconfig athX list [active|ap|caps|chan|freq|keys|scan|sta|wme]\n");
	exit(-1);
}

static int
getopmode(const char *s)
{
	if (streq(s, "sta") || streq(s, "managed"))
		return IEEE80211_M_STA;
	if (streq(s, "ibss") || streq(s, "adhoc") || streq(s, "ad-hoc"))
		return IEEE80211_M_IBSS;
	if (streq(s, "mon"))
		return IEEE80211_M_MONITOR;
	if (streq(s, "ap") || streq(s, "hostap") || streq(s, "master"))
		return IEEE80211_M_HOSTAP;
	if (streq(s, "wds"))
		return IEEE80211_M_WDS;
	if (streq(s, "ahdemo"))
		return IEEE80211_M_AHDEMO;

	errx(1, "unknown operating mode %s", s);
	/*NOTREACHED*/
	return -1;
}

static int
getflag(const char  *s)
{
	const char *cp;
	int flag = 0;

	cp = (s[0] == '-' ? s + 1 : s);
	if (strcmp(cp, "bssid") == 0) {
		printf("WARNING: the -bssid and bssid flags are deprecated.\n");
		flag = IEEE80211_CLONE_BSSID;
	}
	if (strcmp(cp, "uniquebssid") == 0)
		flag = IEEE80211_CLONE_BSSID;
	if (strcmp(cp, "nosbeacon") == 0) {
		printf("WARNING: the nosbeacon flag has been deprecated.\n");
		return 0; /* deprecated but skip */
	}
	if (flag == 0)
		errx(1, "unknown create option %s", s);
	return (s[0] == '-' ? -flag : flag);
}

/*
 * Convert MHz frequency to IEEE channel number.
 */
static u_int
ieee80211_mhz2ieee(u_int freq)
{
	if (freq == 2484)
		return 14;
	if (freq < 2484)
		return (freq - 2407) / 5;
	if (freq < 5000)
		return 15 + ((freq - 2512) / 20);
	return (freq - 5000) / 5;
}

/*
 * Convert RSSI to dBm.
 */
static u_int
rssi2dbm(u_int rssi)
{
	return rssi - 95;
}

static int
getmaxrate(uint8_t rates[15], uint8_t nrates)
{
	int i, maxrate = -1;

	for (i = 0; i < nrates; i++) {
		int rate = rates[i] & IEEE80211_RATE_VAL;
		if (rate > maxrate)
			maxrate = rate;
	}
	return maxrate / 2;
}

static const char *
getcaps(int capinfo)
{
	static char capstring[32];
	char *cp = capstring;

	if (capinfo & IEEE80211_CAPINFO_ESS)
		*cp++ = 'E';
	if (capinfo & IEEE80211_CAPINFO_IBSS)
		*cp++ = 'I';
	if (capinfo & IEEE80211_CAPINFO_CF_POLLABLE)
		*cp++ = 'c';
	if (capinfo & IEEE80211_CAPINFO_CF_POLLREQ)
		*cp++ = 'C';
	if (capinfo & IEEE80211_CAPINFO_PRIVACY)
		*cp++ = 'P';
	if (capinfo & IEEE80211_CAPINFO_SHORT_PREAMBLE)
		*cp++ = 'S';
	if (capinfo & IEEE80211_CAPINFO_PBCC)
		*cp++ = 'B';
	if (capinfo & IEEE80211_CAPINFO_CHNL_AGILITY)
		*cp++ = 'A';
	if (capinfo & IEEE80211_CAPINFO_SHORT_SLOTTIME)
		*cp++ = 's';
	if (capinfo & IEEE80211_CAPINFO_RSN)
		*cp++ = 'R';
	if (capinfo & IEEE80211_CAPINFO_DSSSOFDM)
		*cp++ = 'D';
	*cp = '\0';
	return capstring;
}

static const char *
getathcaps(int capinfo)
{
	static char capstring[32];
	char *cp = capstring;

	if (capinfo & IEEE80211_NODE_TURBOP)
		*cp++ = 'D';
	if (capinfo & IEEE80211_NODE_COMP)
		*cp++ = 'C';
	if (capinfo & IEEE80211_NODE_FF)
		*cp++ = 'F';
	if (capinfo & IEEE80211_NODE_XR)
		*cp++ = 'X';
	if (capinfo & IEEE80211_NODE_AR)
		*cp++ = 'A';
	if (capinfo & IEEE80211_NODE_BOOST)
		*cp++ = 'T';
	*cp = '\0';
	return capstring;
}

static const char *
getstamode(u_int8_t opmode)
{
	if (opmode ==  IEEE80211_STA_OPMODE_XR)
		return "XR";

	return "Normal";
}

static void
printie(const char *tag, const uint8_t *ie, size_t ielen, int maxlen)
{
	printf("%s", tag);
	if (verbose) {
		maxlen -= strlen(tag) + 2;
		if (2 * ielen > maxlen)
			maxlen--;
		printf("<");
		for (; ielen > 0; ie++, ielen--) {
			if (maxlen-- <= 0)
				break;
			printf("%02x", *ie);
		}
		if (ielen != 0)
			printf("-");
		printf(">");
	}
}

/*
 * Copy the ssid string contents into buf, truncating to fit.  If the
 * ssid is entirely printable then just copy intact.  Otherwise convert
 * to hexadecimal.  If the result is truncated then replace the last
 * three characters with "...".
 */
static int
copy_essid(char buf[], size_t bufsize, const u_int8_t *essid, size_t essid_len)
{
	const u_int8_t *p;
	int maxlen;
	int i;

	if (essid_len > bufsize)
		maxlen = bufsize;
	else
		maxlen = essid_len;
	/* determine printable or not */
	for (i = 0, p = essid; i < maxlen; i++, p++) {
		if (*p < ' ' || *p > 0x7e)
			break;
	}
	if (i != maxlen) {		/* not printable, print as hex */
		if (bufsize < 3)
			return 0;
#if 0
		strlcpy(buf, "0x", bufsize);
#else
		strncpy(buf, "0x", bufsize);
#endif
		bufsize -= 2;
		p = essid;
		for (i = 0; i < maxlen && bufsize >= 2; i++) {
			sprintf(&buf[2 + 2 * i], "%02x", *p++);
			bufsize -= 2;
		}
		maxlen = 2 + 2 * i;
	} else {			/* printable, truncate as needed */
		memcpy(buf, essid, maxlen);
	}
	if (maxlen != essid_len)
		memcpy(buf+maxlen - 3, "...", 3);
	return maxlen;
}

/* unaligned little endian access */
#define LE_READ_4(p)					\
	((u_int32_t)					\
	 ((((const u_int8_t *)(p))[0]      ) |		\
	  (((const u_int8_t *)(p))[1] <<  8) |		\
	  (((const u_int8_t *)(p))[2] << 16) |		\
	  (((const u_int8_t *)(p))[3] << 24)))

static __inline int
iswpaoui(const u_int8_t *frm)
{
	return frm[1] > 3 && LE_READ_4(frm + 2) == ((WPA_OUI_TYPE << 24) | WPA_OUI);
}

static __inline int
iswmeoui(const u_int8_t *frm)
{
	return frm[1] > 3 && LE_READ_4(frm + 2) == ((WME_OUI_TYPE << 24) | WME_OUI);
}

static __inline int
isatherosoui(const u_int8_t *frm)
{
	return frm[1] > 3 && LE_READ_4(frm + 2) == ((ATH_OUI_TYPE << 24) | ATH_OUI);
}

static void
printies(const u_int8_t *vp, int ielen, int maxcols)
{
	while (ielen > 0) {
		switch (vp[0]) {
		case IEEE80211_ELEMID_VENDOR:
			if (iswpaoui(vp))
				printie(" WPA", vp, 2 + vp[1], maxcols);
			else if (iswmeoui(vp))
				printie(" WME", vp, 2 + vp[1], maxcols);
			else if (isatherosoui(vp))
				printie(" ATH", vp, 2 + vp[1], maxcols);
			else
				printie(" VEN", vp, 2 + vp[1], maxcols);
			break;
		case IEEE80211_ELEMID_RSN:
			printie(" RSN", vp, 2 + vp[1], maxcols);
			break;
		default:
			printie(" ???", vp, 2 + vp[1], maxcols);
			break;
		}
		ielen -= 2 + vp[1];
		vp += 2 + vp[1];
	}
}

static const char *
ieee80211_ntoa(const uint8_t mac[IEEE80211_ADDR_LEN])
{
	static char a[18];
	int i;

	i = snprintf(a, sizeof(a), "%02x:%02x:%02x:%02x:%02x:%02x",
		mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
	return (i < 17 ? NULL : a);
}

static void
list_stations(const char *ifname)
{
	uint8_t buf[24 * 1024];
	struct iwreq iwr;
	uint8_t *cp;
	int s, len;

	s = socket(AF_INET, SOCK_DGRAM, 0);
	if (s < 0)
		err(1, "socket(SOCK_DGRAM)");

	(void) memset(&iwr, 0, sizeof(iwr));
	(void) strncpy(iwr.ifr_name, ifname, sizeof(iwr.ifr_name));
	iwr.u.data.pointer = (void *)buf;
	iwr.u.data.length = sizeof(buf);
	if (ioctl(s, IEEE80211_IOCTL_STA_INFO, &iwr) < 0)
		errx(1, "unable to get station information");
	len = iwr.u.data.length;
	if (len < sizeof(struct ieee80211req_sta_info))
		return;
	close(s);

	printf("%-17.17s %4s %4s %4s %4s %4s %5s %6s %7s %6s %7s %4s %5s %3s %8s %8s\n",
		"ADDR",
		"AID",
		"CHAN",
		"RATE",
		"RSSI",
		"DBM",
		"IDLE",
		"TXSEQ",
		"TXFRAG",
		"RXSEQ",
		"RXFRAG",
		"CAPS",
		"ACAPS",
		"ERP",
		"STATE",
		"MODE");
	cp = buf;
	do {
		struct ieee80211req_sta_info *si;
		uint8_t *vp;

		si = (struct ieee80211req_sta_info *)cp;
		vp = (u_int8_t *)(si+1);
		printf("%s %4u %4d %3dM %4d %4d %5d %6d %7d %6d %7d %-4.4s %-5.5s %3x %8x %8s",
			ieee80211_ntoa(si->isi_macaddr),
			IEEE80211_AID(si->isi_associd),
			ieee80211_mhz2ieee(si->isi_freq),
			(si->isi_rates[si->isi_txrate] & IEEE80211_RATE_VAL) / 2,
			si->isi_rssi,
			rssi2dbm(si->isi_rssi),
			si->isi_inact,
			(si->isi_txseqs[0] & IEEE80211_SEQ_SEQ_MASK)
				>> IEEE80211_SEQ_SEQ_SHIFT,
			si->isi_txseqs[0] & IEEE80211_SEQ_FRAG_MASK,
			(si->isi_rxseqs[0] & IEEE80211_SEQ_SEQ_MASK)
				>> IEEE80211_SEQ_SEQ_SHIFT,
			si->isi_rxseqs[0] & IEEE80211_SEQ_FRAG_MASK,
		        getcaps(si->isi_capinfo),
		        getathcaps(si->isi_athflags),
			si->isi_erp,
			si->isi_state,
			getstamode(si->isi_opmode));
		printies(vp, si->isi_ie_len, 24);
		printf("\n");
		if (si->isi_uapsd) {
			printf("                   UAPSD QoSInfo: 0x%02x, ",
				si->isi_uapsd);
			printf("(VO,VI,BE,BK) = (%d,%d,%d,%d), MaxSpLimit = %s\n",
				   WME_UAPSD_AC_ENABLED(WME_AC_VO, si->isi_uapsd) ? 1 : 0,
				   WME_UAPSD_AC_ENABLED(WME_AC_VI, si->isi_uapsd) ? 1 : 0,
				   WME_UAPSD_AC_ENABLED(WME_AC_BE, si->isi_uapsd) ? 1 : 0,
				   WME_UAPSD_AC_ENABLED(WME_AC_BK, si->isi_uapsd) ? 1 : 0,
				   WME_UAPSD_MAXSP(si->isi_uapsd) == 1 ? "2" :
				   WME_UAPSD_MAXSP(si->isi_uapsd) == 2 ? "4" :
				   WME_UAPSD_MAXSP(si->isi_uapsd) == 3 ? "6" : "NoLimit");
		}
		cp += si->isi_len;
		len -= si->isi_len;
	} while (len >= sizeof(struct ieee80211req_sta_info));
}

/* unaligned little endian access */
#define LE_READ_4(p)					\
	((u_int32_t)					\
	 ((((const u_int8_t *)(p))[0]      ) |		\
	  (((const u_int8_t *)(p))[1] <<  8) |		\
	  (((const u_int8_t *)(p))[2] << 16) |		\
	  (((const u_int8_t *)(p))[3] << 24)))

static void
list_scan(const char *ifname)
{
	uint8_t buf[24 * 1024];
	char ssid[14];
	uint8_t *cp;
	int len;

	len = get80211priv(ifname, IEEE80211_IOCTL_SCAN_RESULTS,
		buf, sizeof(buf));
	if (len == -1)
		errx(1, "unable to get scan results");
	if (len < sizeof(struct ieee80211req_scan_result))
		return;

	printf("%-14.14s  %-17.17s  %4s %4s  %-5s %3s %4s\n",
		"SSID",
		"BSSID",
		"CHAN",
		"RATE",
		"S:N",
		"INT",
		"CAPS");
	cp = buf;
	do {
		struct ieee80211req_scan_result *sr;
		uint8_t *vp;

		sr = (struct ieee80211req_scan_result *)cp;
		vp = (u_int8_t *)(sr + 1);
		printf("%-14.*s  %s  %3d  %3dM %2d:%-2d  %3d %-4.4s",
			copy_essid(ssid, sizeof(ssid), vp, sr->isr_ssid_len),
			ssid,
			ieee80211_ntoa(sr->isr_bssid),
			ieee80211_mhz2ieee(sr->isr_freq),
			getmaxrate(sr->isr_rates, sr->isr_nrates),
			(int8_t) sr->isr_rssi, sr->isr_noise,
			sr->isr_intval,
			getcaps(sr->isr_capinfo));
		printies(vp + sr->isr_ssid_len, sr->isr_ie_len, 24);
		printf("\n");
		cp += sr->isr_len, len -= sr->isr_len;
	} while (len >= sizeof(struct ieee80211req_scan_result));
}

static void
print_chaninfo(const struct ieee80211_channel *c)
{
	char buf[14];

	buf[0] = '\0';
	if (IEEE80211_IS_CHAN_FHSS(c))
		strlcat(buf, " FHSS", sizeof(buf));
	if (IEEE80211_IS_CHAN_A(c))
		strlcat(buf, " 11a", sizeof(buf));
	/* XXX 11g schizophrenia */
	if (IEEE80211_IS_CHAN_G(c) ||
	    IEEE80211_IS_CHAN_PUREG(c))
		strlcat(buf, " 11g", sizeof(buf));
	else if (IEEE80211_IS_CHAN_B(c))
		strlcat(buf, " 11b", sizeof(buf));
	if (IEEE80211_IS_CHAN_STURBO(c))
		strlcat(buf, " Static", sizeof(buf));
	if (IEEE80211_IS_CHAN_DTURBO(c))
		strlcat(buf, " Dynamic", sizeof(buf));
	if (IEEE80211_IS_CHAN_HALF(c))
		strlcat(buf, " Half", sizeof(buf));
	if (IEEE80211_IS_CHAN_QUARTER(c))
		strlcat(buf, " Quarter", sizeof(buf));
	printf("Channel %3u : %u%c%c Mhz%-14.14s",
		c->ic_ieee, c->ic_freq,
		IEEE80211_IS_CHAN_PASSIVE(c) ? '*' : ' ', 
	        IEEE80211_IS_CHAN_RADAR(c) ? '!' : ' ', 
	       buf);
}

static void
list_channels(const char *ifname, int allchans)
{
	struct ieee80211req_chaninfo chans;
	struct ieee80211req_chaninfo achans;
	const struct ieee80211_channel *c;
	int i, half;

	if (get80211priv(ifname, IEEE80211_IOCTL_GETCHANINFO, &chans, sizeof(chans)) < 0)
		errx(1, "unable to get channel information");
	if (!allchans) {
		uint8_t active[32];

		if (get80211priv(ifname, IEEE80211_IOCTL_GETCHANLIST, &active, sizeof(active)) < 0)
			errx(1, "unable to get active channel list");
		memset(&achans, 0, sizeof(achans));
		for (i = 0; i < chans.ic_nchans; i++) {
			c = &chans.ic_chans[i];
			if (isset(active, ieee80211_mhz2ieee(c->ic_freq)) || allchans)
				achans.ic_chans[achans.ic_nchans++] = *c;
		}
	} else
		achans = chans;
	half = achans.ic_nchans / 2;
	if (achans.ic_nchans % 2)
		half++;
	for (i = 0; i < achans.ic_nchans / 2; i++) {
		print_chaninfo(&achans.ic_chans[i]);
		print_chaninfo(&achans.ic_chans[half + i]);
		printf("\n");
	}
	if (achans.ic_nchans % 2) {
		print_chaninfo(&achans.ic_chans[i]);
		printf("\n");
	}
}

static void
list_keys(const char *ifname)
{
	char cmd[256];
	puts("[list_keys not implemented (yet). Spawning iwlist...]");
	strcpy(cmd, "iwlist ");
	strcat(cmd, ifname);
	strcat(cmd, " key");
	system(cmd);
}

#define	IEEE80211_C_BITS \
"\020\1WEP\2TKIP\3AES\4AES_CCM\6CKIP\7FF\10TURBOP\11IBSS\12PMGT\13HOSTAP\14AHDEMO" \
"\15SWRETRY\16TXPMGT\17SHSLOT\20SHPREAMBLE\21MONITOR\22TKIPMIC\30WPA1" \
"\31WPA2\32BURST\33WME"

/*
 * Print a value a la the %b format of the kernel's printf
 */
static void
printb(const char *s, unsigned v, const char *bits)
{
	int i, any = 0;
	char c;

	if (bits && *bits == 8)
		printf("%s=%o", s, v);
	else
		printf("%s=%x", s, v);
	bits++;
	if (bits) {
		putchar('<');
		while ((i = *bits++) != '\0') {
			if (v & (1 << (i-1))) {
				if (any)
					putchar(',');
				any = 1;
				for (; (c = *bits) > 32; bits++)
					putchar(c);
			} else
				for (; *bits > 32; bits++);
		}
		putchar('>');
	}
}

static void
list_capabilities(const char *ifname)
{
	u_int32_t caps;

	if (get80211param(ifname, IEEE80211_PARAM_DRIVER_CAPS, &caps,
	    sizeof(caps)) < 0)
		errx(1, "unable to get driver capabilities");
	printb(ifname, caps, IEEE80211_C_BITS);
	putchar('\n');
}

static void
list_wme(const char *ifname)
{
#define	GETPARAM() \
	(get80211priv(ifname, IEEE80211_IOCTL_GETWMMPARAMS, param, sizeof(param)) != -1)
	static const char *acnames[] = { "AC_BE", "AC_BK", "AC_VI", "AC_VO" };
	int param[3];
	int ac;

	param[2] = 0;		/* channel params */
	for (ac = WME_AC_BE; ac <= WME_AC_VO; ac++) {
again:
		if (param[2] != 0)
			printf("\t%s", "     ");
		else
			printf("\t%s", acnames[ac]);

		param[1] = ac;

		/* show WME BSS parameters */
		param[0] = IEEE80211_WMMPARAMS_CWMIN;
		if (GETPARAM())
			printf(" cwmin %2u", param[0]);
		param[0] = IEEE80211_WMMPARAMS_CWMAX;
		if (GETPARAM())
			printf(" cwmax %2u", param[0]);
		param[0] = IEEE80211_WMMPARAMS_AIFS;
		if (GETPARAM())
			printf(" aifs %2u", param[0]);
		param[0] = IEEE80211_WMMPARAMS_TXOPLIMIT;
		if (GETPARAM())
			printf(" txopLimit %3u", param[0]);
		param[0] = IEEE80211_WMMPARAMS_ACM;
		if (GETPARAM()) {
			if (param[0])
				printf(" acm");
			else if (verbose)
				printf(" -acm");
		}
		/* !BSS only */
		if (param[2] == 0) {
			param[0] = IEEE80211_WMMPARAMS_NOACKPOLICY;
			if (GETPARAM()) {
				if (param[0])
					printf(" -ack");
				else if (verbose)
					printf(" ack");
			}
		}
		printf("\n");
		if (param[2] == 0) {
			param[2] = 1;		/* bss params */
			goto again;
		} else
			param[2] = 0;
	}
}

static void
ieee80211_status(const char *ifname)
{
	/* XXX fill in */
	char cmd[256];
	puts("[status not implemented (yet). Spawning iwconfig...]");
	strcpy(cmd, "iwconfig ");
	strcat(cmd, ifname);
	system(cmd);
}

static int
getsocket(void)
{
	static int s = -1;

	if (s < 0) {
		s = socket(AF_INET, SOCK_DGRAM, 0);
		if (s < 0)
			err(1, "socket(SOCK_DGRAM)");
	}
	return s;
}

static int
get80211param(const char *ifname, int param, void *data, size_t len)
{
	struct iwreq iwr;

	memset(&iwr, 0, sizeof(iwr));
	strncpy(iwr.ifr_name, ifname, IFNAMSIZ);
	iwr.u.mode = param;

	if (ioctl(getsocket(), IEEE80211_IOCTL_GETPARAM, &iwr) < 0) {
		perror("ioctl[IEEE80211_IOCTL_GETPARAM]");
		return -1;
	}
	if (len < IFNAMSIZ) {
		/*
		 * Argument data fits inline; put it there.
		 */
		memcpy(data, iwr.u.name, len);
	}
	return iwr.u.data.length;
}

#define IOCTL_ERR(x) [x - SIOCIWFIRSTPRIV] = "ioctl[" #x "]"
static int
do80211priv(struct iwreq *iwr, const char *ifname, int op, void *data, size_t len)
{
	memset(iwr, 0, sizeof(struct iwreq));
	strncpy(iwr->ifr_name, ifname, IFNAMSIZ);
	if (len < IFNAMSIZ) {
		/*
		 * Argument data fits inline; put it there.
		 */
		memcpy(iwr->u.name, data, len);
	} else {
		/*
		 * Argument data too big for inline transfer; setup a
		 * parameter block instead; the kernel will transfer
		 * the data for the driver.
		 */
		iwr->u.data.pointer = data;
		iwr->u.data.length = len;
	}

	if (ioctl(getsocket(), op, iwr) < 0) {
		static const char *opnames[] = {
			IOCTL_ERR(IEEE80211_IOCTL_SETPARAM),
			IOCTL_ERR(IEEE80211_IOCTL_GETPARAM),
			IOCTL_ERR(IEEE80211_IOCTL_SETMODE),
			IOCTL_ERR(IEEE80211_IOCTL_GETMODE),
			IOCTL_ERR(IEEE80211_IOCTL_SETWMMPARAMS),
			IOCTL_ERR(IEEE80211_IOCTL_GETWMMPARAMS),
			IOCTL_ERR(IEEE80211_IOCTL_SETCHANLIST),
			IOCTL_ERR(IEEE80211_IOCTL_GETCHANLIST),
			IOCTL_ERR(IEEE80211_IOCTL_CHANSWITCH),
			IOCTL_ERR(IEEE80211_IOCTL_GETCHANINFO),
			IOCTL_ERR(IEEE80211_IOCTL_SETOPTIE),
			IOCTL_ERR(IEEE80211_IOCTL_GETOPTIE),
			IOCTL_ERR(IEEE80211_IOCTL_SETMLME),
			IOCTL_ERR(IEEE80211_IOCTL_RADAR),
			IOCTL_ERR(IEEE80211_IOCTL_SETKEY),
			IOCTL_ERR(IEEE80211_IOCTL_DELKEY),
			IOCTL_ERR(IEEE80211_IOCTL_HALMAP),
			IOCTL_ERR(IEEE80211_IOCTL_ADDMAC),
			IOCTL_ERR(IEEE80211_IOCTL_DELMAC),
			IOCTL_ERR(IEEE80211_IOCTL_WDSADDMAC),
			IOCTL_ERR(IEEE80211_IOCTL_WDSDELMAC),
		};
		op -= SIOCIWFIRSTPRIV;
		if (0 <= op && op < ARRAY_SIZE(opnames))
			perror(opnames[op]);
		else
			perror("ioctl[unknown???]");
		return -1;
	}
	return 0;
}

static int
get80211priv(const char *ifname, int op, void *data, size_t len)
{
	struct iwreq iwr;

	if (do80211priv(&iwr, ifname, op, data, len) < 0)
		return -1;
	if (len < IFNAMSIZ)
		memcpy(data, iwr.u.name, len);
	return iwr.u.data.length;
}

/*
 * Appends src to string dst of size siz (unlike strncat, siz is the
 * full size of dst, not space left).  At most siz-1 characters
 * will be copied.  Always NUL terminates (unless siz <= strlen(dst)).
 * Returns strlen(src) + MIN(siz, strlen(initial dst)).
 * If retval >= siz, truncation occurred.
 */
size_t
strlcat(char *dst, const char *src, size_t siz)
{
	char *d = dst;
	const char *s = src;
	size_t n = siz;
	size_t dlen;

	/* Find the end of dst and adjust bytes left but don't go past end */
	while (n-- != 0 && *d != '\0')
		d++;
	dlen = d - dst;
	n = siz - dlen;

	if (n == 0)
		return(dlen + strlen(s));
	while (*s != '\0') {
		if (n != 1) {
			*d++ = *s;
			n--;
		}
		s++;
	}
	*d = '\0';

	return(dlen + (s - src));	/* count does not include NUL */
}

