#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include "rlm_eap.h"
static const char *eap_codes[] = {
"",
"request",
"response",
"success",
"failure"
};
int eaptype_load(EAP_TYPES **type, int eap_type, CONF_SECTION *cs)
{
char buffer[64];
char namebuf[64];
const char *eaptype_name;
lt_dlhandle handle;
EAP_TYPES *node;
eaptype_name = eaptype_type2name(eap_type, namebuf, sizeof(namebuf));
snprintf(buffer, sizeof(buffer), "rlm_eap_%s", eaptype_name);
handle = lt_dlopenext(buffer);
if (handle == NULL) {
radlog(L_ERR, "rlm_eap: Failed to link EAP-Type/%s: %s",
eaptype_name, lt_dlerror());
return -1;
}
node = (EAP_TYPES *)malloc(sizeof(EAP_TYPES));
if (node == NULL) {
radlog(L_ERR, "rlm_eap: out of memory");
return -1;
}
memset(node, 0, sizeof(*node));
node->handle = handle;
node->cs = cs;
node->typename = eaptype_name;
node->type_data = NULL;
node->type = (EAP_TYPE *)lt_dlsym(node->handle, buffer);
if (!node->type) {
radlog(L_ERR, "rlm_eap: Failed linking to %s structure in %s: %s",
buffer, eaptype_name, lt_dlerror());
lt_dlclose(node->handle);
free(node);
return -1;
}
cf_log_module(cs, "Linked to sub-module %s", buffer);
cf_log_module(cs, "Instantiating eap-%s", eaptype_name);
if ((node->type->attach) &&
((node->type->attach)(node->cs, &(node->type_data)) < 0)) {
radlog(L_ERR, "rlm_eap: Failed to initialize type %s",
eaptype_name);
lt_dlclose(node->handle);
free(node);
return -1;
}
*type = node;
return 0;
}
static int eaptype_call(EAP_TYPES *atype, EAP_HANDLER *handler)
{
int rcode = 1;
REQUEST *request = handler->request;
const char *module = request->module;
RDEBUG2("processing type %s", atype->typename);
request->module = atype->typename;
rad_assert(atype != NULL);
switch (handler->stage) {
case INITIATE:
if (!atype->type->initiate(atype->type_data, handler))
rcode = 0;
break;
case AUTHORIZE:
if (!atype->type->authorize ||
!atype->type->authorize(atype->type_data, handler))
rcode = 0;
break;
case AUTHENTICATE:
if (!atype->type->authenticate ||
!atype->type->authenticate(atype->type_data, handler))
rcode = 0;
break;
default:
RDEBUG("Internal sanity check failed on eap_type");
rcode = 0;
break;
}
request->module = module;
return rcode;
}
int eaptype_select(rlm_eap_t *inst, EAP_HANDLER *handler)
{
size_t i;
unsigned int default_eap_type = inst->default_eap_type;
eaptype_t *eaptype;
VALUE_PAIR *vp;
char namebuf[64];
const char *eaptype_name;
REQUEST *request = handler->request;
eaptype = &handler->eap_ds->response->type;
if ((eaptype->type == 0) ||
(eaptype->type > PW_EAP_MAX_TYPES)) {
RDEBUG2("Asked to select bad type");
return EAP_INVALID;
}
if (handler->request->parent && handler->request->parent->parent) {
RDEBUG2("Multiple levels of TLS nesting is invalid.");
return EAP_INVALID;
}
switch(eaptype->type) {
case PW_EAP_IDENTITY:
RDEBUG2("EAP Identity");
vp = pairfind(handler->request->config_items,
PW_EAP_TYPE);
if (vp) default_eap_type = vp->vp_integer;
do_initiate:
if ((default_eap_type < PW_EAP_MD5) ||
(default_eap_type > PW_EAP_MAX_TYPES) ||
(inst->types[default_eap_type] == NULL)) {
RDEBUG2("No such EAP type %s",
eaptype_type2name(default_eap_type,
namebuf, sizeof(namebuf)));
return EAP_INVALID;
}
handler->stage = INITIATE;
handler->eap_type = default_eap_type;
if ((default_eap_type == PW_EAP_TTLS) ||
(default_eap_type == PW_EAP_PEAP)) {
default_eap_type = PW_EAP_TLS;
}
if ((default_eap_type == PW_EAP_TNC) &&
!handler->request->parent) {
RDEBUG2("ERROR: EAP-TNC must be run inside of a TLS method.");
return EAP_INVALID;
}
if (eaptype_call(inst->types[default_eap_type],
handler) == 0) {
RDEBUG2("Default EAP type %s failed in initiate",
eaptype_type2name(default_eap_type,
namebuf, sizeof(namebuf)));
return EAP_INVALID;
}
break;
case PW_EAP_NAK:
RDEBUG2("EAP NAK");
if (handler->opaque && handler->free_opaque) {
handler->free_opaque(handler->opaque);
handler->free_opaque = NULL;
handler->opaque = NULL;
}
if (eaptype->data == NULL) {
RDEBUG2("Empty NAK packet, cannot decide what EAP type the client wants.");
return EAP_INVALID;
}
default_eap_type = 0;
vp = pairfind(handler->request->config_items,
PW_EAP_TYPE);
for (i = 0; i < eaptype->length; i++) {
if (eaptype->data[i] < PW_EAP_MD5) {
RDEBUG2("NAK asked for bad type %d",
eaptype->data[i]);
return EAP_INVALID;
}
if ((eaptype->data[i] > PW_EAP_MAX_TYPES) ||
!inst->types[eaptype->data[i]]) {
DICT_VALUE *dv;
dv = dict_valbyattr(PW_EAP_TYPE, eaptype->data[i]);
if (dv) {
RDEBUG2("NAK asked for unsupported type %s",
dv->name);
} else {
RDEBUG2("NAK asked for unsupported type %d",
eaptype->data[i]);
}
continue;
}
eaptype_name = eaptype_type2name(eaptype->data[i],
namebuf,
sizeof(namebuf));
if (handler->eap_type == eaptype->data[i]) {
RDEBUG2("ERROR! Our request for %s was NAK'd with a request for %s. Skipping the requested type.",
eaptype_name, eaptype_name);
continue;
}
if (vp && (vp->vp_integer != eaptype->data[i])) {
char mynamebuf[64];
RDEBUG2("Client wants %s, while we require %s. Skipping the requested type.",
eaptype_name,
eaptype_type2name(vp->vp_integer,
mynamebuf,
sizeof(mynamebuf)));
continue;
}
default_eap_type = eaptype->data[i];
break;
}
if (!default_eap_type) {
RDEBUG2("No common EAP types found.");
return EAP_INVALID;
}
eaptype_name = eaptype_type2name(default_eap_type,
namebuf, sizeof(namebuf));
RDEBUG2("EAP-NAK asked for EAP-Type/%s",
eaptype_name);
goto do_initiate;
break;
default:
eaptype_name = eaptype_type2name(eaptype->type,
namebuf,
sizeof(namebuf));
RDEBUG2("EAP/%s", eaptype_name);
if (!inst->types[eaptype->type]) {
RDEBUG2("EAP type %d is unsupported",
eaptype->type);
return EAP_INVALID;
}
rad_assert(handler->stage == AUTHENTICATE);
handler->eap_type = eaptype->type;
if (eaptype_call(inst->types[eaptype->type],
handler) == 0) {
RDEBUG2("Handler failed in EAP/%s",
eaptype_name);
return EAP_INVALID;
}
break;
}
return EAP_OK;
}
int eap_compose(EAP_HANDLER *handler)
{
VALUE_PAIR *vp;
eap_packet_t *eap_packet;
REQUEST *request = handler->request;
EAP_DS *eap_ds = handler->eap_ds;
EAP_PACKET *reply = eap_ds->request;
int rcode;
if (!eap_ds->set_request_id) {
reply->id = handler->eap_ds->response->id;
switch (reply->code) {
case PW_EAP_SUCCESS:
case PW_EAP_FAILURE:
break;
default:
++reply->id;
}
} else {
RDEBUG2("Underlying EAP-Type set EAP ID to %d",
reply->id);
}
if (((eap_ds->request->code == PW_EAP_REQUEST) ||
(eap_ds->request->code == PW_EAP_RESPONSE)) &&
(eap_ds->request->type.type == 0)) {
rad_assert(handler->eap_type >= PW_EAP_MD5);
rad_assert(handler->eap_type <= PW_EAP_MAX_TYPES);
eap_ds->request->type.type = handler->eap_type;
}
if (eap_wireformat(reply) == EAP_INVALID) {
return RLM_MODULE_INVALID;
}
eap_packet = (eap_packet_t *)reply->packet;
vp = eap_packet2vp(eap_packet);
if (!vp) return RLM_MODULE_INVALID;
pairadd(&(request->reply->vps), vp);
vp = pairfind(request->reply->vps, PW_MESSAGE_AUTHENTICATOR);
if (!vp) {
vp = paircreate(PW_MESSAGE_AUTHENTICATOR, PW_TYPE_OCTETS);
memset(vp->vp_octets, 0, AUTH_VECTOR_LEN);
vp->length = AUTH_VECTOR_LEN;
pairadd(&(request->reply->vps), vp);
}
rcode = RLM_MODULE_OK;
if (!request->reply->code) switch(reply->code) {
case PW_EAP_RESPONSE:
request->reply->code = PW_AUTHENTICATION_ACK;
rcode = RLM_MODULE_HANDLED;
break;
case PW_EAP_SUCCESS:
request->reply->code = PW_AUTHENTICATION_ACK;
rcode = RLM_MODULE_OK;
break;
case PW_EAP_FAILURE:
request->reply->code = PW_AUTHENTICATION_REJECT;
rcode = RLM_MODULE_REJECT;
break;
case PW_EAP_REQUEST:
request->reply->code = PW_ACCESS_CHALLENGE;
rcode = RLM_MODULE_HANDLED;
break;
default:
if (request->options & RAD_REQUEST_OPTION_PROXY_EAP) {
return RLM_MODULE_HANDLED;
}
radlog(L_ERR, "rlm_eap: reply code %d is unknown, Rejecting the request.", reply->code);
request->reply->code = PW_AUTHENTICATION_REJECT;
reply->code = PW_EAP_FAILURE;
rcode = RLM_MODULE_REJECT;
break;
}
return rcode;
}
int eap_start(rlm_eap_t *inst, REQUEST *request)
{
VALUE_PAIR *vp, *proxy;
VALUE_PAIR *eap_msg;
eap_msg = pairfind(request->packet->vps, PW_EAP_MESSAGE);
if (eap_msg == NULL) {
RDEBUG2("No EAP-Message, not doing EAP");
return EAP_NOOP;
}
vp = pairfind(request->packet->vps, PW_EAP_TYPE);
if (vp && vp->vp_integer == 0) {
RDEBUG2("Found EAP-Message, but EAP-Type = None, so we're not doing EAP.");
return EAP_NOOP;
}
proxy = pairfind(request->config_items, PW_PROXY_TO_REALM);
if (proxy) {
REALM *realm;
realm = realm_find(proxy->vp_strvalue);
if (!realm || (realm && (realm->auth_pool == NULL))) {
proxy = NULL;
}
}
if ((eap_msg->length == 0) || (eap_msg->length == 2)) {
EAP_DS *eap_ds;
EAP_HANDLER handler;
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");
if ((eap_ds = eap_ds_alloc()) == NULL) {
RDEBUG2("EAP Start failed in allocation");
return EAP_FAIL;
}
eap_ds->request->code = PW_EAP_REQUEST;
eap_ds->request->type.type = PW_EAP_IDENTITY;
memset(&handler, 0, sizeof(handler));
handler.request = request;
handler.eap_ds = eap_ds;
eap_compose(&handler);
eap_ds_free(&eap_ds);
return EAP_FOUND;
}
if (eap_msg->length < (EAP_HEADER_LEN + 1)) {
if (proxy) goto do_proxy;
RDEBUG2("Ignoring EAP-Message which is too short to be meaningful.");
return EAP_FAIL;
}
vp = paircreate(PW_EAP_TYPE, PW_TYPE_INTEGER);
if (vp) {
vp->vp_integer = eap_msg->vp_octets[4];
pairadd(&(request->packet->vps), vp);
}
if (proxy) goto do_proxy;
if ((eap_msg->vp_octets[0] == 0) ||
(eap_msg->vp_octets[0] > PW_EAP_MAX_CODES)) {
RDEBUG2("Unknown EAP packet");
} else {
RDEBUG2("EAP packet type %s id %d length %d",
eap_codes[eap_msg->vp_octets[0]],
eap_msg->vp_octets[1],
eap_msg->length);
}
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;
}
if ((eap_msg->vp_octets[4] >= PW_EAP_MD5) &&
inst->ignore_unknown_eap_types &&
((eap_msg->vp_octets[4] == 0) ||
(eap_msg->vp_octets[4] > PW_EAP_MAX_TYPES) ||
(inst->types[eap_msg->vp_octets[4]] == NULL))) {
RDEBUG2(" Ignoring Unknown EAP type");
return EAP_NOOP;
}
if ((eap_msg->vp_octets[4] == PW_EAP_NAK) &&
(eap_msg->length >= (EAP_HEADER_LEN + 2)) &&
inst->ignore_unknown_eap_types &&
((eap_msg->vp_octets[5] == 0) ||
(eap_msg->vp_octets[5] > PW_EAP_MAX_TYPES) ||
(inst->types[eap_msg->vp_octets[5]] == NULL))) {
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;
}
RDEBUG2("No EAP Start, assuming it's an on-going EAP conversation");
return EAP_NOTFOUND;
}
void eap_fail(EAP_HANDLER *handler)
{
pairdelete(&handler->request->reply->vps, PW_EAP_MESSAGE);
pairdelete(&handler->request->reply->vps, PW_STATE);
eap_packet_free(&handler->eap_ds->request);
handler->eap_ds->request = eap_packet_alloc();
handler->eap_ds->request->code = PW_EAP_FAILURE;
eap_compose(handler);
}
void eap_success(EAP_HANDLER *handler)
{
handler->eap_ds->request->code = PW_EAP_SUCCESS;
eap_compose(handler);
}
static int eap_validation(REQUEST *request, eap_packet_t *eap_packet)
{
uint16_t len;
memcpy(&len, eap_packet->length, sizeof(uint16_t));
len = ntohs(len);
if ((len <= EAP_HEADER_LEN) ||
((eap_packet->code != PW_EAP_RESPONSE) &&
(eap_packet->code != PW_EAP_REQUEST)) ||
(eap_packet->data[0] <= 0) ||
(eap_packet->data[0] > PW_EAP_MAX_TYPES)) {
radlog_request(L_AUTH, 0, request,
"Badly formatted EAP Message: Ignoring the packet");
return EAP_INVALID;
}
if (eap_packet->data[0] == PW_EAP_NOTIFICATION) {
radlog_request(L_AUTH, 0, request, "Got NOTIFICATION, "
"Ignoring the packet");
return EAP_INVALID;
}
return EAP_VALID;
}
static char *eap_identity(REQUEST *request, eap_packet_t *eap_packet)
{
int size;
uint16_t len;
char *identity;
if ((eap_packet == NULL) ||
(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)) {
RDEBUG("UserIdentity Unknown ");
return NULL;
}
size = len - 5;
identity = rad_malloc(size + 1);
memcpy(identity, &eap_packet->data[1], size);
identity[size] = '\0';
return identity;
}
static EAP_DS *eap_buildds(eap_packet_t **eap_packet_p)
{
EAP_DS *eap_ds = NULL;
eap_packet_t *eap_packet = *eap_packet_p;
int typelen;
uint16_t len;
if ((eap_ds = eap_ds_alloc()) == NULL) {
return NULL;
}
eap_ds->response->packet = (unsigned char *)eap_packet;
eap_ds->response->code = eap_packet->code;
eap_ds->response->id = eap_packet->id;
eap_ds->response->type.type = eap_packet->data[0];
memcpy(&len, eap_packet->length, sizeof(uint16_t));
len = ntohs(len);
eap_ds->response->length = len;
*eap_packet_p = NULL;
typelen = len - 5;
if (typelen > 0) {
eap_ds->response->type.data = eap_ds->response->packet + 5;
eap_ds->response->type.length = typelen;
} else {
eap_ds->response->type.length = 0;
eap_ds->response->type.data = NULL;
}
return eap_ds;
}
EAP_HANDLER *eap_handler(rlm_eap_t *inst, eap_packet_t **eap_packet_p,
REQUEST *request)
{
EAP_HANDLER *handler = NULL;
eap_packet_t *eap_packet = *eap_packet_p;
VALUE_PAIR *vp;
if (eap_validation(request, eap_packet) == EAP_INVALID) {
free(*eap_packet_p);
*eap_packet_p = NULL;
return NULL;
}
if (eap_packet->data[0] != PW_EAP_IDENTITY) {
handler = eaplist_find(inst, request, eap_packet);
if (handler == NULL) {
RDEBUG("Either EAP-request timed out OR"
" EAP-response to an unknown EAP-request");
free(*eap_packet_p);
*eap_packet_p = NULL;
return NULL;
}
if ((eap_packet->data[0] != PW_EAP_NAK) &&
(eap_packet->data[0] != handler->eap_type)) {
RDEBUG("Response appears to match, but EAP type is wrong.");
free(*eap_packet_p);
*eap_packet_p = NULL;
return NULL;
}
vp = pairfind(request->packet->vps, PW_USER_NAME);
if (!vp) {
RDEBUG2("Broken NAS did not set User-Name, setting from EAP Identity");
vp = pairmake("User-Name", handler->identity, T_OP_EQ);
if (vp == NULL) {
RDEBUG("Out of memory");
free(*eap_packet_p);
*eap_packet_p = NULL;
return NULL;
}
vp->next = request->packet->vps;
request->packet->vps = vp;
} else {
if (strncmp(handler->identity, vp->vp_strvalue,
MAX_STRING_LEN) != 0) {
RDEBUG("Identity does not match User-Name. Authentication failed.");
free(*eap_packet_p);
*eap_packet_p = NULL;
return NULL;
}
}
} else {
handler = eap_handler_alloc(inst);
if (handler == NULL) {
RDEBUG("Out of memory.");
free(*eap_packet_p);
*eap_packet_p = NULL;
return NULL;
}
handler->identity = eap_identity(request, eap_packet);
if (handler->identity == NULL) {
RDEBUG("Identity Unknown, authentication failed");
free(*eap_packet_p);
*eap_packet_p = NULL;
eap_handler_free(inst, handler);
return NULL;
}
vp = pairfind(request->packet->vps, PW_USER_NAME);
if (!vp) {
RDEBUG2("WARNING NAS did not set User-Name. Setting it locally from EAP Identity");
vp = pairmake("User-Name", handler->identity, T_OP_EQ);
if (vp == NULL) {
RDEBUG("Out of memory");
free(*eap_packet_p);
*eap_packet_p = NULL;
eap_handler_free(inst, handler);
return NULL;
}
vp->next = request->packet->vps;
request->packet->vps = vp;
} else {
if (strncmp(handler->identity, vp->vp_strvalue,
MAX_STRING_LEN) != 0) {
RDEBUG("Identity does not match User-Name, setting from EAP Identity.");
free(*eap_packet_p);
*eap_packet_p = NULL;
eap_handler_free(inst, handler);
return NULL;
}
}
}
handler->eap_ds = eap_buildds(eap_packet_p);
if (handler->eap_ds == NULL) {
free(*eap_packet_p);
*eap_packet_p = NULL;
eap_handler_free(inst, handler);
return NULL;
}
handler->timestamp = request->timestamp;
handler->request = request;
return handler;
}