*
* 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"
-static const char rcsid[] = "$Id$";
-
/*
* Allocate a new EAP_PACKET
*/
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_HANDLER
*/
-EAP_HANDLER *eap_handler_alloc(void)
+EAP_HANDLER *eap_handler_alloc(rlm_eap_t *inst)
{
EAP_HANDLER *handler;
handler = rad_malloc(sizeof(EAP_HANDLER));
memset(handler, 0, sizeof(EAP_HANDLER));
+
+ if (fr_debug_flag && inst->handler_tree) {
+ pthread_mutex_lock(&(inst->session_mutex));
+ rbtree_insert(inst->handler_tree, handler);
+ pthread_mutex_unlock(&(inst->session_mutex));
+
+ }
return handler;
}
-void eap_handler_free(EAP_HANDLER *handler)
+void eap_handler_free(rlm_eap_t *inst, EAP_HANDLER *handler)
{
if (!handler)
return;
+ if (inst->handler_tree) {
+ pthread_mutex_lock(&(inst->session_mutex));
+ rbtree_deletebydata(inst->handler_tree, handler);
+ pthread_mutex_unlock(&(inst->session_mutex));
+ }
+
if (handler->identity) {
free(handler->identity);
handler->identity = NULL;
handler->opaque = NULL;
handler->free_opaque = 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)
+{
+ check_handler_t *check = data;
+
+ if (!check) return;
+ if (!check->inst || !check->handler) {
+ free(check);
+ return;
+ }
+
+ pthread_mutex_lock(&(check->inst->session_mutex));
+ if (!rbtree_finddata(check->inst->handler_tree, check->handler)) {
+ goto done;
+ }
+
+ if (check->handler->trips > check->trips) {
+ goto done;
+ }
+
+ if (check->handler->tls && !check->handler->finished) {
+ DEBUG("WARNING: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ DEBUG("WARNING: !! EAP session for state 0x%02x%02x%02x%02x%02x%02x%02x%02x did not finish!",
+ check->handler->state[0], check->handler->state[1],
+ check->handler->state[2], check->handler->state[3],
+ check->handler->state[4], check->handler->state[5],
+ check->handler->state[6], check->handler->state[7]);
+
+ DEBUG("WARNING: !! Please read http://wiki.freeradius.org/Certificate_Compatibility");
+ DEBUG("WARNING: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
+ }
+
+done:
+ pthread_mutex_unlock(&(check->inst->session_mutex));
+ free(check);
+}
+
void eaptype_free(EAP_TYPES *i)
{
if (i->type->detach) (i->type->detach)(i->type_data);
i->type_data = NULL;
if (i->handle) lt_dlclose(i->handle);
+ free(i);
}
for (node = inst->session_head; node != NULL; node = next) {
next = node->next;
- eap_handler_free(node);
+ eap_handler_free(inst, node);
}
inst->session_head = inst->session_tail = NULL;
}
/*
+ * Return a 32-bit random number.
+ */
+static uint32_t eap_rand(fr_randctx *ctx)
+{
+ uint32_t num;
+
+ num = ctx->randrsl[ctx->randcnt++];
+ if (ctx->randcnt >= 256) {
+ ctx->randcnt = 0;
+ fr_isaac(ctx);
+ }
+
+ return num;
+}
+
+
+static EAP_HANDLER *eaplist_delete(rlm_eap_t *inst, EAP_HANDLER *handler)
+{
+ rbnode_t *node;
+
+ node = rbtree_find(inst->session_tree, handler);
+ if (!node) return NULL;
+
+ 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);
+ }
+ }
+}
+
+/*
* Add a handler to the set of active sessions.
*
* Since we're adding it to the list, we guess that this means
*/
int eaplist_add(rlm_eap_t *inst, EAP_HANDLER *handler)
{
- int status;
+ int status = 0;
VALUE_PAIR *state;
-
+ REQUEST *request = handler->request;
+
rad_assert(handler != NULL);
- rad_assert(handler->request != NULL);
+ rad_assert(request != NULL);
/*
* Generate State, since we've been asked to add it to
* the list.
*/
- state = generate_state(handler->request->timestamp);
- pairadd(&(handler->request->reply->vps), state);
-
- /*
- * Create a unique 'key' for the handler, based
- * on State, Client-IP-Address, and EAP ID.
- */
- rad_assert(state->length == EAP_STATE_LEN);
+ 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 = handler->request->timestamp;
+ handler->timestamp = request->timestamp;
handler->status = 1;
- memcpy(handler->state, state->strvalue, sizeof(handler->state));
- handler->src_ipaddr = handler->request->packet->src_ipaddr;
+ handler->src_ipaddr = request->packet->src_ipaddr;
handler->eap_id = handler->eap_ds->request->id;
/*
- * We don't need this any more.
- */
- handler->request = NULL;
-
- /*
* 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;
+ }
+
+ /*
+ * 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;
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;
}
}
* 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) {
- radlog(L_ERR, "rlm_eap: Failed to remember handler!");
- eap_handler_free(handler);
+ 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;
}
+ pairadd(&(request->reply->vps), state);
+
return 1;
}
EAP_HANDLER *eaplist_find(rlm_eap_t *inst, REQUEST *request,
eap_packet_t *eap_packet)
{
- int i;
VALUE_PAIR *state;
- rbnode_t *node;
EAP_HANDLER *handler, myHandler;
/*
* We key the sessions off of the 'state' attribute, so it
* must exist.
*/
- state = pairfind(request->packet->vps, PW_STATE);
+ state = pairfind(request->packet->vps, PW_STATE, 0);
if (!state ||
(state->length != EAP_STATE_LEN)) {
return NULL;
myHandler.src_ipaddr = request->packet->src_ipaddr;
myHandler.eap_id = eap_packet->id;
- memcpy(myHandler.state, state->strvalue, sizeof(myHandler.state));
+ memcpy(myHandler.state, state->vp_strvalue, sizeof(myHandler.state));
/*
* Playing with a data structure shared among threads
*/
pthread_mutex_lock(&(inst->session_mutex));
- /*
- * 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 < 2; i++) {
- handler = inst->session_head;
- if (handler &&
- ((request->timestamp - handler->timestamp) > inst->timer_limit)) {
- node = rbtree_find(inst->session_tree, handler);
- rad_assert(node != NULL);
- rbtree_delete(inst->session_tree, node);
-
- inst->session_head = handler->next;
- if (handler->next) handler->next->prev = NULL;
- eap_handler_free(handler);
- }
- }
-
- handler = NULL;
- node = rbtree_find(inst->session_tree, &myHandler);
- if (node) {
- handler = rbtree_node2data(inst->session_tree, node);
-
- /*
- * Check against replays. The client can re-play
- * a State attribute verbatim, so we wish to
- * ensure that the attribute falls within the
- * valid time window, which is the second at
- * which it was sent out.
- *
- * Hmm... I'm not sure that this step is
- * necessary, or even that it does anything.
- */
- if (verify_state(state, handler->timestamp) != 0) {
- handler = NULL;
- } else {
- /*
- * It's OK, delete it 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 = NULL;
- }
- if (handler->next) {
- handler->next->prev = handler->prev;
- } else {
- inst->session_tail = NULL;
- }
- handler->prev = handler->next = NULL;
- }
- }
+ eaplist_expire(inst, request->timestamp);
+ handler = eaplist_delete(inst, &myHandler);
pthread_mutex_unlock(&(inst->session_mutex));
/*
- * Not found.
+ * Might not have been there.
*/
- if (!node) {
- DEBUG2(" rlm_eap: Request not found in the list");
+ if (!handler) {
+ radlog(L_ERR, "rlm_eap: No EAP session matching the State variable.");
return NULL;
}
- /*
- * Found, but state verification failed.
- */
- if (!handler) {
- radlog(L_ERR, "rlm_eap: State verification failed.");
+ if (handler->trips >= 50) {
+ RDEBUG2("More than 50 authentication packets for this EAP session. Aborted.");
+ eap_handler_free(inst, handler);
return NULL;
}
+ handler->trips++;
- DEBUG2(" rlm_eap: Request found, released from the list");
+ 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;
}