Use handler mutex for checks, not session mutex
[freeradius.git] / src / modules / rlm_eap / mem.c
1 /*
2  * mem.c  Memory allocation, deallocation stuff.
3  *
4  * Version:     $Id$
5  *
6  *   This program is free software; you can redistribute it and/or modify
7  *   it under the terms of the GNU General Public License as published by
8  *   the Free Software Foundation; either version 2 of the License, or
9  *   (at your option) any later version.
10  *
11  *   This program is distributed in the hope that it will be useful,
12  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *   GNU General Public License for more details.
15  *
16  *   You should have received a copy of the GNU General Public License
17  *   along with this program; if not, write to the Free Software
18  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19  *
20  * Copyright 2000,2001,2006  The FreeRADIUS server project
21  * Copyright 2001  hereUare Communications, Inc. <raghud@hereuare.com>
22  */
23
24 #include <freeradius-devel/ident.h>
25 RCSID("$Id$")
26
27 #include <stdio.h>
28 #include "rlm_eap.h"
29
30 /*
31  * Allocate a new EAP_PACKET
32  */
33 EAP_PACKET *eap_packet_alloc(void)
34 {
35         EAP_PACKET   *rp;
36
37         rp = rad_malloc(sizeof(EAP_PACKET));
38         memset(rp, 0, sizeof(EAP_PACKET));
39         return rp;
40 }
41
42 /*
43  * Free EAP_PACKET
44  */
45 void eap_packet_free(EAP_PACKET **eap_packet_ptr)
46 {
47         EAP_PACKET *eap_packet;
48
49         if (!eap_packet_ptr) return;
50         eap_packet = *eap_packet_ptr;
51         if (!eap_packet) return;
52
53         if (eap_packet->type.data) {
54                 /*
55                  *      There's no packet, OR the type data isn't
56                  *      pointing inside of the packet: free it.
57                  */
58                 if ((eap_packet->packet == NULL) ||
59                     (eap_packet->type.data != (eap_packet->packet + 5))) {
60                         free(eap_packet->type.data);
61                 }
62                 eap_packet->type.data = NULL;
63         }
64
65         if (eap_packet->packet) {
66                 free(eap_packet->packet);
67                 eap_packet->packet = NULL;
68         }
69
70         free(eap_packet);
71
72         *eap_packet_ptr = NULL;
73 }
74
75 /*
76  * Allocate a new EAP_PACKET
77  */
78 EAP_DS *eap_ds_alloc(void)
79 {
80         EAP_DS  *eap_ds;
81
82         eap_ds = rad_malloc(sizeof(EAP_DS));
83         memset(eap_ds, 0, sizeof(EAP_DS));
84         if ((eap_ds->response = eap_packet_alloc()) == NULL) {
85                 eap_ds_free(&eap_ds);
86                 return NULL;
87         }
88         if ((eap_ds->request = eap_packet_alloc()) == NULL) {
89                 eap_ds_free(&eap_ds);
90                 return NULL;
91         }
92
93         return eap_ds;
94 }
95
96 void eap_ds_free(EAP_DS **eap_ds_p)
97 {
98         EAP_DS *eap_ds;
99
100         if (!eap_ds_p) return;
101
102         eap_ds = *eap_ds_p;
103         if (!eap_ds) return;
104
105         if (eap_ds->response) eap_packet_free(&(eap_ds->response));
106         if (eap_ds->request) eap_packet_free(&(eap_ds->request));
107
108         free(eap_ds);
109         *eap_ds_p = NULL;
110 }
111
112 /*
113  * Allocate a new EAP_HANDLER
114  */
115 EAP_HANDLER *eap_handler_alloc(rlm_eap_t *inst)
116 {
117         EAP_HANDLER     *handler;
118
119         handler = rad_malloc(sizeof(EAP_HANDLER));
120         memset(handler, 0, sizeof(EAP_HANDLER));
121
122         if (fr_debug_flag && inst->handler_tree) {
123                 pthread_mutex_lock(&(inst->handler_mutex));
124                 rbtree_insert(inst->handler_tree, handler);
125                 pthread_mutex_unlock(&(inst->handler_mutex));
126         
127         }
128         return handler;
129 }
130
131 void eap_handler_free(rlm_eap_t *inst, EAP_HANDLER *handler)
132 {
133         if (!handler)
134                 return;
135
136         if (inst->handler_tree) {
137                 pthread_mutex_lock(&(inst->handler_mutex));
138                 rbtree_deletebydata(inst->handler_tree, handler);
139                 pthread_mutex_unlock(&(inst->handler_mutex));
140         }
141
142         if (handler->identity) {
143                 free(handler->identity);
144                 handler->identity = NULL;
145         }
146
147         if (handler->prev_eapds) eap_ds_free(&(handler->prev_eapds));
148         if (handler->eap_ds) eap_ds_free(&(handler->eap_ds));
149
150         if ((handler->opaque) && (handler->free_opaque)) {
151                 handler->free_opaque(handler->opaque);
152                 handler->opaque = NULL;
153         }
154         else if ((handler->opaque) && (handler->free_opaque == NULL))
155                 radlog(L_ERR, "Possible memory leak ...");
156
157         handler->opaque = NULL;
158         handler->free_opaque = NULL;
159
160         if (handler->certs) pairfree(&handler->certs);
161
162         free(handler);
163 }
164
165
166 typedef struct check_handler_t {
167         rlm_eap_t       *inst;
168         EAP_HANDLER     *handler;
169         int             trips;
170 } check_handler_t;
171
172 static void check_handler(void *data)
173 {
174         int do_warning = FALSE;
175         uint8_t state[8];
176         check_handler_t *check = data;
177
178         if (!check) return;
179
180         if (!check->inst || !check->handler) {
181                 free(check);
182                 return;
183         }
184
185         pthread_mutex_lock(&(check->inst->handler_mutex));
186         if (!rbtree_finddata(check->inst->handler_tree, check->handler)) {
187                 goto done;
188         }
189
190         /*
191          *      The session has continued *after* this packet.
192          *      Don't do a warning.
193          */
194         if (check->handler->trips > check->trips) {
195                 goto done;
196         }
197
198         if (check->handler->tls && !check->handler->finished) {
199                 do_warning = TRUE;
200                 memcpy(state, check->handler->state, sizeof(state));
201         }
202
203 done:
204         pthread_mutex_unlock(&(check->inst->handler_mutex));
205         free(check);
206
207         if (do_warning) {
208                 DEBUG("WARNING: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
209                 DEBUG("WARNING: !! EAP session for state 0x%02x%02x%02x%02x%02x%02x%02x%02x did not finish!",
210                       state[0], state[1],
211                       state[2], state[3],
212                       state[4], state[5],
213                       state[6], state[7]);
214
215                 DEBUG("WARNING: !! Please read http://wiki.freeradius.org/Certificate_Compatibility");
216                 DEBUG("WARNING: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
217         }
218 }
219
220 void eaptype_free(EAP_TYPES *i)
221 {
222         if (i->type->detach) (i->type->detach)(i->type_data);
223         i->type_data = NULL;
224         if (i->handle) lt_dlclose(i->handle);
225         free(i);
226 }
227
228
229 void eaplist_free(rlm_eap_t *inst)
230 {
231         EAP_HANDLER *node, *next;
232
233         for (node = inst->session_head; node != NULL; node = next) {
234                 next = node->next;
235                 eap_handler_free(inst, node);
236         }
237
238         inst->session_head = inst->session_tail = NULL;
239 }
240
241 /*
242  *      Return a 32-bit random number.
243  */
244 static uint32_t eap_rand(fr_randctx *ctx)
245 {
246         uint32_t num;
247
248         num = ctx->randrsl[ctx->randcnt++];
249         if (ctx->randcnt >= 256) {
250                 ctx->randcnt = 0;
251                 fr_isaac(ctx);
252         }
253
254         return num;
255 }
256
257
258 static EAP_HANDLER *eaplist_delete(rlm_eap_t *inst, EAP_HANDLER *handler)
259 {
260         rbnode_t *node;
261
262         node = rbtree_find(inst->session_tree, handler);
263         if (!node) return NULL;
264
265         handler = rbtree_node2data(inst->session_tree, node);
266
267         /*
268          *      Delete old handler from the tree.
269          */
270         rbtree_delete(inst->session_tree, node);
271         
272         /*
273          *      And unsplice it from the linked list.
274          */
275         if (handler->prev) {
276                 handler->prev->next = handler->next;
277         } else {
278                 inst->session_head = handler->next;
279         }
280         if (handler->next) {
281                 handler->next->prev = handler->prev;
282         } else {
283                 inst->session_tail = handler->prev;
284         }
285         handler->prev = handler->next = NULL;
286
287         return handler;
288 }
289
290
291 static void eaplist_expire(rlm_eap_t *inst, time_t timestamp)
292 {
293         int i;
294         EAP_HANDLER *handler;
295
296         /*
297          *      Check the first few handlers in the list, and delete
298          *      them if they're too old.  We don't need to check them
299          *      all, as incoming requests will quickly cause older
300          *      handlers to be deleted.
301          *
302          */
303         for (i = 0; i < 3; i++) {
304                 handler = inst->session_head;
305                 if (!handler) break;
306
307                 /*
308                  *      Expire entries from the start of the list.
309                  *      They should be the oldest ones.
310                  */
311                 if ((timestamp - handler->timestamp) > inst->timer_limit) {
312                         rbnode_t *node;
313                         node = rbtree_find(inst->session_tree, handler);
314                         rad_assert(node != NULL);
315                         rbtree_delete(inst->session_tree, node);
316
317                         /*
318                          *      handler == inst->session_head
319                          */
320                         inst->session_head = handler->next;
321                         if (handler->next) {
322                                 handler->next->prev = NULL;
323                         } else {
324                                 inst->session_head = NULL;
325                                 inst->session_tail = NULL;
326                         }
327                         eap_handler_free(inst, handler);
328                 }
329         }
330 }
331
332 /*
333  *      Add a handler to the set of active sessions.
334  *
335  *      Since we're adding it to the list, we guess that this means
336  *      the packet needs a State attribute.  So add one.
337  */
338 int eaplist_add(rlm_eap_t *inst, EAP_HANDLER *handler)
339 {
340         int             status = 0;
341         VALUE_PAIR      *state;
342         REQUEST         *request = handler->request;
343
344         rad_assert(handler != NULL);
345         rad_assert(request != NULL);
346
347         /*
348          *      Generate State, since we've been asked to add it to
349          *      the list.
350          */
351         state = pairmake("State", "0x00", T_OP_EQ);
352         if (!state) return 0;
353
354         /*
355          *      The time at which this request was made was the time
356          *      at which it was received by the RADIUS server.
357          */
358         handler->timestamp = request->timestamp;
359         handler->status = 1;
360
361         handler->src_ipaddr = request->packet->src_ipaddr;
362         handler->eap_id = handler->eap_ds->request->id;
363
364         /*
365          *      Playing with a data structure shared among threads
366          *      means that we need a lock, to avoid conflict.
367          */
368         pthread_mutex_lock(&(inst->session_mutex));
369
370         /*
371          *      If we have a DoS attack, discard new sessions.
372          */
373         if (rbtree_num_elements(inst->session_tree) >= inst->max_sessions) {
374                 status = -1;
375                 eaplist_expire(inst, handler->timestamp);
376                 goto done;
377         }
378
379         /*
380          *      Create a unique content for the State variable.
381          *      It will be modified slightly per round trip, but less so
382          *      than in 1.x.
383          */
384         if (handler->trips == 0) {
385                 int i;
386
387                 for (i = 0; i < 4; i++) {
388                         uint32_t lvalue;
389
390                         lvalue = eap_rand(&inst->rand_pool);
391
392                         memcpy(handler->state + i * 4, &lvalue,
393                                sizeof(lvalue));
394                 }               
395         }
396
397         memcpy(state->vp_octets, handler->state, sizeof(handler->state));
398         state->length = EAP_STATE_LEN;
399
400         /*
401          *      Add some more data to distinguish the sessions.
402          */
403         state->vp_octets[4] = handler->trips ^ handler->state[0];
404         state->vp_octets[5] = handler->eap_id ^ handler->state[1];
405         state->vp_octets[6] = handler->eap_type ^ handler->state[2];
406
407         /*
408          *      and copy the state back again.
409          */
410         memcpy(handler->state, state->vp_octets, sizeof(handler->state));
411
412         /*
413          *      Big-time failure.
414          */
415         status = rbtree_insert(inst->session_tree, handler);
416
417         /*
418          *      Catch Access-Challenge without response.
419          */
420         if (fr_debug_flag) {
421                 check_handler_t *check = rad_malloc(sizeof(*check));
422
423                 check->inst = inst;
424                 check->handler = handler;
425                 check->trips = handler->trips;
426                 request_data_add(request, inst, 0, check, check_handler);
427         }
428
429         if (status) {
430                 EAP_HANDLER *prev;
431
432                 prev = inst->session_tail;
433                 if (prev) {
434                         prev->next = handler;
435                         handler->prev = prev;
436                         handler->next = NULL;
437                         inst->session_tail = handler;
438                 } else {
439                         inst->session_head = inst->session_tail = handler;
440                         handler->next = handler->prev = NULL;
441                 }
442         }
443
444         /*
445          *      Now that we've finished mucking with the list,
446          *      unlock it.
447          */
448  done:
449
450         /*
451          *      We don't need this any more.
452          */
453         if (status > 0) handler->request = NULL;
454
455         pthread_mutex_unlock(&(inst->session_mutex));
456
457         if (status <= 0) {
458                 pairfree(&state);
459
460                 if (status < 0) {
461                         static time_t last_logged = 0;
462
463                         if (last_logged < handler->timestamp) {
464                                 last_logged = handler->timestamp;
465                                 radlog(L_ERR, "rlm_eap: Too many open sessions.  Try increasing \"max_sessions\" in the EAP module configuration");
466                         }                                      
467                 } else {
468                         radlog(L_ERR, "rlm_eap: Internal error: failed to store handler");
469                 }
470                 return 0;
471         }
472
473         pairadd(&(request->reply->vps), state);
474
475         return 1;
476 }
477
478 /*
479  *      Find a a previous EAP-Request sent by us, which matches
480  *      the current EAP-Response.
481  *
482  *      Then, release the handle from the list, and return it to
483  *      the caller.
484  *
485  *      Also since we fill the eap_ds with the present EAP-Response we
486  *      got to free the prev_eapds & move the eap_ds to prev_eapds
487  */
488 EAP_HANDLER *eaplist_find(rlm_eap_t *inst, REQUEST *request,
489                           eap_packet_t *eap_packet)
490 {
491         VALUE_PAIR      *state;
492         EAP_HANDLER     *handler, myHandler;
493
494         /*
495          *      We key the sessions off of the 'state' attribute, so it
496          *      must exist.
497          */
498         state = pairfind(request->packet->vps, PW_STATE, 0);
499         if (!state ||
500             (state->length != EAP_STATE_LEN)) {
501                 return NULL;
502         }
503
504         myHandler.src_ipaddr = request->packet->src_ipaddr;
505         myHandler.eap_id = eap_packet->id;
506         memcpy(myHandler.state, state->vp_strvalue, sizeof(myHandler.state));
507
508         /*
509          *      Playing with a data structure shared among threads
510          *      means that we need a lock, to avoid conflict.
511          */
512         pthread_mutex_lock(&(inst->session_mutex));
513
514         eaplist_expire(inst, request->timestamp);
515
516         handler = eaplist_delete(inst, &myHandler);
517         pthread_mutex_unlock(&(inst->session_mutex));
518
519         /*
520          *      Might not have been there.
521          */
522         if (!handler) {
523                 radlog(L_ERR, "rlm_eap: No EAP session matching the State variable.");
524                 return NULL;
525         }
526
527         if (handler->trips >= 50) {
528                 RDEBUG2("More than 50 authentication packets for this EAP session.  Aborted.");
529                 eap_handler_free(inst, handler);
530                 return NULL;
531         }
532         handler->trips++;
533
534         RDEBUG2("Request found, released from the list");
535
536         /*
537          *      Remember what the previous request was.
538          */
539         eap_ds_free(&(handler->prev_eapds));
540         handler->prev_eapds = handler->eap_ds;
541         handler->eap_ds = NULL;
542
543         return handler;
544 }