/*
 * hostapd / IEEE 802.1X-2004 Authenticator
 * Copyright (c) 2002-2012, Jouni Malinen <j@w1.fi>
 *
 * This software may be distributed under the terms of the BSD license.
 * See README for more details.
 */

#include "utils/includes.h"

#include "utils/common.h"
#include "utils/eloop.h"
#include "crypto/crypto.h"
#include "common/ieee802_11_defs.h"
#include "hostapd.h"
#include "ap/sta_info.h"
#include "ap/wpa_auth.h"
#include "eap_server/eap.h"
#include "ap/ap_config.h"
#include "eap_common/eap_wsc_common.h"
#include "ap/ieee802_1x.h"
#include "utils/wpa_debug.h"
#include "eapol_auth/eapol_auth_sm.h"
#include "eapol_auth/eapol_auth_sm_i.h"
#include "eap_server/eap.h"
#include "sta_info.h"
#include "ieee802_1x.h"

int hostapd_send_eapol(const u8 *source, const u8 *sta_addr,
		       const u8 *data, size_t data_len);

static void ieee802_1x_finished(struct hostapd_data *hapd,
                                struct sta_info *sta, int success,
                                int remediation);

static void ieee802_1x_send(struct hostapd_data *hapd, struct sta_info *sta,
			    u8 type, const u8 *data, size_t datalen)
{
	u8 *buf;
	struct ieee802_1x_hdr *xhdr;
	size_t len;

	len = sizeof(*xhdr) + datalen;
	buf = os_zalloc(len);
	if (!buf) {
		wpa_printf(MSG_ERROR, "malloc() failed for %s(len=%lu)",
			   __func__, (unsigned long) len);
		return;
	}

	xhdr = (struct ieee802_1x_hdr *) buf;
	xhdr->version = EAPOL_VERSION;
	xhdr->type = type;
	xhdr->length = host_to_be16(datalen);

	if (datalen > 0 && data != NULL)
		os_memcpy(xhdr + 1, data, datalen);

	hostapd_send_eapol(hapd->own_addr, sta->addr, buf, len);
	os_free(buf);
}



static void handle_eap_response(struct hostapd_data *hapd,
				struct sta_info *sta, struct eap_hdr *eap,
				size_t len)
{
	u8 type, *data;
	struct eapol_state_machine *sm = sta->eapol_sm;

	if (!sm)
		return;

	data = (u8 *) (eap + 1);

	if (len < sizeof(*eap) + 1) {
		wpa_printf(MSG_INFO, "%s: too short response data", __func__);
		return;
	}

	sm->eap_type_supp = type = data[0];

	sm->dot1xAuthEapolRespFramesRx++;

	wpabuf_free(sm->eap_if->eapRespData);
	sm->eap_if->eapRespData = wpabuf_alloc_copy(eap, len);
	sm->eapolEap = true;
}

/* Process incoming EAP packet from Supplicant */
static void handle_eap(struct hostapd_data *hapd, struct sta_info *sta,
		       u8 *buf, size_t len)
{
	struct eap_hdr *eap;
	u16 eap_len;

	if (len < sizeof(*eap)) {
		wpa_printf(MSG_INFO, "   too short EAP packet");
		return;
	}

	eap = (struct eap_hdr *) buf;

	eap_len = be_to_host16(eap->length);
	wpa_printf(MSG_DEBUG, "EAP: code=%d identifier=%d length=%d",
		   eap->code, eap->identifier,
		   eap_len);
	if (eap_len < sizeof(*eap)) {
		wpa_printf(MSG_DEBUG, "   Invalid EAP length");
		return;
	} else if (eap_len > len) {
		wpa_printf(MSG_DEBUG,
			   "   Too short frame to contain this EAP packet");
		return;
	} else if (eap_len < len) {
		wpa_printf(MSG_DEBUG,
			   "   Ignoring %lu extra bytes after EAP packet",
			   (unsigned long) len - eap_len);
	}

	switch (eap->code) {
	case EAP_CODE_RESPONSE:
		handle_eap_response(hapd, sta, eap, eap_len);
		break;
	case EAP_CODE_INITIATE:
		break;
	}
}

struct eapol_state_machine *
ieee802_1x_alloc_eapol_sm(struct hostapd_data *hapd, struct sta_info *sta)
{
	int flags = 0;

	if (sta->wpa_sm) {
		flags |= EAPOL_SM_USES_WPA;
	}
	return eapol_auth_alloc(hapd->eapol_auth, sta->addr, flags,
			sta->wps_ie, NULL, sta,
			sta->identity, NULL);
}


/**
 * ieee802_1x_receive - Process the EAPOL frames from the Supplicant
 * @hapd: hostapd BSS data
 * @sa: Source address (sender of the EAPOL frame)
 * @buf: EAPOL frame
 * @len: Length of buf in octets
 *
 * This function is called for each incoming EAPOL frame from the interface
 */
void ieee802_1x_receive(struct hostapd_data *hapd, const u8 *sa, const u8 *buf,
			size_t len)
{
	struct sta_info *sta;
	struct ieee802_1x_hdr *hdr;
	struct ieee802_1x_eapol_key *key;
	u16 datalen;

	wpa_printf(MSG_DEBUG, "IEEE 802.1X: %lu bytes from " MACSTR,
		   (unsigned long) len, MAC2STR(sa));
	sta = ap_get_sta(hapd, sa);
	if (!sta) {
		wpa_printf(MSG_DEBUG,
			   "IEEE 802.1X data frame from not associated/Pre-authenticating STA");

		return;
	}

	if (len < sizeof(*hdr)) {
		wpa_printf(MSG_INFO, "   too short IEEE 802.1X packet");
		return;
	}

	hdr = (struct ieee802_1x_hdr *) buf;
	datalen = be_to_host16(hdr->length);
	wpa_printf(MSG_DEBUG, "   IEEE 802.1X: version=%d type=%d length=%d",
		   hdr->version, hdr->type, datalen);

	if (len - sizeof(*hdr) < datalen) {
		wpa_printf(MSG_INFO,
			   "   frame too short for this IEEE 802.1X packet");
		if (sta->eapol_sm)
			sta->eapol_sm->dot1xAuthEapLengthErrorFramesRx++;
		return;
	}
	if (len - sizeof(*hdr) > datalen) {
		wpa_printf(MSG_DEBUG,
			   "   ignoring %lu extra octets after IEEE 802.1X packet",
			   (unsigned long) len - sizeof(*hdr) - datalen);
	}

	if (sta->eapol_sm) {
		sta->eapol_sm->dot1xAuthLastEapolFrameVersion = hdr->version;
		sta->eapol_sm->dot1xAuthEapolFramesRx++;
	}

	key = (struct ieee802_1x_eapol_key *) (hdr + 1);
	if (datalen >= sizeof(struct ieee802_1x_eapol_key) &&
	    hdr->type == IEEE802_1X_TYPE_EAPOL_KEY &&
	    (key->type == EAPOL_KEY_TYPE_WPA ||
	     key->type == EAPOL_KEY_TYPE_RSN)) {
		wpa_receive(hapd->wpa_auth, sta->wpa_sm, (u8 *) hdr,
			    sizeof(*hdr) + datalen);
		return;
	}

	if (!sta->eapol_sm) {
		sta->eapol_sm = ieee802_1x_alloc_eapol_sm(hapd, sta);
		if (!sta->eapol_sm)
			return;

#ifdef CONFIG_WPS
		if (!hapd->conf->ieee802_1x && hapd->conf->wps_state) {
			u32 wflags = sta->flags & (WLAN_STA_WPS |
						   WLAN_STA_WPS2 |
						   WLAN_STA_MAYBE_WPS);
			if (wflags == WLAN_STA_MAYBE_WPS ||
			    wflags == (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS)) {
				/*
				 * Delay EAPOL frame transmission until a
				 * possible WPS STA initiates the handshake
				 * with EAPOL-Start. Only allow the wait to be
				 * skipped if the STA is known to support WPS
				 * 2.0.
				 */
				wpa_printf(MSG_DEBUG,
					   "WPS: Do not start EAPOL until EAPOL-Start is received");
				sta->eapol_sm->flags |= EAPOL_SM_WAIT_START;
			}
		}
#endif /* CONFIG_WPS */

		sta->eapol_sm->eap_if->portEnabled = true;
	}

	/* since we support version 1, we can ignore version field and proceed
	 * as specified in version 1 standard [IEEE Std 802.1X-2001, 7.5.5] */
	/* TODO: actually, we are not version 1 anymore.. However, Version 2
	 * does not change frame contents, so should be ok to process frames
	 * more or less identically. Some changes might be needed for
	 * verification of fields. */

	switch (hdr->type) {
	case IEEE802_1X_TYPE_EAP_PACKET:
		handle_eap(hapd, sta, (u8 *) (hdr + 1), datalen);
		break;

	case IEEE802_1X_TYPE_EAPOL_START:
		sta->eapol_sm->flags &= ~EAPOL_SM_WAIT_START;
		sta->eapol_sm->eapolStart = true;
		sta->eapol_sm->dot1xAuthEapolStartFramesRx++;
		eap_server_clear_identity(sta->eapol_sm->eap);
		wpa_auth_sm_event(sta->wpa_sm, WPA_REAUTH_EAPOL);
		break;


	case IEEE802_1X_TYPE_EAPOL_LOGOFF:
		break;

	case IEEE802_1X_TYPE_EAPOL_KEY:
		wpa_printf(MSG_DEBUG, "   EAPOL-Key");
		break;

	case IEEE802_1X_TYPE_EAPOL_ENCAPSULATED_ASF_ALERT:
		wpa_printf(MSG_DEBUG, "   EAPOL-Encapsulated-ASF-Alert");
		/* TODO: implement support for this; show data */
		break;

	default:
		wpa_printf(MSG_DEBUG, "   unknown IEEE 802.1X packet type");
		sta->eapol_sm->dot1xAuthInvalidEapolFramesRx++;
		break;
	}

	eapol_auth_step(sta->eapol_sm);
}


void ieee802_1x_free_station(struct hostapd_data *hapd, struct sta_info *sta)
{
	struct eapol_state_machine *sm = sta->eapol_sm;

	if (!sm)
		return;

	sta->eapol_sm = NULL;
	eapol_auth_free(sm);
}


static void ieee802_1x_eapol_send(void *ctx, void *sta_ctx, u8 type,
				  const u8 *data, size_t datalen)
{
#ifdef CONFIG_WPS
	struct sta_info *sta = sta_ctx;

	if ((sta->flags & (WLAN_STA_WPS | WLAN_STA_MAYBE_WPS)) ==
	    WLAN_STA_MAYBE_WPS) {
		const u8 *identity;
		size_t identity_len;
		struct eapol_state_machine *sm = sta->eapol_sm;

		identity = eap_get_identity(sm->eap, &identity_len);
		if (identity &&
		    ((identity_len == WSC_ID_ENROLLEE_LEN &&
		      os_memcmp(identity, WSC_ID_ENROLLEE,
				WSC_ID_ENROLLEE_LEN) == 0) ||
		     (identity_len == WSC_ID_REGISTRAR_LEN &&
		      os_memcmp(identity, WSC_ID_REGISTRAR,
				WSC_ID_REGISTRAR_LEN) == 0))) {
			wpa_printf(MSG_DEBUG,
				   "WPS: WLAN_STA_MAYBE_WPS -> WLAN_STA_WPS");
			sta->flags |= WLAN_STA_WPS;
		}
	}
#endif /* CONFIG_WPS */

	ieee802_1x_send(ctx, sta_ctx, type, data, datalen);
}


static void ieee802_1x_aaa_send(void *ctx, void *sta_ctx,
				const u8 *data, size_t datalen)
{
#ifndef CONFIG_NO_RADIUS
	struct hostapd_data *hapd = ctx;
	struct sta_info *sta = sta_ctx;

	ieee802_1x_encapsulate_radius(hapd, sta, data, datalen);
#endif /* CONFIG_NO_RADIUS */
}


static void _ieee802_1x_finished(void *ctx, void *sta_ctx, int success,
				 int preauth, int remediation)
{
	struct hostapd_data *hapd = ctx;
	struct sta_info *sta = sta_ctx;
	ieee802_1x_finished(hapd, sta, success, remediation);
}


static int ieee802_1x_get_eap_user(void *ctx, const u8 *identity,
				   size_t identity_len, int phase2,
				   struct eap_user *user)
{
	struct hostapd_data *hapd = ctx;
	const struct hostapd_eap_user *eap_user;
	int i;
	int rv = -1;

	eap_user = hostapd_get_eap_user(hapd, identity, identity_len, phase2);
	if (!eap_user)
		goto out;

	os_memset(user, 0, sizeof(*user));
	user->phase2 = phase2;
	for (i = 0; i < EAP_MAX_METHODS; i++) {
		user->methods[i].vendor = eap_user->methods[i].vendor;
		user->methods[i].method = eap_user->methods[i].method;
	}

	if (eap_user->password) {
		user->password = os_memdup(eap_user->password,
					   eap_user->password_len);
		if (!user->password)
			goto out;
		user->password_len = eap_user->password_len;
		user->password_hash = eap_user->password_hash;
	}
	user->force_version = eap_user->force_version;
	user->ttls_auth = eap_user->ttls_auth;
	rv = 0;

out:
	if (rv)
		wpa_printf(MSG_DEBUG, "%s: Failed to find user", __func__);

	return rv;
}


static int ieee802_1x_sta_entry_alive(void *ctx, const u8 *addr)
{
	struct hostapd_data *hapd = ctx;
	struct sta_info *sta;

	sta = ap_get_sta(hapd, addr);
	if (!sta || !sta->eapol_sm)
		return 0;
	return 1;
}


static void ieee802_1x_set_port_authorized(void *ctx, void *sta_ctx,
					   int authorized)
{
}


static void _ieee802_1x_abort_auth(void *ctx, void *sta_ctx)
{
}


static void ieee802_1x_eapol_event(void *ctx, void *sta_ctx,
				   enum eapol_event type)
{
#if 0
	/* struct hostapd_data *hapd = ctx; */
	struct sta_info *sta = sta_ctx;

	switch (type) {
	case EAPOL_AUTH_SM_CHANGE:
		wpa_auth_sm_notify(sta->wpa_sm);
		break;
	case EAPOL_AUTH_REAUTHENTICATE:
		wpa_auth_sm_event(sta->wpa_sm, WPA_REAUTH_EAPOL);
		break;
	}
#endif
}


int ieee802_1x_init(struct hostapd_data *hapd)
{
	struct eapol_auth_config conf;
	struct eapol_auth_cb cb;
	struct eap_config *eap_cfg;

	os_memset(&conf, 0, sizeof(conf));
	eap_cfg = os_zalloc(sizeof(struct eap_config));
	eap_cfg->max_auth_rounds = 100;
	eap_cfg->max_auth_rounds_short = 50;
	//eap_cfg->backend_auth = 1;
	eap_cfg->eap_server = 1;
	conf.eap_cfg = eap_cfg;
	conf.ctx = hapd;
	conf.wpa = hapd->conf->wpa;

	os_memset(&cb, 0, sizeof(cb));
	cb.eapol_send = ieee802_1x_eapol_send;
	cb.aaa_send = ieee802_1x_aaa_send;
	cb.finished = _ieee802_1x_finished;
	cb.get_eap_user = ieee802_1x_get_eap_user;
	cb.sta_entry_alive = ieee802_1x_sta_entry_alive;
	cb.set_port_authorized = ieee802_1x_set_port_authorized;
	cb.abort_auth = _ieee802_1x_abort_auth;
	cb.eapol_event = ieee802_1x_eapol_event;

	hapd->eapol_auth = eapol_auth_init(&conf, &cb);
	if (!hapd->eapol_auth)
		return -1;

	return 0;
}


static void ieee802_1x_finished(struct hostapd_data *hapd,
				struct sta_info *sta, int success,
				int remediation)
{
	if (!success) {
		/*
		 * Many devices require deauthentication after WPS provisioning
		 * and some may not be be able to do that themselves, so
		 * disconnect the client here. In addition, this may also
		 * benefit IEEE 802.1X/EAPOL authentication cases, too since
		 * the EAPOL PAE state machine would remain in HELD state for
		 * considerable amount of time and some EAP methods, like
		 * EAP-FAST with anonymous provisioning, may require another
		 * EAPOL authentication to be started to complete connection.
		 */
		ap_sta_delayed_1x_auth_fail_disconnect(hapd, sta);
	}
}
