Fixes to build without PTHREADs
[freeradius.git] / src / modules / rlm_eap / mem.c
index bc40ba1..56bf35c 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"
 
+#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)
 {
@@ -36,7 +48,7 @@ EAP_PACKET *eap_packet_alloc(void)
 }
 
 /*
- *      Free EAP_PACKET
+ * Free EAP_PACKET
  */
 void eap_packet_free(EAP_PACKET **eap_packet_ptr)
 {
@@ -48,10 +60,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;
        }
 
@@ -66,12 +81,12 @@ void eap_packet_free(EAP_PACKET **eap_packet_ptr)
 }
 
 /*
- *      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) {
@@ -91,8 +106,9 @@ void eap_ds_free(EAP_DS **eap_ds_p)
        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));
@@ -102,31 +118,33 @@ void eap_ds_free(EAP_DS **eap_ds_p)
 }
 
 /*
- *      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) {
@@ -134,146 +152,401 @@ void eap_handler_free(EAP_HANDLER **handler_p)
                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;
 }