/*
 * eapcommon.c    rfc2284 & rfc2869 implementation
 *
 * code common to clients and to servers.
 *
 * Version:     $Id: eapcommon.c,v 1.5.4.2 2007/03/05 14:34:55 aland Exp $
 *
 *   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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Copyright 2000-2003  The FreeRADIUS server project
 * Copyright 2001  hereUare Communications, Inc. <raghud@hereuare.com>
 * Copyright 2003  Alan DeKok <aland@freeradius.org>
 * Copyright 2003  Michael Richardson <mcr@sandelman.ottawa.on.ca>
 */
/*
 *  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 "libradius.h"
#include "eap_types.h"

static const char rcsid[] = "$Id: eapcommon.c,v 1.5.4.2 2007/03/05 14:34:55 aland Exp $";

static const char *eap_types[] = {
  "",
  "identity",
  "notification",
  "nak",			/* NAK */
  "md5",
  "otp",
  "gtc",
  "7",
  "8",
  "9",
  "10",
  "11",
  "12",
  "tls",			/* 13 */
  "14",
  "15",
  "16",
  "leap",			/* 17 */
  "sim",                        /* 18 GSM-SIM authentication */
  "19",
  "20",
  "ttls",			/* 21 */
  "22",
  "23",
  "24",
  "peap",			/* 25 */
  "mschapv2",			/* 26 */
  "27",
  "28",
  "cisco_mschapv2"		/* 29 */
};
#define MAX_EAP_TYPE_NAME 29

/*
 *	Return an EAP-Type for a particular name.
 */
int eaptype_name2type(const char *name)
{
	int i;

	for (i = 0; i <= PW_EAP_MAX_TYPES; i++) {
		if (strcmp(name, eap_types[i]) == 0) {
			return i;
		}
	}

	return -1;
}

/*
 *	Returns a text string containing the name of the EAP type.
 */
const char *eaptype_type2name(unsigned int type, char *buffer, size_t buflen)
{
	DICT_VALUE	*dval;

	if (type > MAX_EAP_TYPE_NAME) {
		/*
		 *	Prefer the dictionary name over a number,
		 *	if it exists.
		 */
		dval = dict_valbyattr(PW_EAP_TYPE, type);
		if (dval) {
			snprintf(buffer, buflen, "%s", dval->name);
		}

		snprintf(buffer, buflen, "%d", type);
		return buffer;
	} else if ((*eap_types[type] >= '0') && (*eap_types[type] <= '9')) {
		/*
		 *	Prefer the dictionary name, if it exists.
		 */
		dval = dict_valbyattr(PW_EAP_TYPE, type);
		if (dval) {
			snprintf(buffer, buflen, "%s", dval->name);
			return buffer;
		} /* else it wasn't in the dictionary */
	} /* else the name in the array was non-numeric */

	/*
	 *	Return the name, whatever it is.
	 */
	return eap_types[type];
}

/*
 *	EAP packet format to be sent over the wire
 *
 *	i.e. code+id+length+data where data = null/type+typedata
 *	based on code.
 *
 * INPUT to function is reply->code
 *                      reply->id
 *                      reply->type   - setup with data
 *
 * OUTPUT reply->packet is setup with wire format, and will
 *                      be malloc()'ed to the right size.
 *
 */
static int eap_wireformat(EAP_PACKET *reply)
{

	eap_packet_t	*hdr;
	uint16_t total_length = 0;

	if (reply == NULL) return EAP_INVALID;

	/*
	 * if reply->packet is set, then the wire format
	 * has already been calculated, just succeed!
	 */
	if(reply->packet != NULL)
	{
		return EAP_VALID;
	}

	total_length = EAP_HEADER_LEN;
	if (reply->code < 3) {
		total_length += 1/*EAPtype*/;
		if (reply->type.data && reply->type.length > 0) {
			total_length += reply->type.length;
		}
	}

	reply->packet = (unsigned char *)malloc(total_length);
	hdr = (eap_packet_t *)reply->packet;
	if (!hdr) {
		radlog(L_ERR, "rlm_eap: out of memory");
		return EAP_INVALID;
	}

	hdr->code = (reply->code & 0xFF);
	hdr->id = (reply->id & 0xFF);
	total_length = htons(total_length);
	memcpy(hdr->length, &total_length, sizeof(uint16_t));

	/*
	 *	Request and Response packets are special.
	 */
	if ((reply->code == PW_EAP_REQUEST) ||
	    (reply->code == PW_EAP_RESPONSE)) {
		hdr->data[0] = (reply->type.type & 0xFF);

		/*
		 * Here since we cannot know the typedata format and length
		 *
		 * Type_data is expected to be wired by each EAP-Type
		 *
		 * Zero length/No typedata is supported as long as
		 * type is defined
		 */
		if (reply->type.data && reply->type.length > 0) {
			memcpy(&hdr->data[1], reply->type.data, reply->type.length);
			free(reply->type.data);
			reply->type.data = reply->packet + EAP_HEADER_LEN + 1/*EAPtype*/;
		}
	}

	return EAP_VALID;
}

/*
 *	compose EAP reply packet in EAP-Message attr of RADIUS.  If
 *	EAP exceeds 253, frame it in multiple EAP-Message attrs.
 */
int eap_basic_compose(RADIUS_PACKET *packet, EAP_PACKET *reply)
{
	uint16_t eap_len, len;
	VALUE_PAIR *eap_msg;
	VALUE_PAIR *vp;
	eap_packet_t *eap_packet;
	unsigned char 	*ptr;
	int rcode;

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

	memcpy(&eap_len, &(eap_packet->length), sizeof(uint16_t));
	len = eap_len = ntohs(eap_len);
	ptr = (unsigned char *)eap_packet;

	pairdelete(&(packet->vps), PW_EAP_MESSAGE);

	do {
		if (eap_len > 253) {
			len = 253;
			eap_len -= 253;
		} else {
			len = eap_len;
			eap_len = 0;
		}

		/*
		 * create a value pair & append it to the packet list
		 * This memory gets freed up when packet is freed up
		 */
		eap_msg = paircreate(PW_EAP_MESSAGE, PW_TYPE_OCTETS);
		memcpy(eap_msg->strvalue, ptr, len);
		eap_msg->length = len;
		pairadd(&(packet->vps), eap_msg);
		ptr += len;
		eap_msg = NULL;
	} while (eap_len);

	/*
	 *	EAP-Message is always associated with
	 *	Message-Authenticator but not vice-versa.
	 *
	 *	Don't add a Message-Authenticator if it's already
	 *	there.
	 */
	vp = pairfind(packet->vps, PW_MESSAGE_AUTHENTICATOR);
	if (!vp) {
		vp = paircreate(PW_MESSAGE_AUTHENTICATOR, PW_TYPE_OCTETS);
		memset(vp->strvalue, 0, AUTH_VECTOR_LEN);
		vp->length = AUTH_VECTOR_LEN;
		pairadd(&(packet->vps), vp);
	}

	/* Set request reply code, but only if it's not already set. */
	rcode = RLM_MODULE_OK;
	if (!packet->code) switch(reply->code) {
	case PW_EAP_RESPONSE:
	case PW_EAP_SUCCESS:
		packet->code = PW_AUTHENTICATION_ACK;
		rcode = RLM_MODULE_HANDLED;
		break;
	case PW_EAP_FAILURE:
		packet->code = PW_AUTHENTICATION_REJECT;
		rcode = RLM_MODULE_REJECT;
		break;
	case PW_EAP_REQUEST:
		packet->code = PW_ACCESS_CHALLENGE;
		rcode = RLM_MODULE_HANDLED;
		break;
	default:
		/* Should never enter here */
		radlog(L_ERR, "rlm_eap: reply code %d is unknown, Rejecting the request.", reply->code);
		packet->code = PW_AUTHENTICATION_REJECT;
		break;
	}

	return rcode;
}

/*
 * given a radius request with some attributes in the EAP range, build
 * them all into a single EAP-Message body.
 *
 * Note that this function will build multiple EAP-Message bodies
 * if there are multiple eligible EAP-types. This is incorrect, as the
 * recipient will in fact concatenate them.
 *
 * XXX - we could break the loop once we process one type. Maybe this
 *       just deserves an assert?
 *
 */
void map_eap_types(RADIUS_PACKET *req)
{
	VALUE_PAIR *vp, *vpnext;
	int id, eapcode;
	EAP_PACKET ep;
	int eap_type;

	vp = pairfind(req->vps, ATTRIBUTE_EAP_ID);
	if(vp == NULL) {
		id = ((int)getpid() & 0xff);
	} else {
		id = vp->lvalue;
	}

	vp = pairfind(req->vps, ATTRIBUTE_EAP_CODE);
	if(vp == NULL) {
		eapcode = PW_EAP_REQUEST;
	} else {
		eapcode = vp->lvalue;
	}


	for(vp = req->vps; vp != NULL; vp = vpnext) {
		/* save it in case it changes! */
		vpnext = vp->next;

		if(vp->attribute >= ATTRIBUTE_EAP_BASE &&
		   vp->attribute < ATTRIBUTE_EAP_BASE+256) {
			break;
		}
	}

	if(vp == NULL) {
		return;
	}

	eap_type = vp->attribute - ATTRIBUTE_EAP_BASE;

	switch(eap_type) {
	case PW_EAP_IDENTITY:
	case PW_EAP_NOTIFICATION:
	case PW_EAP_NAK:
	case PW_EAP_MD5:
	case PW_EAP_OTP:
	case PW_EAP_GTC:
	case PW_EAP_TLS:
	case PW_EAP_LEAP:
	case PW_EAP_TTLS:
	case PW_EAP_PEAP:
	default:
		/*
		 * no known special handling, it is just encoded as an
		 * EAP-message with the given type.
		 */

		/* nuke any existing EAP-Messages */
		pairdelete(&req->vps, PW_EAP_MESSAGE);

		memset(&ep, 0, sizeof(ep));
		ep.code = eapcode;
		ep.id   = id;
		ep.type.type = eap_type;
		ep.type.length = vp->length;
		ep.type.data = malloc(vp->length);
		memcpy(ep.type.data,vp->strvalue, vp->length);
		eap_basic_compose(req, &ep);
	}
}

/*
 * Handles multiple EAP-Message attrs
 * ie concatenates all to get the complete EAP packet.
 *
 * NOTE: Sometimes Framed-MTU might contain the length of EAP-Message,
 *      refer fragmentation in rfc2869.
 */
eap_packet_t *eap_attribute(VALUE_PAIR *vps)
{
	VALUE_PAIR *first, *vp;
	eap_packet_t *eap_packet;
	unsigned char *ptr;
	uint16_t len;
	int total_len;

	/*
	 *	Get only EAP-Message attribute list
	 */
	first = pairfind(vps, PW_EAP_MESSAGE);
	if (first == NULL) {
		radlog(L_ERR, "rlm_eap: EAP-Message not found");
		return NULL;
	}

	/*
	 *	Sanity check the length before doing anything.
	 */
	if (first->length < 4) {
		radlog(L_ERR, "rlm_eap: EAP packet is too short.");
		return NULL;
	}

	/*
	 *	Get the Actual length from the EAP packet
	 *	First EAP-Message contains the EAP packet header
	 */
	memcpy(&len, first->strvalue + 2, sizeof(len));
	len = ntohs(len);

	/*
	 *	Take out even more weird things.
	 */
	if (len < 4) {
		radlog(L_ERR, "rlm_eap: EAP packet has invalid length.");
		return NULL;
	}

	/*
	 *	Sanity check the length, BEFORE malloc'ing memory.
	 */
	total_len = 0;
	for (vp = first; vp; vp = pairfind(vp->next, PW_EAP_MESSAGE)) {
		total_len += vp->length;

		if (total_len > len) {
			radlog(L_ERR, "rlm_eap: Malformed EAP packet.  Length in packet header does not match actual length");
			return NULL;
		}
	}

	/*
	 *	If the length is SMALLER, die, too.
	 */
	if (total_len < len) {
		radlog(L_ERR, "rlm_eap: Malformed EAP packet.  Length in packet header does not match actual length");
		return NULL;
	}

	/*
	 *	Now that we know the lengths are OK, allocate memory.
	 */
	eap_packet = (eap_packet_t *) malloc(len);
	if (eap_packet == NULL) {
		radlog(L_ERR, "rlm_eap: out of memory");
		return NULL;
	}

	/*
	 *	Copy the data from EAP-Message's over to out EAP packet.
	 */
	ptr = (unsigned char *)eap_packet;

	/* RADIUS ensures order of attrs, so just concatenate all */
	for (vp = first; vp; vp = pairfind(vp->next, PW_EAP_MESSAGE)) {
		memcpy(ptr, vp->strvalue, vp->length);
		ptr += vp->length;
	}

	return eap_packet;
}

/*
 * given a radius request with an EAP-Message body, decode it specific
 * attributes.
 */
void unmap_eap_types(RADIUS_PACKET *rep)
{
	VALUE_PAIR *eap1;
	eap_packet_t *e;
	int len;
	int type;

	/* find eap message */
	e = eap_attribute(rep->vps);

	/* nothing to do! */
	if(e == NULL) return;

	/* create EAP-ID and EAP-CODE attributes to start */
	eap1 = paircreate(ATTRIBUTE_EAP_ID, PW_TYPE_INTEGER);
	eap1->lvalue = e->id;
	pairadd(&(rep->vps), eap1);

	eap1 = paircreate(ATTRIBUTE_EAP_CODE, PW_TYPE_INTEGER);
	eap1->lvalue = e->code;
	pairadd(&(rep->vps), eap1);

	switch(e->code)
	{
	default:
	case PW_EAP_SUCCESS:
	case PW_EAP_FAILURE:
		/* no data */
		break;

	case PW_EAP_REQUEST:
	case PW_EAP_RESPONSE:
		/* there is a type field, which we use to create
		 * a new attribute */

		/* the length was decode already into the attribute
		 * length, and was checked already. Network byte
		 * order, just pull it out using math.
		 */
		len = e->length[0]*256 + e->length[1];

		/* verify the length is big enough to hold type */
		if(len < 5)
		{
			free(e);
			return;
		}

		type = e->data[0];

		type += ATTRIBUTE_EAP_BASE;
		len -= 5;

		if(len > MAX_STRING_LEN) {
			len = MAX_STRING_LEN;
		}

		eap1 = paircreate(type, PW_TYPE_OCTETS);
		memcpy(eap1->strvalue, &e->data[1], len);
		eap1->length = len;
		pairadd(&(rep->vps), eap1);
		break;
	}

	free(e);
	return;
}

