*
* 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
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
- * Copyright 2000,2001 The FreeRADIUS server project
+ * Copyright 2000,2001,2006 The FreeRADIUS server project
* Copyright 2001 hereUare Communications, Inc. <raghud@hereuare.com>
*/
+
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
+
#include <stdio.h>
#include "rlm_eap.h"
+#ifdef HAVE_PTHREAD_H
+#define PTHREAD_MUTEX_LOCK pthread_mutex_lock
+#define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
+#else
+#define PTHREAD_MUTEX_LOCK(_x)
+#define PTHREAD_MUTEX_UNLOCK(_x)
+#endif
+
/*
- * Allocate a new EAP_PACKET
+ * Allocate a new EAP_PACKET
*/
EAP_PACKET *eap_packet_alloc(void)
{
}
/*
- * Free EAP_PACKET
+ * Free EAP_PACKET
*/
void eap_packet_free(EAP_PACKET **eap_packet_ptr)
{
if (eap_packet->type.data) {
/*
- * This is just a pointer in the packet
- * so we do not free it but we NULL it
- free(eap_packet->type.data);
- */
+ * There's no packet, OR the type data isn't
+ * pointing inside of the packet: free it.
+ */
+ if ((eap_packet->packet == NULL) ||
+ (eap_packet->type.data != (eap_packet->packet + 5))) {
+ free(eap_packet->type.data);
+ }
eap_packet->type.data = NULL;
}
}
/*
- * Allocate a new EAP_PACKET
+ * Allocate a new EAP_PACKET
*/
EAP_DS *eap_ds_alloc(void)
{
EAP_DS *eap_ds;
-
+
eap_ds = rad_malloc(sizeof(EAP_DS));
memset(eap_ds, 0, sizeof(EAP_DS));
if ((eap_ds->response = eap_packet_alloc()) == NULL) {
EAP_DS *eap_ds;
if (!eap_ds_p) return;
- eap_ds = *eap_ds_p;
- if (!eap_ds) return;
+
+ eap_ds = *eap_ds_p;
+ if (!eap_ds) return;
if (eap_ds->response) eap_packet_free(&(eap_ds->response));
if (eap_ds->request) eap_packet_free(&(eap_ds->request));
}
/*
- * Allocate a new EAP_HANDLER
+ * Allocate a new EAP_HANDLER
*/
-EAP_HANDLER *eap_handler_alloc(void)
+EAP_HANDLER *eap_handler_alloc(rlm_eap_t *inst)
{
EAP_HANDLER *handler;
-
- if ((handler = malloc(sizeof(EAP_HANDLER))) == NULL) {
- radlog(L_ERR, "out of memory");
- return NULL;
- }
+
handler = rad_malloc(sizeof(EAP_HANDLER));
+ memset(handler, 0, sizeof(EAP_HANDLER));
+
+ if (fr_debug_flag && inst->handler_tree) {
+ PTHREAD_MUTEX_LOCK(&(inst->handler_mutex));
+ rbtree_insert(inst->handler_tree, handler);
+ PTHREAD_MUTEX_UNLOCK(&(inst->handler_mutex));
+
+ }
return handler;
}
-void eap_handler_free(EAP_HANDLER **handler_p)
+void eap_handler_free(rlm_eap_t *inst, EAP_HANDLER *handler)
{
- EAP_HANDLER *handler;
-
- if ((handler_p == NULL) || (*handler_p == NULL))
+ if (!handler)
return;
- handler = *handler_p;
- if (handler->id) {
- free(handler->id);
- handler->id = NULL;
+ if (inst->handler_tree) {
+ PTHREAD_MUTEX_LOCK(&(inst->handler_mutex));
+ rbtree_deletebydata(inst->handler_tree, handler);
+ PTHREAD_MUTEX_UNLOCK(&(inst->handler_mutex));
}
if (handler->identity) {
handler->identity = NULL;
}
- if (handler->username) pairfree(&(handler->username));
- if (handler->configured) pairfree(&(handler->configured));
-
if (handler->prev_eapds) eap_ds_free(&(handler->prev_eapds));
if (handler->eap_ds) eap_ds_free(&(handler->eap_ds));
- if ((handler->opaque) && (handler->free_opaque))
- handler->free_opaque(&handler->opaque);
+ if ((handler->opaque) && (handler->free_opaque)) {
+ handler->free_opaque(handler->opaque);
+ handler->opaque = NULL;
+ }
else if ((handler->opaque) && (handler->free_opaque == NULL))
radlog(L_ERR, "Possible memory leak ...");
handler->opaque = NULL;
handler->free_opaque = NULL;
- handler->next = NULL;
- *handler_p = NULL;
+ if (handler->certs) pairfree(&handler->certs);
+
+ free(handler);
+}
+
+
+typedef struct check_handler_t {
+ rlm_eap_t *inst;
+ EAP_HANDLER *handler;
+ int trips;
+} check_handler_t;
+
+static void check_handler(void *data)
+{
+ int do_warning = FALSE;
+ uint8_t state[8];
+ check_handler_t *check = data;
+
+ if (!check) return;
+
+ if (!check->inst || !check->handler) {
+ free(check);
+ return;
+ }
+
+ PTHREAD_MUTEX_LOCK(&(check->inst->handler_mutex));
+ if (!rbtree_finddata(check->inst->handler_tree, check->handler)) {
+ goto done;
+ }
+
+ /*
+ * The session has continued *after* this packet.
+ * Don't do a warning.
+ */
+ if (check->handler->trips > check->trips) {
+ goto done;
+ }
+
+ if (check->handler->tls && !check->handler->finished) {
+ do_warning = TRUE;
+ memcpy(state, check->handler->state, sizeof(state));
+ }
+
+done:
+ PTHREAD_MUTEX_UNLOCK(&(check->inst->handler_mutex));
+ free(check);
+
+ if (do_warning) {
+ DEBUG("WARNING: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ DEBUG("WARNING: !! EAP session for state 0x%02x%02x%02x%02x%02x%02x%02x%02x did not finish!",
+ state[0], state[1],
+ state[2], state[3],
+ state[4], state[5],
+ state[6], state[7]);
+
+ DEBUG("WARNING: !! Please read http://wiki.freeradius.org/Certificate_Compatibility");
+ DEBUG("WARNING: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ }
}
-void eaptype_freelist(EAP_TYPES **i)
+void eaptype_free(EAP_TYPES *i)
{
- EAP_TYPES *c, *next;
-
- c = *i;
- while (c) {
- next = c->next;
- if(c->type->detach) (c->type->detach)(&(c->type_stuff));
- if (c->handle) lt_dlclose(c->handle);
- free(c);
- c = next;
- }
- *i = NULL;
+ if (i->type->detach) (i->type->detach)(i->type_data);
+ i->type_data = NULL;
+ if (i->handle) lt_dlclose(i->handle);
+ free(i);
}
-void eaplist_free(EAP_HANDLER **list)
+
+void eaplist_free(rlm_eap_t *inst)
{
EAP_HANDLER *node, *next;
- if (!list) return;
- node = *list;
- while (node) {
+ for (node = inst->session_head; node != NULL; node = next) {
next = node->next;
- eap_handler_free(&node);
- node = next;
+ eap_handler_free(inst, node);
}
- *list = NULL;
+ inst->session_head = inst->session_tail = NULL;
}
-int eaplist_add(EAP_HANDLER **list, EAP_HANDLER *node)
+/*
+ * Return a 32-bit random number.
+ */
+static uint32_t eap_rand(fr_randctx *ctx)
{
- EAP_HANDLER **last;
+ uint32_t num;
- if (node == NULL) return 0;
-
- last = list;
- while (*last) last = &((*last)->next);
-
- node->timestamp = time(NULL);
- node->status = 1;
- node->next = NULL;
+ num = ctx->randrsl[ctx->randcnt++];
+ if (ctx->randcnt >= 256) {
+ ctx->randcnt = 0;
+ fr_isaac(ctx);
+ }
- *last = node;
- return 1;
+ return num;
}
-/*
- * List should contain only recent packets with life < x seconds.
- */
-void eaplist_clean(EAP_HANDLER **first, time_t limit)
+
+static EAP_HANDLER *eaplist_delete(rlm_eap_t *inst, EAP_HANDLER *handler)
{
- time_t now;
- EAP_HANDLER *node, *next;
- EAP_HANDLER **last = first;
+ rbnode_t *node;
- now = time(NULL);
+ node = rbtree_find(inst->session_tree, handler);
+ if (!node) return NULL;
- for (node = *first; node; node = next) {
- next = node->next;
- if ((now - node->timestamp) > limit) {
- radlog(L_INFO, "rlm_eap: list_clean deleted one item");
- *last = next;
- eap_handler_free(&node);
- } else {
- last = &(node->next);
+ handler = rbtree_node2data(inst->session_tree, node);
+
+ /*
+ * Delete old handler from the tree.
+ */
+ rbtree_delete(inst->session_tree, node);
+
+ /*
+ * And unsplice it from the linked list.
+ */
+ if (handler->prev) {
+ handler->prev->next = handler->next;
+ } else {
+ inst->session_head = handler->next;
+ }
+ if (handler->next) {
+ handler->next->prev = handler->prev;
+ } else {
+ inst->session_tail = handler->prev;
+ }
+ handler->prev = handler->next = NULL;
+
+ return handler;
+}
+
+
+static void eaplist_expire(rlm_eap_t *inst, time_t timestamp)
+{
+ int i;
+ EAP_HANDLER *handler;
+
+ /*
+ * Check the first few handlers in the list, and delete
+ * them if they're too old. We don't need to check them
+ * all, as incoming requests will quickly cause older
+ * handlers to be deleted.
+ *
+ */
+ for (i = 0; i < 3; i++) {
+ handler = inst->session_head;
+ if (!handler) break;
+
+ /*
+ * Expire entries from the start of the list.
+ * They should be the oldest ones.
+ */
+ if ((timestamp - handler->timestamp) > inst->timer_limit) {
+ rbnode_t *node;
+ node = rbtree_find(inst->session_tree, handler);
+ rad_assert(node != NULL);
+ rbtree_delete(inst->session_tree, node);
+
+ /*
+ * handler == inst->session_head
+ */
+ inst->session_head = handler->next;
+ if (handler->next) {
+ handler->next->prev = NULL;
+ } else {
+ inst->session_head = NULL;
+ inst->session_tail = NULL;
+ }
+ eap_handler_free(inst, handler);
}
}
}
/*
- * If the present EAP-Response is a reply to the previous
- * EAP-Request sent by us, then return the EAP_HANDLER
- * only after releasing from the eaplist
- * Also since we fill the eap_ds with the present EAP-Response
- * we got to free the prev_eapds & move the eap_ds to prev_eapds
+ * Add a handler to the set of active sessions.
+ *
+ * Since we're adding it to the list, we guess that this means
+ * the packet needs a State attribute. So add one.
*/
-EAP_HANDLER *eaplist_isreply(EAP_HANDLER **first, unsigned char id[])
+int eaplist_add(rlm_eap_t *inst, EAP_HANDLER *handler)
{
- EAP_HANDLER *node, *next, *ret = NULL;
- EAP_HANDLER **last = first;
+ int status = 0;
+ VALUE_PAIR *state;
+ REQUEST *request = handler->request;
+
+ rad_assert(handler != NULL);
+ rad_assert(request != NULL);
+
+ /*
+ * Generate State, since we've been asked to add it to
+ * the list.
+ */
+ state = pairmake("State", "0x00", T_OP_EQ);
+ if (!state) return 0;
+
+ /*
+ * The time at which this request was made was the time
+ * at which it was received by the RADIUS server.
+ */
+ handler->timestamp = request->timestamp;
+ handler->status = 1;
+
+ handler->src_ipaddr = request->packet->src_ipaddr;
+ handler->eap_id = handler->eap_ds->request->id;
+
+ /*
+ * Playing with a data structure shared among threads
+ * means that we need a lock, to avoid conflict.
+ */
+ PTHREAD_MUTEX_LOCK(&(inst->session_mutex));
+
+ /*
+ * If we have a DoS attack, discard new sessions.
+ */
+ if (rbtree_num_elements(inst->session_tree) >= inst->max_sessions) {
+ status = -1;
+ eaplist_expire(inst, handler->timestamp);
+ goto done;
+ }
- for (node = *first; node; node = next) {
- next = node->next;
- if (memcmp(node->id, id, id[0]) == 0) {
- radlog(L_INFO, "rlm_eap: Request found, released from the list");
- /* detach the node from the list */
- *last = next;
- node->next = NULL;
-
- /* clean up the unwanted stuff before returning */
- eap_ds_free(&(node->prev_eapds));
- node->prev_eapds = node->eap_ds;
- node->eap_ds = NULL;
-
- ret = node;
- break;
- } else {
- last = &(node->next);
+ /*
+ * Create a unique content for the State variable.
+ * It will be modified slightly per round trip, but less so
+ * than in 1.x.
+ */
+ if (handler->trips == 0) {
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ uint32_t lvalue;
+
+ lvalue = eap_rand(&inst->rand_pool);
+
+ memcpy(handler->state + i * 4, &lvalue,
+ sizeof(lvalue));
+ }
+ }
+
+ memcpy(state->vp_octets, handler->state, sizeof(handler->state));
+ state->length = EAP_STATE_LEN;
+
+ /*
+ * Add some more data to distinguish the sessions.
+ */
+ state->vp_octets[4] = handler->trips ^ handler->state[0];
+ state->vp_octets[5] = handler->eap_id ^ handler->state[1];
+ state->vp_octets[6] = handler->eap_type ^ handler->state[2];
+
+ /*
+ * and copy the state back again.
+ */
+ memcpy(handler->state, state->vp_octets, sizeof(handler->state));
+
+ /*
+ * Big-time failure.
+ */
+ status = rbtree_insert(inst->session_tree, handler);
+
+ /*
+ * Catch Access-Challenge without response.
+ */
+ if (fr_debug_flag) {
+ check_handler_t *check = rad_malloc(sizeof(*check));
+
+ check->inst = inst;
+ check->handler = handler;
+ check->trips = handler->trips;
+ request_data_add(request, inst, 0, check, check_handler);
+ }
+
+ if (status) {
+ EAP_HANDLER *prev;
+
+ prev = inst->session_tail;
+ if (prev) {
+ prev->next = handler;
+ handler->prev = prev;
+ handler->next = NULL;
+ inst->session_tail = handler;
+ } else {
+ inst->session_head = inst->session_tail = handler;
+ handler->next = handler->prev = NULL;
}
}
- if (!ret) {
- radlog(L_INFO, "rlm_eap: Request not found in the list");
+ /*
+ * Now that we've finished mucking with the list,
+ * unlock it.
+ */
+ done:
+
+ /*
+ * We don't need this any more.
+ */
+ if (status > 0) handler->request = NULL;
+
+ PTHREAD_MUTEX_UNLOCK(&(inst->session_mutex));
+
+ if (status <= 0) {
+ pairfree(&state);
+
+ if (status < 0) {
+ static time_t last_logged = 0;
+
+ if (last_logged < handler->timestamp) {
+ last_logged = handler->timestamp;
+ radlog(L_ERR, "rlm_eap: Too many open sessions. Try increasing \"max_sessions\" in the EAP module configuration");
+ }
+ } else {
+ radlog(L_ERR, "rlm_eap: Internal error: failed to store handler");
+ }
+ return 0;
}
- return ret;
+
+ pairadd(&(request->reply->vps), state);
+
+ return 1;
}
-EAP_HANDLER *eaplist_findhandler(EAP_HANDLER *list, unsigned char id[])
+/*
+ * Find a a previous EAP-Request sent by us, which matches
+ * the current EAP-Response.
+ *
+ * Then, release the handle from the list, and return it to
+ * the caller.
+ *
+ * Also since we fill the eap_ds with the present EAP-Response we
+ * got to free the prev_eapds & move the eap_ds to prev_eapds
+ */
+EAP_HANDLER *eaplist_find(rlm_eap_t *inst, REQUEST *request,
+ eap_packet_t *eap_packet)
{
- EAP_HANDLER *node;
- node = list;
-
- while (node) {
- /*
- * Match is identified by the same IDs
- */
- if (memcmp(node->id, id, id[0]) == 0) {
- radlog(L_INFO, "rlm_eap: EAP Handler found in the list ");
- return node;
- }
- node = node->next;
+ VALUE_PAIR *state;
+ EAP_HANDLER *handler, myHandler;
+
+ /*
+ * We key the sessions off of the 'state' attribute, so it
+ * must exist.
+ */
+ state = pairfind(request->packet->vps, PW_STATE, 0);
+ if (!state ||
+ (state->length != EAP_STATE_LEN)) {
+ return NULL;
}
- return NULL;
+
+ myHandler.src_ipaddr = request->packet->src_ipaddr;
+ myHandler.eap_id = eap_packet->id;
+ memcpy(myHandler.state, state->vp_strvalue, sizeof(myHandler.state));
+
+ /*
+ * Playing with a data structure shared among threads
+ * means that we need a lock, to avoid conflict.
+ */
+ PTHREAD_MUTEX_LOCK(&(inst->session_mutex));
+
+ eaplist_expire(inst, request->timestamp);
+
+ handler = eaplist_delete(inst, &myHandler);
+ PTHREAD_MUTEX_UNLOCK(&(inst->session_mutex));
+
+ /*
+ * Might not have been there.
+ */
+ if (!handler) {
+ radlog(L_ERR, "rlm_eap: No EAP session matching the State variable.");
+ return NULL;
+ }
+
+ if (handler->trips >= 50) {
+ RDEBUG2("More than 50 authentication packets for this EAP session. Aborted.");
+ eap_handler_free(inst, handler);
+ return NULL;
+ }
+ handler->trips++;
+
+ RDEBUG2("Request found, released from the list");
+
+ /*
+ * Remember what the previous request was.
+ */
+ eap_ds_free(&(handler->prev_eapds));
+ handler->prev_eapds = handler->eap_ds;
+ handler->eap_ds = NULL;
+
+ return handler;
}