Use handler mutex for checks, not session mutex
[freeradius.git] / src / modules / rlm_eap / mem.c
index c38624c..5f6d6d8 100644 (file)
  *
  *   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
  */
@@ -50,10 +52,13 @@ 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;
        }
 
@@ -73,7 +78,7 @@ void eap_packet_free(EAP_PACKET **eap_packet_ptr)
 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) {
@@ -107,23 +112,33 @@ void eap_ds_free(EAP_DS **eap_ds_p)
 /*
  * 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->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 (inst->handler_tree) {
+               pthread_mutex_lock(&(inst->handler_mutex));
+               rbtree_deletebydata(inst->handler_tree, handler);
+               pthread_mutex_unlock(&(inst->handler_mutex));
+       }
+
        if (handler->identity) {
                free(handler->identity);
                handler->identity = NULL;
@@ -141,10 +156,65 @@ void eap_handler_free(EAP_HANDLER **handler_p)
 
        handler->opaque = NULL;
        handler->free_opaque = NULL;
-       handler->next = NULL;
+
+       if (handler->certs) pairfree(&handler->certs);
 
        free(handler);
-       *handler_p = NULL;
+}
+
+
+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_free(EAP_TYPES *i)
@@ -152,29 +222,110 @@ 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);
 }
 
+
 void eaplist_free(rlm_eap_t *inst)
 {
+       EAP_HANDLER *node, *next;
+
+               for (node = inst->session_head; node != NULL; node = next) {
+               next = node->next;
+               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;
 
        /*
-        *      The sessions are split out into an array, which makes
-        *      looking them up a bit faster.
+        *      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 < 256; i++) {
-               EAP_HANDLER *node, *next;
+       for (i = 0; i < 3; i++) {
+               handler = inst->session_head;
+               if (!handler) break;
 
-               if (inst->sessions[i]) continue;
+               /*
+                *      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);
 
-               node = inst->sessions[i];
-               while (node) {
-                       next = node->next;
-                       eap_handler_free(&node);
-                       node = next;
+                       /*
+                        *      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);
                }
-               
-               inst->sessions[i] = NULL;
        }
 }
 
@@ -186,65 +337,140 @@ void eaplist_free(rlm_eap_t *inst)
  */
 int eaplist_add(rlm_eap_t *inst, EAP_HANDLER *handler)
 {
-       EAP_HANDLER     **last;
+       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);
-               
+       state = pairmake("State", "0x00", T_OP_EQ);
+       if (!state) return 0;
+
        /*
-        *      Create a unique 'key' for the handler, based
-        *      on State, Client-IP-Address, and EAP ID.
+        *      The time at which this request was made was the time
+        *      at which it was received by the RADIUS server.
         */
-       rad_assert(state->length == EAP_STATE_LEN);
+       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;
 
-#if HAVE_PTHREAD_H
        /*
         *      Playing with a data structure shared among threads
         *      means that we need a lock, to avoid conflict.
         */
        pthread_mutex_lock(&(inst->session_mutex));
-#endif
 
        /*
-        *      We key the array based on the challenge, which is
-        *      a random number.  This "fans out" the sessions, and
-        *      helps to minimize the amount of work we've got to do
-        *      under heavy load.
+        *      If we have a DoS attack, discard new sessions.
         */
-       last = &(inst->sessions[state->strvalue[0]]);
+       if (rbtree_num_elements(inst->session_tree) >= inst->max_sessions) {
+               status = -1;
+               eaplist_expire(inst, handler->timestamp);
+               goto done;
+       }
 
-       while (*last) last = &((*last)->next);
-       
-       *last = handler;
+       /*
+        *      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;
 
-#if HAVE_PTHREAD_H
-       pthread_mutex_unlock(&(inst->session_mutex));
-#endif
+               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;
 
        /*
-        *      The time at which this request was made was the time
-        *      at which it was received by the RADIUS server.
+        *      Add some more data to distinguish the sessions.
         */
-       handler->timestamp = handler->request->timestamp;
-       handler->status = 1;
-       handler->next = NULL;
+       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;
+               }
+       }
+
+       /*
+        *      Now that we've finished mucking with the list,
+        *      unlock it.
+        */
+ done:
 
        /*
         *      We don't need this any more.
         */
-       handler->request = NULL;
+       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;
+       }
+
+       pairadd(&(request->reply->vps), state);
 
        return 1;
 }
@@ -262,105 +488,57 @@ int eaplist_add(rlm_eap_t *inst, EAP_HANDLER *handler)
 EAP_HANDLER *eaplist_find(rlm_eap_t *inst, REQUEST *request,
                          eap_packet_t *eap_packet)
 {
-       EAP_HANDLER     *node, *next;
        VALUE_PAIR      *state;
-       EAP_HANDLER     **first,  **last;
+       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;
        }
 
-#if HAVE_PTHREAD_H
+       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));
-#endif
-
-       last = first = &(inst->sessions[state->strvalue[0]]);
 
-       for (node = *first; node; node = next) {
-               next = node->next;
-
-               /*
-                *      If the time on this entry has expired, 
-                *      delete it.  We do this while walking the list,
-                *      in order to spread out the work of deleting old
-                *      sessions.
-                */
-               if ((request->timestamp - node->timestamp) > inst->timer_limit) {
-                       *last = next;
-                       eap_handler_free(&node);
-                       continue;
-               }
+       eaplist_expire(inst, request->timestamp);
 
-               /*
-                *      Find the previous part of the same conversation,
-                *      keying off of the EAP ID, the client IP, and
-                *      the State attribute.
-                *
-                *      If we've found a conversation, then we don't
-                *      have to check entries later in the list for
-                *      timeout, as they're guaranteed to be newer than
-                *      the one we found.
-                */
-               if ((node->eap_id == eap_packet->id) &&
-                   (node->src_ipaddr == request->packet->src_ipaddr) &&
-                   (memcmp(node->state, state->strvalue, state->length) == 0)) {
-                       /*
-                        *      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.
-                        */
-                       if (verify_state(state, node->timestamp) != 0) {
-                               radlog(L_ERR, "rlm_eap: State verification failed.");
-                               node = NULL;
-                               break;
-                       }
-                       
-                       DEBUG2("  rlm_eap: Request found, released from the list");
-                       /*
-                        *      detach the node from the list
-                        */
-                       *last = next;
-                       node->next = NULL;
-
-                       /*
-                        *      Don't bother updating handler->request, etc.
-                        *      eap_handler() will do that for us.
-                        */
+       handler = eaplist_delete(inst, &myHandler);
+       pthread_mutex_unlock(&(inst->session_mutex));
 
-                       /*
-                        *      Remember what the previous request was.
-                        */
-                       eap_ds_free(&(node->prev_eapds));
-                       node->prev_eapds = node->eap_ds;
-                       node->eap_ds = NULL;
+       /*
+        *      Might not have been there.
+        */
+       if (!handler) {
+               radlog(L_ERR, "rlm_eap: No EAP session matching the State variable.");
+               return NULL;
+       }
 
-                       /*
-                        *      Stop here.
-                        */
-                       break;
-               } else  {
-                       last = &(node->next);
-               }
+       if (handler->trips >= 50) {
+               RDEBUG2("More than 50 authentication packets for this EAP session.  Aborted.");
+               eap_handler_free(inst, handler);
+               return NULL;
        }
+       handler->trips++;
 
-#if HAVE_PTHREAD_H
-       pthread_mutex_unlock(&(inst->session_mutex));
-#endif
+       RDEBUG2("Request found, released from the list");
 
-       if (!node) {
-               DEBUG2("  rlm_eap: Request not found in the list");
-       }
-       return node;
+       /*
+        *      Remember what the previous request was.
+        */
+       eap_ds_free(&(handler->prev_eapds));
+       handler->prev_eapds = handler->eap_ds;
+       handler->eap_ds = NULL;
+
+       return handler;
 }