/*
 * eap.c    rfc2284 & rfc2869 implementation
 *
 * Version:     $Id: f0452b43f4f887e9872363cfa6df53c25359b207 $
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 * Copyright 2000-2003,2006  The FreeRADIUS server project
 * Copyright 2001  hereUare Communications, Inc. <raghud@hereuare.com>
 * Copyright 2003  Alan DeKok <aland@freeradius.org>
 */
/*
 *  EAP PACKET FORMAT
 *  --- ------ ------
 *  0		   1		   2		   3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |     Code      |  Identifier   |	    Length	     |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |    Data ...
 * +-+-+-+-+
 *
 *
 * EAP Request and Response Packet Format
 * --- ------- --- -------- ------ ------
 *  0		   1		   2		   3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |     Code      |  Identifier   |	    Length	     |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |     Type      |  Type-Data ...
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-
 *
 *
 * EAP Success and Failure Packet Format
 * --- ------- --- ------- ------ ------
 *  0		   1		   2		   3
 *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 * |     Code      |  Identifier   |	    Length	     |
 * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 *
 */

#include <freeradius-devel/modpriv.h>

RCSID("$Id: f0452b43f4f887e9872363cfa6df53c25359b207 $")

#include "rlm_eap.h"
#include <ctype.h>

static char const *eap_codes[] = {
	 "",				/* 0 is invalid */
	"Request",
	"Response",
	"Success",
	"Failure"
};

static int _eap_module_free(eap_module_t *inst)
{
	/*
	 *	We have to check inst->type as it's only allocated
	 *	if we loaded the eap method.
	 */
	if (inst->type && inst->type->detach) (inst->type->detach)(inst->instance);

#ifndef NDEBUG
	/*
	 *	Don't dlclose() modules if we're doing memory
	 *	debugging.  This removes the symbols needed by
	 *	valgrind.
	 */
	if (!main_config.debug_memory)
#endif
	if (inst->handle) dlclose(inst->handle);

	return 0;
}

/** Load required EAP sub-modules (methods)
 *
 */
int eap_module_instantiate(rlm_eap_t *inst, eap_module_t **m_inst, eap_type_t num, CONF_SECTION *cs)
{
	eap_module_t *method;
	char *mod_name, *p;

	/* Make room for the EAP-Type */
	*m_inst = method = talloc_zero(cs, eap_module_t);
	if (!inst) return -1;

	talloc_set_destructor(method, _eap_module_free);

	/* fill in the structure */
	method->cs = cs;
	method->name = eap_type2name(num);

	/*
	 *	The name of the module were trying to load
	 */
	mod_name = talloc_typed_asprintf(method, "rlm_eap_%s", method->name);

	/*
	 *	dlopen is case sensitive
	 */
	p = mod_name;
	while (*p) {
		*p = tolower(*p);
		p++;
	}

	/*
	 *	Link the loaded EAP-Type
	 */
	method->handle = fr_dlopenext(mod_name);
	if (!method->handle) {
		ERROR("rlm_eap (%s): Failed to link %s: %s", inst->xlat_name, mod_name, fr_strerror());

		return -1;
	}

	method->type = dlsym(method->handle, mod_name);
	if (!method->type) {
		ERROR("rlm_eap (%s): Failed linking to structure in %s: %s", inst->xlat_name,
		       method->name, dlerror());

		return -1;
	}

	cf_log_module(cs, "Linked to sub-module %s", mod_name);

	/*
	 *	Call the attach num in the EAP num module
	 */
	if ((method->type->instantiate) && ((method->type->instantiate)(method->cs, &(method->instance)) < 0)) {
		ERROR("rlm_eap (%s): Failed to initialise %s", inst->xlat_name, mod_name);

		if (method->instance) {
			(void) talloc_steal(method, method->instance);
		}

		return -1;
	}

	if (method->instance) {
		(void) talloc_steal(method, method->instance);
	}

	return 0;
}

/*
 * Call the appropriate handle with the right eap_method.
 */
static int eap_module_call(eap_module_t *module, eap_handler_t *handler)
{
	int rcode = 1;
	REQUEST *request = handler->request;

	char const *caller = request->module;

	rad_assert(module != NULL);

	RDEBUG2("Calling submodule %s to process data", module->type->name);

	request->module = module->type->name;

	switch (handler->stage) {
	case INITIATE:
		if (!module->type->session_init(module->instance, handler)) {
			rcode = 0;
		}

		break;

	case PROCESS:
		/*
		 *   The called function updates the EAP reply packet.
		 */
		if (!module->type->process ||
		    !module->type->process(module->instance, handler)) {
			rcode = 0;
		}

		break;

	default:
		/* Should never enter here */
		RDEBUG("Internal sanity check failed on EAP");
		rcode = 0;
		break;
	}

	request->module = caller;
	return rcode;
}

/** Process NAK data from EAP peer
 *
 */
static eap_type_t eap_process_nak(rlm_eap_t *inst, REQUEST *request,
				    eap_type_t type,
				    eap_type_data_t *nak)
{
	unsigned int i;
	VALUE_PAIR *vp;
	eap_type_t method = PW_EAP_INVALID;

	/*
	 *	The NAK data is the preferred EAP type(s) of
	 *	the client.
	 *
	 *	RFC 3748 says to list one or more proposed
	 *	alternative types, one per octet, or to use
	 *	0 for no alternative.
	 */
	if (!nak->data) {
		REDEBUG("Peer sent empty (invalid) NAK. "
			"Can't select method to continue with");

		return PW_EAP_INVALID;
	}

	/*
	 *	Pick one type out of the one they asked for,
	 *	as they may have asked for many.
	 */
	vp = fr_pair_find_by_num(request->config, PW_EAP_TYPE, 0, TAG_ANY);
	for (i = 0; i < nak->length; i++) {
		/*
		 *	Type 0 is valid, and means there are no
		 *	common choices.
		 */
		if (nak->data[i] == 0) {
			RDEBUG("Peer NAK'd indicating it is not willing to "
			       "continue ");

			return PW_EAP_INVALID;
		}

		/*
		 *	It is invalid to request identity,
		 *	notification & nak in nak.
		 */
		if (nak->data[i] < PW_EAP_MD5) {
			REDEBUG("Peer NAK'd asking for bad "
				"type %s (%d)",
				eap_type2name(nak->data[i]),
				nak->data[i]);

			return PW_EAP_INVALID;
		}

		if ((nak->data[i] >= PW_EAP_MAX_TYPES) ||
		    !inst->methods[nak->data[i]]) {
			RDEBUG2("Peer NAK'd asking for "
				"unsupported EAP type %s (%d), skipping...",
				eap_type2name(nak->data[i]),
				nak->data[i]);

			continue;
		}

		/*
		 *	Prevent a firestorm if the client is confused.
		 */
		if (type == nak->data[i]) {
			RDEBUG2("Peer NAK'd our request for "
				"%s (%d) with a request for "
				"%s (%d), skipping...",
				eap_type2name(nak->data[i]),
				nak->data[i],
				eap_type2name(nak->data[i]),
				nak->data[i]);

			RWARN("!!! We requested to use an EAP type as normal.");
			RWARN("!!! The supplicant rejected that, and requested to use the same EAP type.");
			RWARN("!!!     i.e. the supplicant said 'I don't like X, please use X instead.");
			RWARN("!!! The supplicant software is broken and does not work properly.");
			RWARN("!!! Please upgrade it to software that works.");

			continue;
		}

		/*
		 *	Enforce per-user configuration of EAP
		 *	types.
		 */
		if (vp && (vp->vp_integer != nak->data[i])) {
			RDEBUG2("Peer wants %s (%d), while we "
				"require %s (%d), skipping",
				eap_type2name(nak->data[i]),
				nak->data[i],
				eap_type2name(vp->vp_integer),
				vp->vp_integer);

			continue;
		}

		RDEBUG("Found mutually acceptable type %s (%d)",
		       eap_type2name(nak->data[i]), nak->data[i]);

		method = nak->data[i];

		break;
	}

	if (method == PW_EAP_INVALID) {
		REDEBUG("No mutually acceptable types found");
	}

	return method;
}

/** Select the correct callback based on a response
 *
 * Based on the EAP response from the supplicant, call the appropriate
 * method callback.
 *
 * Default to the configured EAP-Type for all Unsupported EAP-Types.
 *
 * @param inst Configuration data for this instance of rlm_eap.
 * @param handler State data that persists over multiple rounds of EAP.
 * @return a status code.
 */
eap_rcode_t eap_method_select(rlm_eap_t *inst, eap_handler_t *handler)
{
	eap_type_data_t		*type = &handler->eap_ds->response->type;
	REQUEST			*request = handler->request;

	eap_type_t		next = inst->default_method;
	VALUE_PAIR		*vp;

	/*
	 *	Don't trust anyone.
	 */
	if ((type->num == 0) || (type->num >= PW_EAP_MAX_TYPES)) {
		REDEBUG("Peer sent EAP method number %d, which is outside known range", type->num);

		return EAP_INVALID;
	}

	/*
	 *	Multiple levels of TLS nesting are invalid.  But if
	 *	the parent has a home_server defined, then this
	 *	request is being processed through a virtual
	 *	server... so that's OK.
	 *
	 *	i.e. we're inside an EAP tunnel, which means we have a
	 *	parent.  If the outer session exists, and doesn't have
	 *	a home server, then it's multiple layers of tunneling.
	 */
	if (handler->request->parent &&
	    handler->request->parent->parent &&
	    !handler->request->parent->parent->home_server) {
		RERROR("Multiple levels of TLS nesting are invalid");

		return EAP_INVALID;
	}

	RDEBUG2("Peer sent packet with method EAP %s (%d)", eap_type2name(type->num), type->num);
	/*
	 *	Figure out what to do.
	 */
	switch (type->num) {
	case PW_EAP_IDENTITY:
		/*
		 *	Allow per-user configuration of EAP types.
		 */
		vp = fr_pair_find_by_num(handler->request->config, PW_EAP_TYPE, 0,
			      TAG_ANY);
		if (vp) next = vp->vp_integer;

		/*
		 *	Ensure it's valid.
		 */
		if ((next < PW_EAP_MD5) ||
		    (next >= PW_EAP_MAX_TYPES) ||
		    (!inst->methods[next])) {
			REDEBUG2("Tried to start unsupported EAP type %s (%d)",
				 eap_type2name(next), next);

			return EAP_INVALID;
		}

	do_initiate:
		/*
		 *	If any of these fail, we messed badly somewhere
		 */
		rad_assert(next >= PW_EAP_MD5);
		rad_assert(next < PW_EAP_MAX_TYPES);
		rad_assert(inst->methods[next]);

		handler->stage = INITIATE;
		handler->type = next;

		if (eap_module_call(inst->methods[next], handler) == 0) {
			REDEBUG2("Failed starting EAP %s (%d) session.  EAP sub-module failed",
				 eap_type2name(next), next);

			return EAP_INVALID;
		}
		break;

	case PW_EAP_NAK:
		/*
		 *	Delete old data, if necessary.
		 */
		if (handler->opaque && handler->free_opaque) {
			handler->free_opaque(handler->opaque);
			handler->free_opaque = NULL;
			handler->opaque = NULL;
		}

		/*
		 *	We got a NAK after the peer started doing a
		 *	particular EAP type.  That's rude, tell the
		 *	peer to go away.
		 */
		if (handler->started) return EAP_INVALID;

		next = eap_process_nak(inst, handler->request,
				       handler->type, type);

		/*
		 *	We probably want to return 'fail' here...
		 */
		if (!next) {
			return EAP_INVALID;
		}

		goto do_initiate;

		/*
		 *	Key off of the configured sub-modules.
		 */
	default:
		/*
		 *	We haven't configured it, it doesn't exist.
		 */
		if (!inst->methods[type->num]) {
			REDEBUG2("Client asked for unsupported EAP type %s (%d)",
				 eap_type2name(type->num),
				 type->num);

			return EAP_INVALID;
		}

		rad_assert(handler->stage == PROCESS);
		handler->type = type->num;
		if (eap_module_call(inst->methods[type->num],
				    handler) == 0) {
			REDEBUG2("Failed continuing EAP %s (%d) session.  EAP sub-module failed",
				 eap_type2name(type->num),
				 type->num);

			return EAP_INVALID;
		}
		handler->started = true;
		break;
	}

	return EAP_OK;
}


/*
 *	compose EAP reply packet in EAP-Message attr of RADIUS.
 *
 *	Set the RADIUS reply codes based on EAP request codes.  Append
 *	any additonal VPs to RADIUS reply
 */
rlm_rcode_t eap_compose(eap_handler_t *handler)
{
	VALUE_PAIR *vp;
	eap_packet_raw_t *eap_packet;
	REQUEST *request;
	EAP_DS *eap_ds;
	eap_packet_t *reply;
	int rcode;

#ifndef NDEBUG
	handler = talloc_get_type_abort(handler, eap_handler_t);
	request = talloc_get_type_abort(handler->request, REQUEST);
	eap_ds = talloc_get_type_abort(handler->eap_ds, EAP_DS);
	reply = talloc_get_type_abort(eap_ds->request, eap_packet_t);
#else
	request = handler->request;
	eap_ds = handler->eap_ds;
	reply = eap_ds->request;
#endif

	/*
	 *	The Id for the EAP packet to the NAS wasn't set.
	 *	Do so now.
	 */
	if (!eap_ds->set_request_id) {
		/*
		 *	Id serves to suppport request/response
		 *	retransmission in the EAP layer and as such
		 *	must be different for 'adjacent' packets
		 *	except in case of success/failure-replies.
		 *
		 *	RFC2716 (EAP-TLS) requires this to be
		 *	incremented, RFC2284 only makes the above-
		 *	mentioned restriction.
		 */
		reply->id = handler->eap_ds->response->id;

		switch (reply->code) {
			/*
			 *	The Id is a simple "ack" for success
			 *	and failure.
			 *
			 *	RFC 3748 section 4.2 says
			 *
			 *	... The Identifier field MUST match
			 *	the Identifier field of the Response
			 *	packet that it is sent in response
			 *	to.
			 */
		case PW_EAP_SUCCESS:
		case PW_EAP_FAILURE:
			break;

			/*
			 *	We've sent a response to their
			 *	request, the Id is incremented.
			 */
		default:
			++reply->id;
		}
	}

	/*
	 *	For Request & Response packets, set the EAP sub-type,
	 *	if the EAP sub-module didn't already set it.
	 *
	 *	This allows the TLS module to be "morphic", and means
	 *	that the TTLS and PEAP modules can call it to do most
	 *	of their dirty work.
	 */
	if (((eap_ds->request->code == PW_EAP_REQUEST) ||
	     (eap_ds->request->code == PW_EAP_RESPONSE)) &&
	    (eap_ds->request->type.num == 0)) {
		rad_assert(handler->type >= PW_EAP_MD5);
		rad_assert(handler->type < PW_EAP_MAX_TYPES);

		eap_ds->request->type.num = handler->type;
	}

	if (eap_wireformat(reply) == EAP_INVALID) {
		return RLM_MODULE_INVALID;
	}
	eap_packet = (eap_packet_raw_t *)reply->packet;

	vp = radius_pair_create(request->reply, &request->reply->vps, PW_EAP_MESSAGE, 0);
	if (!vp) return RLM_MODULE_INVALID;

	vp->vp_length = eap_packet->length[0] * 256 + eap_packet->length[1];
	vp->vp_octets = talloc_steal(vp, reply->packet);
	reply->packet = NULL;

	/*
	 *	EAP-Message is always associated with
	 *	Message-Authenticator but not vice-versa.
	 *
	 *	Don't add a Message-Authenticator if it's already
	 *	there.
	 */
	vp = fr_pair_find_by_num(request->reply->vps, PW_MESSAGE_AUTHENTICATOR, 0, TAG_ANY);
	if (!vp) {
		vp = fr_pair_afrom_num(request->reply, PW_MESSAGE_AUTHENTICATOR, 0);
		vp->vp_length = AUTH_VECTOR_LEN;
		vp->vp_octets = talloc_zero_array(vp, uint8_t, vp->vp_length);
		fr_pair_add(&(request->reply->vps), vp);
	}

	/* Set request reply code, but only if it's not already set. */
	rcode = RLM_MODULE_OK;
	if (!request->reply->code) switch (reply->code) {
	case PW_EAP_RESPONSE:
		request->reply->code = PW_CODE_ACCESS_REJECT;
		rcode = RLM_MODULE_REJECT;
		break;
	case PW_EAP_SUCCESS:
		request->reply->code = PW_CODE_ACCESS_ACCEPT;
		rcode = RLM_MODULE_OK;
		break;
	case PW_EAP_FAILURE:
		request->reply->code = PW_CODE_ACCESS_REJECT;
		rcode = RLM_MODULE_REJECT;
		break;
	case PW_EAP_REQUEST:
		request->reply->code = PW_CODE_ACCESS_CHALLENGE;
		rcode = RLM_MODULE_HANDLED;
		break;
	default:
		/*
		 *	When we're pulling MS-CHAPv2 out of EAP-MS-CHAPv2,
		 *	we do so WITHOUT setting a reply code, as the
		 *	request is being proxied.
		 */
		if (request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
			return RLM_MODULE_HANDLED;
		}

		/* Should never enter here */
		REDEBUG("Reply code %d is unknown, rejecting the request", reply->code);
		request->reply->code = PW_CODE_ACCESS_REJECT;
		reply->code = PW_EAP_FAILURE;
		rcode = RLM_MODULE_REJECT;
		break;
	}

	RDEBUG2("Sending EAP %s (code %i) ID %d length %i",
		eap_codes[eap_packet->code], eap_packet->code, reply->id,
		eap_packet->length[0] * 256 + eap_packet->length[1]);

	return rcode;
}

/*
 * Radius criteria, EAP-Message is invalid without Message-Authenticator
 * For EAP_START, send Access-Challenge with EAP Identity request.
 */
int eap_start(rlm_eap_t *inst, REQUEST *request)
{
	VALUE_PAIR *vp, *proxy;
	VALUE_PAIR *eap_msg;

	eap_msg = fr_pair_find_by_num(request->packet->vps, PW_EAP_MESSAGE, 0, TAG_ANY);
	if (!eap_msg) {
		RDEBUG2("No EAP-Message, not doing EAP");
		return EAP_NOOP;
	}

	/*
	 *	Look for EAP-Type = None (FreeRADIUS specific attribute)
	 *	this allows you to NOT do EAP for some users.
	 */
	vp = fr_pair_find_by_num(request->packet->vps, PW_EAP_TYPE, 0, TAG_ANY);
	if (vp && vp->vp_integer == 0) {
		RDEBUG2("Found EAP-Message, but EAP-Type = None, so we're not doing EAP");
		return EAP_NOOP;
	}

	/*
	 *	http://www.freeradius.org/rfc/rfc2869.html#EAP-Message
	 *
	 *	Checks for Message-Authenticator are handled by rad_recv().
	 */

	/*
	 *	Check for a Proxy-To-Realm.  Don't get excited over LOCAL
	 *	realms (sigh).
	 */
	proxy = fr_pair_find_by_num(request->config, PW_PROXY_TO_REALM, 0, TAG_ANY);
	if (proxy) {
		REALM *realm;

		/*
		 *	If it's a LOCAL realm, then we're not proxying
		 *	to it.
		 */
		realm = realm_find(proxy->vp_strvalue);
		if (!realm || (realm && (!realm->auth_pool))) {
			proxy = NULL;
		}
	}

	/*
	 *	Check the length before de-referencing the contents.
	 *
	 *	Lengths of zero are required by the RFC for EAP-Start,
	 *	but we've never seen them in practice.
	 *
	 *	Lengths of two are what we see in practice as
	 *	EAP-Starts.
	 */
	if ((eap_msg->vp_length == 0) || (eap_msg->vp_length == 2)) {
		uint8_t *p;

		/*
		 *	It's a valid EAP-Start, but the request
		 *	was marked as being proxied.  So we don't
		 *	do EAP, as the home server will do it.
		 */
		if (proxy) {
		do_proxy:
			RDEBUG2("Request is supposed to be proxied to "
				"Realm %s. Not doing EAP.", proxy->vp_strvalue);
			return EAP_NOOP;
		}

		RDEBUG2("Got EAP_START message");
		vp = fr_pair_afrom_num(request->reply, PW_EAP_MESSAGE, 0);
		if (!vp) return EAP_FAIL;
		fr_pair_add(&request->reply->vps, vp);

		/*
		 *	Manually create an EAP Identity request
		 */
		vp->vp_length = 5;
		vp->vp_octets = p = talloc_array(vp, uint8_t, vp->vp_length);

		p[0] = PW_EAP_REQUEST;
		p[1] = 0; /* ID */
		p[2] = 0;
		p[3] = 5; /* length */
		p[4] = PW_EAP_IDENTITY;

		request->reply->code = PW_CODE_ACCESS_CHALLENGE;
		return EAP_FOUND;
	} /* end of handling EAP-Start */

	/*
	 *	The EAP packet header is 4 bytes, plus one byte of
	 *	EAP sub-type.  Short packets are discarded, unless
	 *	we're proxying.
	 */
	if (eap_msg->vp_length < (EAP_HEADER_LEN + 1)) {
		if (proxy) goto do_proxy;

		RDEBUG2("Ignoring EAP-Message which is too short to be meaningful");
		return EAP_FAIL;
	}

	/*
	 *	Create an EAP-Type containing the EAP-type
	 *	from the packet.
	 */
	vp = fr_pair_afrom_num(request->packet, PW_EAP_TYPE, 0);
	if (vp) {
		vp->vp_integer = eap_msg->vp_octets[4];
		fr_pair_add(&(request->packet->vps), vp);
	}

	/*
	 *	If the request was marked to be proxied, do it now.
	 *	This is done after checking for a valid length
	 *	(which may not be good), and after adding the EAP-Type
	 *	attribute.  This lets other modules selectively cancel
	 *	proxying based on EAP-Type.
	 */
	if (proxy) goto do_proxy;

	/*
	 *	From now on, we're supposed to be handling the
	 *	EAP packet.  We better understand it...
	 */

	/*
	 *	We're allowed only a few codes.  Request, Response,
	 *	Success, or Failure.
	 */
	if ((eap_msg->vp_octets[0] == 0) ||
	    (eap_msg->vp_octets[0] >= PW_EAP_MAX_CODES)) {
		RDEBUG2("Peer sent EAP packet with unknown code %i", eap_msg->vp_octets[0]);
	} else {
		RDEBUG2("Peer sent EAP %s (code %i) ID %d length %zu",
		        eap_codes[eap_msg->vp_octets[0]],
		        eap_msg->vp_octets[0],
		        eap_msg->vp_octets[1],
		        eap_msg->vp_length);
	}

	/*
	 *	We handle request and responses.  The only other defined
	 *	codes are success and fail.  The client SHOULD NOT be
	 *	sending success/fail packets to us, as it doesn't make
	 *	sense.
	 */
	if ((eap_msg->vp_octets[0] != PW_EAP_REQUEST) &&
	    (eap_msg->vp_octets[0] != PW_EAP_RESPONSE)) {
		RDEBUG2("Ignoring EAP packet which we don't know how to handle");
		return EAP_FAIL;
	}

	/*
	 *	We've been told to ignore unknown EAP types, AND it's
	 *	an unknown type.  Return "NOOP", which will cause the
	 *	mod_authorize() to return NOOP.
	 *
	 *	EAP-Identity, Notification, and NAK are all handled
	 *	internally, so they never have handlers.
	 */
	if ((eap_msg->vp_octets[4] >= PW_EAP_MD5) &&
	    inst->ignore_unknown_types &&
	    ((eap_msg->vp_octets[4] == 0) ||
	     (eap_msg->vp_octets[4] >= PW_EAP_MAX_TYPES) ||
	     (!inst->methods[eap_msg->vp_octets[4]]))) {
		RDEBUG2("Ignoring Unknown EAP type");
		return EAP_NOOP;
	}

	/*
	 *	They're NAKing the EAP type we wanted to use, and
	 *	asking for one which we don't support.
	 *
	 *	NAK is code + id + length1 + length + NAK
	 *	     + requested EAP type(s).
	 *
	 *	We know at this point that we can't handle the
	 *	request.  We could either return an EAP-Fail here, but
	 *	it's not too critical.
	 *
	 *	By returning "noop", we can ensure that authorize()
	 *	returns NOOP, and another module may choose to proxy
	 *	the request.
	 */
	if ((eap_msg->vp_octets[4] == PW_EAP_NAK) &&
	    (eap_msg->vp_length >= (EAP_HEADER_LEN + 2)) &&
	    inst->ignore_unknown_types &&
	    ((eap_msg->vp_octets[5] == 0) ||
	     (eap_msg->vp_octets[5] >= PW_EAP_MAX_TYPES) ||
	     (!inst->methods[eap_msg->vp_octets[5]]))) {
		RDEBUG2("Ignoring NAK with request for unknown EAP type");
		return EAP_NOOP;
	}

	if ((eap_msg->vp_octets[4] == PW_EAP_TTLS) ||
	    (eap_msg->vp_octets[4] == PW_EAP_PEAP)) {
		RDEBUG2("Continuing tunnel setup");
		return EAP_OK;
	}
	/*
	 * We return ok in response to EAP identity
	 * This means we can write:
	 *
	 * eap {
	 *   ok = return
	 * }
	 * ldap
	 * sql
	 *
	 * ...in the inner-tunnel, to avoid expensive and unnecessary SQL/LDAP lookups
	 */
	if (eap_msg->vp_octets[4] == PW_EAP_IDENTITY) {
		RDEBUG2("EAP-Identity reply, returning 'ok' so we can short-circuit the rest of authorize");
		return EAP_OK;
	}

	/*
	 *	Later EAP messages are longer than the 'start'
	 *	message, so if everything is OK, this function returns
	 *	'no start found', so that the rest of the EAP code can
	 *	use the State attribute to match this EAP-Message to
	 *	an ongoing conversation.
	 */
	RDEBUG2("No EAP Start, assuming it's an on-going EAP conversation");

	return EAP_NOTFOUND;
}

/*
 *	compose EAP FAILURE packet in EAP-Message
 */
void eap_fail(eap_handler_t *handler)
{
	/*
	 *	Delete any previous replies.
	 */
	fr_pair_delete_by_num(&handler->request->reply->vps, PW_EAP_MESSAGE, 0, TAG_ANY);
	fr_pair_delete_by_num(&handler->request->reply->vps, PW_STATE, 0, TAG_ANY);

	talloc_free(handler->eap_ds->request);
	handler->eap_ds->request = talloc_zero(handler->eap_ds, eap_packet_t);
	handler->eap_ds->request->code = PW_EAP_FAILURE;
	handler->finished = true;
	eap_compose(handler);
}

/*
 *	compose EAP SUCCESS packet in EAP-Message
 */
void eap_success(eap_handler_t *handler)
{
	handler->eap_ds->request->code = PW_EAP_SUCCESS;
	handler->finished = true;
	eap_compose(handler);
}

/*
 * Basic EAP packet verfications & validations
 */
static int eap_validation(REQUEST *request, eap_packet_raw_t **eap_packet_p)
{
	uint16_t len;
	eap_packet_raw_t *eap_packet = *eap_packet_p;

	memcpy(&len, eap_packet->length, sizeof(uint16_t));
	len = ntohs(len);

	/*
	 *	High level EAP packet checks
	 */
	if (len <= EAP_HEADER_LEN) {
		RAUTH("EAP packet is too small: Ignoring it.");
		return EAP_INVALID;
	}

	if (eap_packet->code == PW_EAP_REQUEST) {
		VALUE_PAIR *vp;
		RAUTH("Unexpected EAP-Request.  NAKing it.");

		vp = pair_make_reply("EAP-Message", "123456", T_OP_SET);
		if (vp) {
			uint8_t buffer[6];

			buffer[0] = PW_EAP_RESPONSE;
			buffer[1] = eap_packet->id;
			buffer[2] = 0;
			buffer[3] = 6;
			buffer[4] = PW_EAP_NAK;
			buffer[5] = 0; /* no overlapping EAP types */

			fr_pair_value_memcpy(vp, buffer, 6);
		}

		/*
		 *	Ensure that the Access-Reject has a Message-Authenticator
		 */
		vp = fr_pair_find_by_num(request->reply->vps, PW_MESSAGE_AUTHENTICATOR, 0, TAG_ANY);
		if (!vp) {
			vp = fr_pair_afrom_num(request->reply, PW_MESSAGE_AUTHENTICATOR, 0);
			vp->vp_length = AUTH_VECTOR_LEN;
			vp->vp_octets = talloc_zero_array(vp, uint8_t, vp->vp_length);
			fr_pair_add(&(request->reply->vps), vp);
		}
		request->reply->code = PW_CODE_ACCESS_REJECT;

		return EAP_INVALID;
	}

	/*
	 *	We only allow responses from the peer.  The peer
	 *	CANNOT ask us to authenticate outselves.
	 */
	if (eap_packet->code != PW_EAP_RESPONSE) {
		RAUTH("Unexpected packet code %02x: Ignoring it.", eap_packet->code);
		return EAP_INVALID;
	}

	if ((eap_packet->data[0] <= 0) ||
	    (eap_packet->data[0] >= PW_EAP_MAX_TYPES)) {
		/*
		 *	Handle expanded types by smashing them to
		 *	normal types.
		 */
		if (eap_packet->data[0] == PW_EAP_EXPANDED_TYPE) {
			uint8_t *p, *q;

			if (len <= (EAP_HEADER_LEN + 1 + 3 + 4)) {
				RAUTH("Expanded EAP type is too short: ignoring the packet");
				return EAP_INVALID;
			}

			if ((eap_packet->data[1] != 0) ||
			    (eap_packet->data[2] != 0) ||
			    (eap_packet->data[3] != 0)) {
				RAUTH("Expanded EAP type has unknown Vendor-ID: ignoring the packet");
				return EAP_INVALID;
			}

			if ((eap_packet->data[4] != 0) ||
			    (eap_packet->data[5] != 0) ||
			    (eap_packet->data[6] != 0)) {
				RAUTH("Expanded EAP type has unknown Vendor-Type: ignoring the packet");
				return EAP_INVALID;
			}

			if ((eap_packet->data[7] == 0) ||
			    (eap_packet->data[7] >= PW_EAP_MAX_TYPES)) {
				RAUTH("Unsupported Expanded EAP type %s (%u): ignoring the packet",
				      eap_type2name(eap_packet->data[7]), eap_packet->data[7]);
				return EAP_INVALID;
			}

			if (eap_packet->data[7] == PW_EAP_NAK) {
				RAUTH("Unsupported Expanded EAP-NAK: ignoring the packet");
				return EAP_INVALID;
			}

			/*
			 *	Re-write the EAP packet to NOT have the expanded type.
			 */
			q = (uint8_t *) eap_packet;
			memmove(q + EAP_HEADER_LEN, q + EAP_HEADER_LEN + 7, len - 7 - EAP_HEADER_LEN);

			p = talloc_realloc(talloc_parent(eap_packet), eap_packet, uint8_t, len - 7);
			if (!p) {
				RAUTH("Unsupported EAP type %s (%u): ignoring the packet",
				      eap_type2name(eap_packet->data[0]), eap_packet->data[0]);
				return EAP_INVALID;
			}

			len -= 7;
			p[2] = (len >> 8) & 0xff;
			p[3] = len & 0xff;

			*eap_packet_p = (eap_packet_raw_t *) p;
			RWARN("Converting Expanded EAP to normal EAP.");
			RWARN("Unnecessary use of Expanded EAP types is not recommended.");

			return EAP_VALID;
		}

		RAUTH("Unsupported EAP type %s (%u): ignoring the packet",
		      eap_type2name(eap_packet->data[0]), eap_packet->data[0]);
		return EAP_INVALID;
	}

	/* we don't expect notification, but we send it */
	if (eap_packet->data[0] == PW_EAP_NOTIFICATION) {
		RAUTH("Got NOTIFICATION, "
			       "Ignoring the packet");
		return EAP_INVALID;
	}

	return EAP_VALID;
}


/*
 *  Get the user Identity only from EAP-Identity packets
 */
static char *eap_identity(REQUEST *request, eap_handler_t *handler, eap_packet_raw_t *eap_packet)
{
	int size;
	uint16_t len;
	char *identity;

	if ((!eap_packet) ||
	    (eap_packet->code != PW_EAP_RESPONSE) ||
	    (eap_packet->data[0] != PW_EAP_IDENTITY)) {
		return NULL;
	}

	memcpy(&len, eap_packet->length, sizeof(uint16_t));
	len = ntohs(len);

	if ((len <= 5) || (eap_packet->data[1] == 0x00)) {
		REDEBUG("EAP-Identity Unknown");
		return NULL;
	}

	if (len > 1024) {
		REDEBUG("EAP-Identity too long");
		return NULL;
	}

	size = len - 5;
	identity = talloc_array(handler, char, size + 1);
	memcpy(identity, &eap_packet->data[1], size);
	identity[size] = '\0';

	return identity;
}


/*
 *	Create our Request-Response data structure with the eap packet
 */
static EAP_DS *eap_buildds(eap_handler_t *handler,
			   eap_packet_raw_t **eap_packet_p)
{
	EAP_DS		*eap_ds = NULL;
	eap_packet_raw_t	*eap_packet = *eap_packet_p;
	int		typelen;
	uint16_t	len;

	if ((eap_ds = eap_ds_alloc(handler)) == NULL) {
		return NULL;
	}

	eap_ds->response->packet = (uint8_t *) eap_packet;
	(void) talloc_steal(eap_ds, eap_packet);
	eap_ds->response->code = eap_packet->code;
	eap_ds->response->id = eap_packet->id;
	eap_ds->response->type.num = eap_packet->data[0];

	memcpy(&len, eap_packet->length, sizeof(uint16_t));
	len = ntohs(len);
	eap_ds->response->length = len;

	/*
	 *	We've eaten the eap packet into the eap_ds.
	 */
	*eap_packet_p = NULL;

	/*
	 *	First 5 bytes in eap, are code + id + length(2) + type.
	 *
	 *	The rest is type-specific data.  We skip type while
	 *	getting typedata from data.
	 */
	typelen = len - 5/*code + id + length + type */;
	if (typelen > 0) {
		/*
		 *	Since the packet contains the complete
		 *	eap_packet, typedata will be a ptr in packet
		 *	to its typedata
		 */
		eap_ds->response->type.data = eap_ds->response->packet + 5/*code+id+length+type*/;
		eap_ds->response->type.length = typelen;
	} else {
		eap_ds->response->type.length = 0;
		eap_ds->response->type.data = NULL;
	}

	return eap_ds;
}


/*
 * If identity response then create a fresh handler & fill the identity
 * else handler MUST be in our list, get that.
 * This handler creation cannot fail
 *
 * username contains REQUEST->username which might have been stripped.
 * identity contains the one sent in EAP-Identity response
 */
eap_handler_t *eap_handler(rlm_eap_t *inst, eap_packet_raw_t **eap_packet_p,
			   REQUEST *request)
{
	eap_handler_t	*handler = NULL;
	eap_packet_raw_t *eap_packet;
	VALUE_PAIR	*vp;

	/*
	 *	Ensure it's a valid EAP-Request, or EAP-Response.
	 */
	if (eap_validation(request, eap_packet_p) == EAP_INVALID) {
	error:
		talloc_free(*eap_packet_p);
		*eap_packet_p = NULL;
		return NULL;
	}

	eap_packet = *eap_packet_p;

	/*
	 *	eap_handler_t MUST be found in the list if it is not
	 *	EAP-Identity response
	 */
	if (eap_packet->data[0] != PW_EAP_IDENTITY) {
		handler = eaplist_find(inst, request, eap_packet);
		if (!handler) {
			/* Either send EAP_Identity or EAP-Fail */
			RDEBUG("Either EAP-request timed out OR EAP-response to an unknown EAP-request");
			goto error;
		}

		/*
		 *	Even more paranoia.  Without this, some weird
		 *	clients could do crazy things.
		 *
		 *	It's ok to send EAP sub-type NAK in response
		 *	to a request for a particular type, but it's NOT
		 *	OK to blindly return data for another type.
		 */
		if ((eap_packet->data[0] != PW_EAP_NAK) &&
		    (eap_packet->data[0] != handler->type)) {
			RERROR("Response appears to match a previous request, but the EAP type is wrong");
			RERROR("We expected EAP type %s, but received type %s",
			       eap_type2name(handler->type),
			       eap_type2name(eap_packet->data[0]));
			RERROR("Your Supplicant or NAS is probably broken");
			goto error;
		}

	       vp = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
	       if (!vp) {
		       /*
			*	NAS did not set the User-Name
			*	attribute, so we set it here and
			*	prepend it to the beginning of the
			*	request vps so that autz's work
			*	correctly
			*/
		       RDEBUG2("Broken NAS did not set User-Name, setting from EAP Identity");
		       vp = fr_pair_make(request->packet, &request->packet->vps, "User-Name", handler->identity, T_OP_EQ);
		       if (!vp) {
			       goto error;
		       }
	       } else {
		       /*
			*      A little more paranoia.  If the NAS
			*      *did* set the User-Name, and it doesn't
			*      match the identity, (i.e. If they
			*      change their User-Name part way through
			*      the EAP transaction), then reject the
			*      request as the NAS is doing something
			*      funny.
			*/
		       if (strncmp(handler->identity, vp->vp_strvalue,
				   MAX_STRING_LEN) != 0) {
			       RDEBUG("Identity does not match User-Name.  Authentication failed");
			       goto error;
		       }
	       }
	} else {		/* packet was EAP identity */
		handler = eap_handler_alloc(inst);
		if (!handler) {
			goto error;
		}

		/*
		 *	All fields in the handler are set to zero.
		 */
		handler->identity = eap_identity(request, handler, eap_packet);
		if (!handler->identity) {
			RDEBUG("Identity Unknown, authentication failed");
		error2:
			talloc_free(handler);
			goto error;
		}

	       vp = fr_pair_find_by_num(request->packet->vps, PW_USER_NAME, 0, TAG_ANY);
	       if (!vp) {
		       /*
			*	NAS did not set the User-Name
			*	attribute, so we set it here and
			*	prepend it to the beginning of the
			*	request vps so that autz's work
			*	correctly
			*/
		       RWDEBUG2("NAS did not set User-Name.  Setting it locally from EAP Identity");
		       vp = fr_pair_make(request->packet, &request->packet->vps, "User-Name", handler->identity, T_OP_EQ);
		       if (!vp) {
			       goto error2;
		       }
	       } else {
		       /*
			*      Paranoia.  If the NAS *did* set the
			*      User-Name, and it doesn't match the
			*      identity, the NAS is doing something
			*      funny, so reject the request.
			*/
		       if (strncmp(handler->identity, vp->vp_strvalue,
				   MAX_STRING_LEN) != 0) {
			       RDEBUG("Identity does not match User-Name, setting from EAP Identity");
			       goto error2;
		       }
	       }
	}

	handler->eap_ds = eap_buildds(handler, eap_packet_p);
	if (!handler->eap_ds) {
		goto error2;
	}

	handler->timestamp = request->timestamp;
	handler->request = request;
	return handler;
}
