/*
 * Driver interaction with OpenBSD net80211 layer
 * Copyright (c) 2013, Mark Kettenis
 *
 * This software may be distributed under the terms of the BSD license.
 * See README for more details.
 */

#include "includes.h"
#include <sys/ioctl.h>

#include <net/if.h>
#include <net80211/ieee80211.h>
#include <net80211/ieee80211_crypto.h>
#include <net80211/ieee80211_ioctl.h>

#include "common.h"
#include "driver.h"

struct openbsd_driver_data {
	char ifname[IFNAMSIZ + 1];
	void *ctx;

	int sock;			/* open socket for 802.11 ioctls */
};


static int
wpa_driver_openbsd_get_ssid(void *priv, u8 *ssid)
{
	struct openbsd_driver_data *drv = priv;
	struct ieee80211_nwid nwid;
	struct ifreq ifr;

	os_memset(&ifr, 0, sizeof(ifr));
	os_strlcpy(ifr.ifr_name, drv->ifname, sizeof(ifr.ifr_name));
	ifr.ifr_data = (void *)&nwid;
	if (ioctl(drv->sock, SIOCG80211NWID, &ifr) < 0 ||
	    nwid.i_len > IEEE80211_NWID_LEN)
		return -1;

	os_memcpy(ssid, nwid.i_nwid, nwid.i_len);
	return nwid.i_len;
}

static int
wpa_driver_openbsd_get_bssid(void *priv, u8 *bssid)
{
	struct openbsd_driver_data *drv = priv;
	struct ieee80211_bssid id;

	os_strlcpy(id.i_name, drv->ifname, sizeof(id.i_name));
	if (ioctl(drv->sock, SIOCG80211BSSID, &id) < 0)
		return -1;

	os_memcpy(bssid, id.i_bssid, IEEE80211_ADDR_LEN);
	return 0;
}


static int
wpa_driver_openbsd_get_capa(void *priv, struct wpa_driver_capa *capa)
{
	os_memset(capa, 0, sizeof(*capa));
	capa->flags = WPA_DRIVER_FLAGS_4WAY_HANDSHAKE_PSK |
		      WPA_DRIVER_FLAGS_4WAY_HANDSHAKE_8021X;
	return 0;
}


static int
wpa_driver_openbsd_set_key(const char *ifname, void *priv, enum wpa_alg alg,
	    const unsigned char *addr, int key_idx, int set_tx, const u8 *seq,
	    size_t seq_len, const u8 *key, size_t key_len)
{
	struct openbsd_driver_data *drv = priv;
	struct ieee80211_keyavail keyavail;

	if (alg != WPA_ALG_PMK || key_len > IEEE80211_PMK_LEN)
		return -1;

	memset(&keyavail, 0, sizeof(keyavail));
	os_strlcpy(keyavail.i_name, drv->ifname, sizeof(keyavail.i_name));
	if (wpa_driver_openbsd_get_bssid(priv, keyavail.i_macaddr) < 0)
		return -1;
	memcpy(keyavail.i_key, key, key_len);

	if (ioctl(drv->sock, SIOCS80211KEYAVAIL, &keyavail) < 0)
		return -1;

	return 0;
}

static void *
wpa_driver_openbsd_init(void *ctx, const char *ifname)
{
	struct openbsd_driver_data *drv;

	drv = os_zalloc(sizeof(*drv));
	if (drv == NULL)
		return NULL;

	drv->sock = socket(PF_INET, SOCK_DGRAM, 0);
	if (drv->sock < 0)
		goto fail;

	drv->ctx = ctx;
	os_strlcpy(drv->ifname, ifname, sizeof(drv->ifname));

	return drv;

fail:
	os_free(drv);
	return NULL;
}


static void
wpa_driver_openbsd_deinit(void *priv)
{
	struct openbsd_driver_data *drv = priv;

	close(drv->sock);
	os_free(drv);
}


const struct wpa_driver_ops wpa_driver_openbsd_ops = {
	.name = "openbsd",
	.desc = "OpenBSD 802.11 support",
	.get_ssid = wpa_driver_openbsd_get_ssid,
	.get_bssid = wpa_driver_openbsd_get_bssid,
	.get_capa = wpa_driver_openbsd_get_capa,
	.set_key = wpa_driver_openbsd_set_key,
	.init = wpa_driver_openbsd_init,
	.deinit = wpa_driver_openbsd_deinit,
};
