#include <freeradius-devel/ident.h>
RCSID("$Id$")
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include "rlm_eap.h"
static const CONF_PARSER module_config[] = {
{ "default_eap_type", PW_TYPE_STRING_PTR,
offsetof(rlm_eap_t, default_eap_type_name), NULL, "md5" },
{ "timer_expire", PW_TYPE_INTEGER,
offsetof(rlm_eap_t, timer_limit), NULL, "60"},
{ "ignore_unknown_eap_types", PW_TYPE_BOOLEAN,
offsetof(rlm_eap_t, ignore_unknown_eap_types), NULL, "no" },
{ "cisco_accounting_username_bug", PW_TYPE_BOOLEAN,
offsetof(rlm_eap_t, cisco_accounting_username_bug), NULL, "no" },
{ "max_sessions", PW_TYPE_INTEGER,
offsetof(rlm_eap_t, max_sessions), NULL, "2048"},
{ NULL, -1, 0, NULL, NULL }
};
static int eap_detach(void *instance)
{
rlm_eap_t *inst;
int i;
inst = (rlm_eap_t *)instance;
#ifdef HAVE_PTHREAD_H
pthread_mutex_destroy(&(inst->session_mutex));
if (inst->handler_tree) pthread_mutex_destroy(&(inst->handler_mutex));
#endif
rbtree_free(inst->session_tree);
if (inst->handler_tree) rbtree_free(inst->handler_tree);
inst->session_tree = NULL;
eaplist_free(inst);
for (i = 0; i < PW_EAP_MAX_TYPES; i++) {
if (inst->types[i]) eaptype_free(inst->types[i]);
inst->types[i] = NULL;
}
free(inst);
return 0;
}
static int eap_handler_cmp(const void *a, const void *b)
{
int rcode;
const EAP_HANDLER *one = a;
const EAP_HANDLER *two = b;
if (one->eap_id < two->eap_id) return -1;
if (one->eap_id > two->eap_id) return +1;
rcode = memcmp(one->state, two->state, sizeof(one->state));
if (rcode != 0) return rcode;
if (fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr) != 0) {
DEBUG("WARNING: EAP packets are arriving from two different upstream servers. Has there been a proxy fail-over?");
}
return 0;
}
static int eap_handler_ptr_cmp(const void *a, const void *b)
{
if (a < b) return -1;
if (a > b) return +1;
return 0;
}
static int eap_instantiate(CONF_SECTION *cs, void **instance)
{
int i, eap_type;
int num_types;
CONF_SECTION *scs;
rlm_eap_t *inst;
inst = (rlm_eap_t *) malloc(sizeof(*inst));
if (!inst) {
return -1;
}
memset(inst, 0, sizeof(*inst));
if (cf_section_parse(cs, inst, module_config) < 0) {
eap_detach(inst);
return -1;
}
for (i = 0; i < 256; i++) {
inst->rand_pool.randrsl[i] = fr_rand();
}
fr_randinit(&inst->rand_pool, 1);
inst->rand_pool.randcnt = 0;
inst->xlat_name = cf_section_name2(cs);
if (!inst->xlat_name) inst->xlat_name = "EAP";
num_types = 0;
for(scs=cf_subsection_find_next(cs, NULL, NULL);
scs != NULL;
scs=cf_subsection_find_next(cs, scs, NULL)) {
const char *auth_type;
auth_type = cf_section_name1(scs);
if (!auth_type) continue;
eap_type = eaptype_name2type(auth_type);
if (eap_type < 0) {
radlog(L_ERR, "rlm_eap: Unknown EAP type %s",
auth_type);
eap_detach(inst);
return -1;
}
#ifndef HAVE_OPENSSL_SSL_H
if ((eap_type == PW_EAP_TLS) ||
(eap_type == PW_EAP_TTLS) ||
(eap_type == PW_EAP_PEAP)) {
DEBUG2("Ignoring EAP-Type/%s because we do not have OpenSSL support.", auth_type);
continue;
}
#endif
if (((eap_type == PW_EAP_TTLS) ||
(eap_type == PW_EAP_PEAP)) &&
(inst->types[PW_EAP_TLS] == NULL)) {
radlog(L_ERR, "rlm_eap: Unable to load EAP-Type/%s, as EAP-Type/TLS is required first.",
auth_type);
return -1;
}
if (eaptype_load(&inst->types[eap_type], eap_type, scs) < 0) {
eap_detach(inst);
return -1;
}
num_types++;
}
if (num_types == 0) {
radlog(L_ERR|L_CONS, "rlm_eap: No EAP type configured, module cannot do anything.");
eap_detach(inst);
return -1;
}
eap_type = eaptype_name2type(inst->default_eap_type_name);
if (eap_type < 0) {
radlog(L_ERR|L_CONS, "rlm_eap: Unknown default EAP type %s",
inst->default_eap_type_name);
eap_detach(inst);
return -1;
}
if (inst->types[eap_type] == NULL) {
radlog(L_ERR|L_CONS, "rlm_eap: No such sub-type for default EAP type %s",
inst->default_eap_type_name);
eap_detach(inst);
return -1;
}
inst->default_eap_type = eap_type;
inst->session_tree = rbtree_create(eap_handler_cmp, NULL, 0);
if (!inst->session_tree) {
radlog(L_ERR|L_CONS, "rlm_eap: Cannot initialize tree");
eap_detach(inst);
return -1;
}
if (fr_debug_flag) {
inst->handler_tree = rbtree_create(eap_handler_ptr_cmp, NULL, 0);
if (!inst->handler_tree) {
radlog(L_ERR|L_CONS, "rlm_eap: Cannot initialize tree");
eap_detach(inst);
return -1;
}
#ifdef HAVE_PTHREAD_H
if (pthread_mutex_init(&(inst->handler_mutex), NULL) < 0) {
radlog(L_ERR|L_CONS, "rlm_eap: Failed initializing mutex: %s", strerror(errno));
eap_detach(inst);
return -1;
}
#endif
}
#ifdef HAVE_PTHREAD_H
if (pthread_mutex_init(&(inst->session_mutex), NULL) < 0) {
radlog(L_ERR|L_CONS, "rlm_eap: Failed initializing mutex: %s", strerror(errno));
eap_detach(inst);
return -1;
}
#endif
*instance = inst;
return 0;
}
static int eap_authenticate(void *instance, REQUEST *request)
{
rlm_eap_t *inst;
EAP_HANDLER *handler;
eap_packet_t *eap_packet;
int rcode;
inst = (rlm_eap_t *) instance;
if (!pairfind(request->packet->vps, PW_EAP_MESSAGE)) {
RDEBUG("ERROR: You set 'Auth-Type = EAP' for a request that does not contain an EAP-Message attribute!");
return RLM_MODULE_INVALID;
}
eap_packet = eap_vp2packet(request->packet->vps);
if (eap_packet == NULL) {
radlog_request(L_ERR, 0, request, "Malformed EAP Message");
return RLM_MODULE_FAIL;
}
handler = eap_handler(inst, &eap_packet, request);
if (handler == NULL) {
RDEBUG2("Failed in handler");
return RLM_MODULE_INVALID;
}
rcode = eaptype_select(inst, handler);
if (rcode == EAP_INVALID) {
eap_fail(handler);
eap_handler_free(inst, handler);
RDEBUG2("Failed in EAP select");
return RLM_MODULE_INVALID;
}
#ifdef WITH_PROXY
if ((request->options & RAD_REQUEST_OPTION_PROXY_EAP) != 0) {
RDEBUG2(" Not-EAP proxy set. Not composing EAP");
handler->inst_holder = inst;
rcode = request_data_add(request,
inst, REQUEST_DATA_EAP_HANDLER,
handler,
(void *) eap_opaque_free);
rad_assert(rcode == 0);
return RLM_MODULE_HANDLED;
}
#endif
#ifdef WITH_PROXY
if (request->proxy != NULL) {
VALUE_PAIR *vp = NULL;
rad_assert(request->proxy_reply == NULL);
handler->inst_holder = inst;
rcode = request_data_add(request,
inst, REQUEST_DATA_EAP_HANDLER,
handler,
(void *) eap_opaque_free);
rad_assert(rcode == 0);
vp = pairfind(request->proxy->vps, PW_EAP_MESSAGE);
if (vp) {
vp = pairfind(request->proxy->vps, PW_MESSAGE_AUTHENTICATOR);
if (!vp) {
vp = pairmake("Message-Authenticator",
"0x00", T_OP_EQ);
rad_assert(vp != NULL);
pairadd(&(request->proxy->vps), vp);
}
}
pairdelete(&request->proxy->vps, PW_FREERADIUS_PROXIED_TO);
RDEBUG2(" Tunneled session will be proxied. Not doing EAP.");
return RLM_MODULE_HANDLED;
}
#endif
rcode = eap_compose(handler);
if (((handler->eap_ds->request->code == PW_EAP_REQUEST) &&
(handler->eap_ds->request->type.type >= PW_EAP_MD5)) ||
((handler->eap_ds->response->code == PW_EAP_RESPONSE) &&
(handler->eap_ds->response->type.type == PW_EAP_LEAP) &&
(handler->eap_ds->request->code == PW_EAP_SUCCESS) &&
(handler->eap_ds->request->type.type == 0))) {
if (!eaplist_add(inst, handler)) {
eap_fail(handler);
eap_handler_free(inst, handler);
return RLM_MODULE_FAIL;
}
} else {
RDEBUG2("Freeing handler");
eap_handler_free(inst, handler);
}
if ((request->reply->code == PW_AUTHENTICATION_ACK) &&
request->username) {
VALUE_PAIR *vp;
vp = pairfind(request->reply->vps, PW_USER_NAME);
if (!vp) {
vp = pairmake("User-Name", "",
T_OP_EQ);
strlcpy(vp->vp_strvalue, request->username->vp_strvalue,
sizeof(vp->vp_strvalue));
vp->length = request->username->length;
rad_assert(vp != NULL);
pairadd(&(request->reply->vps), vp);
}
if ((inst->cisco_accounting_username_bug) &&
(vp->length < (int) sizeof(vp->vp_strvalue))) {
vp->vp_strvalue[vp->length] = '\0';
vp->length++;
}
}
return rcode;
}
static int eap_authorize(void *instance, REQUEST *request)
{
rlm_eap_t *inst;
int status;
VALUE_PAIR *vp;
inst = (rlm_eap_t *)instance;
#ifdef WITH_PROXY
if (request->proxy != NULL)
return RLM_MODULE_NOOP;
#endif
status = eap_start(inst, request);
switch(status) {
case EAP_NOOP:
return RLM_MODULE_NOOP;
case EAP_FAIL:
return RLM_MODULE_FAIL;
case EAP_FOUND:
return RLM_MODULE_HANDLED;
case EAP_OK:
case EAP_NOTFOUND:
default:
break;
}
vp = pairfind(request->config_items, PW_AUTH_TYPE);
if ((!vp) ||
(vp->vp_integer != PW_AUTHTYPE_REJECT)) {
vp = pairmake("Auth-Type", inst->xlat_name, T_OP_EQ);
if (!vp) {
RDEBUG2("Failed to create Auth-Type %s: %s\n",
inst->xlat_name, fr_strerror());
return RLM_MODULE_FAIL;
}
pairadd(&request->config_items, vp);
} else {
RDEBUG2("WARNING: Auth-Type already set. Not setting to EAP");
}
if (status == EAP_OK) return RLM_MODULE_OK;
return RLM_MODULE_UPDATED;
}
#ifdef WITH_PROXY
static int eap_post_proxy(void *inst, REQUEST *request)
{
size_t i;
size_t len;
VALUE_PAIR *vp;
EAP_HANDLER *handler;
handler = request_data_get(request, inst, REQUEST_DATA_EAP_HANDLER);
if (handler != NULL) {
int rcode;
eap_tunnel_data_t *data;
data = (eap_tunnel_data_t *) request_data_get(request,
request->proxy,
REQUEST_DATA_EAP_TUNNEL_CALLBACK);
if (!data) {
radlog_request(L_ERR, 0, request, "Failed to retrieve callback for tunneled session!");
eap_handler_free(inst, handler);
return RLM_MODULE_FAIL;
}
RDEBUG2("Doing post-proxy callback");
rcode = data->callback(handler, data->tls_session);
free(data);
if (rcode == 0) {
RDEBUG2("Failed in post-proxy callback");
eap_fail(handler);
eap_handler_free(inst, handler);
return RLM_MODULE_REJECT;
}
eap_compose(handler);
if ((handler->eap_ds->request->code == PW_EAP_REQUEST) &&
(handler->eap_ds->request->type.type >= PW_EAP_MD5)) {
if (!eaplist_add(inst, handler)) {
eap_fail(handler);
eap_handler_free(inst, handler);
return RLM_MODULE_FAIL;
}
} else {
RDEBUG2("Freeing handler");
eap_handler_free(inst, handler);
}
if ((request->reply->code == PW_AUTHENTICATION_ACK) &&
request->username) {
vp = pairfind(request->reply->vps, PW_USER_NAME);
if (!vp) {
vp = pairmake("User-Name", request->username->vp_strvalue,
T_OP_EQ);
rad_assert(vp != NULL);
pairadd(&(request->reply->vps), vp);
}
}
return RLM_MODULE_OK;
} else {
RDEBUG2("No pre-existing handler found");
}
vp = request->proxy_reply->vps;
for (;;) {
vp = pairfind(vp, (9 << 16) | 1);
if (!vp) {
return RLM_MODULE_NOOP;
}
if (strncasecmp(vp->vp_strvalue, "leap:session-key=", 17) == 0) {
break;
}
vp = vp->next;
}
if (vp->length != 17 + 34) {
RDEBUG2("Cisco-AVPair with leap:session-key has incorrect length %d: Expected %d",
vp->length, 17 + 34);
return RLM_MODULE_NOOP;
}
i = 34;
len = rad_tunnel_pwdecode(vp->vp_octets + 17, &i,
request->home_server->secret,
request->proxy->vector);
rad_tunnel_pwencode(vp->vp_strvalue + 17, &len,
request->client->secret,
request->packet->vector);
return RLM_MODULE_UPDATED;
}
#endif
module_t rlm_eap = {
RLM_MODULE_INIT,
"eap",
RLM_TYPE_CHECK_CONFIG_SAFE,
eap_instantiate,
eap_detach,
{
eap_authenticate,
eap_authorize,
NULL,
NULL,
NULL,
NULL,
#ifdef WITH_PROXY
eap_post_proxy,
#else
NULL,
#endif
NULL
},
};