6fda268d6b717e01b2d7b84521796b8b743a7b41
[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 RCSID("$Id$")
25
26 #include <stdio.h>
27 #include "rlm_eap.h"
28
29 #ifdef HAVE_PTHREAD_H
30 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
31 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
32 #else
33 #define PTHREAD_MUTEX_LOCK(_x)
34 #define PTHREAD_MUTEX_UNLOCK(_x)
35 #endif
36
37 /*
38  * Allocate a new eap_packet_t
39  */
40 EAP_DS *eap_ds_alloc(eap_handler_t *handler)
41 {
42         EAP_DS  *eap_ds;
43
44         eap_ds = talloc_zero(handler, EAP_DS);
45         eap_ds->response = talloc_zero(eap_ds, eap_packet_t);
46         if (!eap_ds->response) {
47                 eap_ds_free(&eap_ds);
48                 return NULL;
49         }
50         eap_ds->request = talloc_zero(eap_ds, eap_packet_t);
51         if (!eap_ds->response) {
52                 eap_ds_free(&eap_ds);
53                 return NULL;
54         }
55
56         return eap_ds;
57 }
58
59 void eap_ds_free(EAP_DS **eap_ds_p)
60 {
61         EAP_DS *eap_ds;
62
63         if (!eap_ds_p) return;
64
65         eap_ds = *eap_ds_p;
66         if (!eap_ds) return;
67
68         if (eap_ds->response) talloc_free(eap_ds->response);
69         if (eap_ds->request) talloc_free(eap_ds->request);
70
71         talloc_free(eap_ds);
72         *eap_ds_p = NULL;
73 }
74
75 static int _eap_handler_free(eap_handler_t *handler)
76 {
77         if (handler->identity) {
78                 talloc_free(handler->identity);
79                 handler->identity = NULL;
80         }
81
82         if (handler->prev_eapds) eap_ds_free(&(handler->prev_eapds));
83         if (handler->eap_ds) eap_ds_free(&(handler->eap_ds));
84
85         if ((handler->opaque) && (handler->free_opaque)) {
86                 handler->free_opaque(handler->opaque);
87                 handler->opaque = NULL;
88         }
89
90         handler->opaque = NULL;
91         handler->free_opaque = NULL;
92
93         if (handler->certs) fr_pair_list_free(&handler->certs);
94
95         /*
96          *      Give helpful debug messages if:
97          *
98          *      we're debugging TLS sessions, which don't finish,
99          *      and which aren't deleted early due to a likely RADIUS
100          *      retransmit which nukes our ID, and therefore our stare.
101          */
102         if (fr_debug_lvl && handler->tls && !handler->finished &&
103             (time(NULL) > (handler->timestamp + 3))) {
104                 WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
105                 WARN("!! EAP session with state 0x%02x%02x%02x%02x%02x%02x%02x%02x did not finish!                  !!",
106                      handler->state[0], handler->state[1],
107                      handler->state[2], handler->state[3],
108                      handler->state[4], handler->state[5],
109                      handler->state[6], handler->state[7]);
110
111                 WARN("!! Please read http://wiki.freeradius.org/guide/Certificate_Compatibility     !!");
112                 WARN("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
113         }
114         
115         return 0;
116 }
117
118 /*
119  * Allocate a new eap_handler_t
120  */
121 eap_handler_t *eap_handler_alloc(rlm_eap_t *inst)
122 {
123         eap_handler_t   *handler;
124
125         handler = talloc_zero(NULL, eap_handler_t);
126         if (!handler) {
127                 ERROR("Failed allocating handler");
128                 return NULL;
129         }
130         handler->inst_holder = inst;
131
132         /* Doesn't need to be inside the critical region */
133         talloc_set_destructor(handler, _eap_handler_free);
134
135         return handler;
136 }
137
138
139 void eaplist_free(rlm_eap_t *inst)
140 {
141         eap_handler_t *node, *next;
142
143         for (node = inst->session_head; node != NULL; node = next) {
144                 next = node->next;
145                 talloc_free(node);
146         }
147
148         inst->session_head = inst->session_tail = NULL;
149 }
150
151 /*
152  *      Return a 32-bit random number.
153  */
154 static uint32_t eap_rand(fr_randctx *ctx)
155 {
156         uint32_t num;
157
158         num = ctx->randrsl[ctx->randcnt++];
159         if (ctx->randcnt >= 256) {
160                 ctx->randcnt = 0;
161                 fr_isaac(ctx);
162         }
163
164         return num;
165 }
166
167
168 static eap_handler_t *eaplist_delete(rlm_eap_t *inst, REQUEST *request,
169                                    eap_handler_t *handler)
170 {
171         rbnode_t *node;
172
173         node = rbtree_find(inst->session_tree, handler);
174         if (!node) return NULL;
175
176         handler = rbtree_node2data(inst->session_tree, node);
177
178         RDEBUG("Finished EAP session with state "
179                "0x%02x%02x%02x%02x%02x%02x%02x%02x",
180                handler->state[0], handler->state[1],
181                handler->state[2], handler->state[3],
182                handler->state[4], handler->state[5],
183                handler->state[6], handler->state[7]);
184         /*
185          *      Delete old handler from the tree.
186          */
187         rbtree_delete(inst->session_tree, node);
188
189         /*
190          *      And unsplice it from the linked list.
191          */
192         if (handler->prev) {
193                 handler->prev->next = handler->next;
194         } else {
195                 inst->session_head = handler->next;
196         }
197         if (handler->next) {
198                 handler->next->prev = handler->prev;
199         } else {
200                 inst->session_tail = handler->prev;
201         }
202         handler->prev = handler->next = NULL;
203
204         return handler;
205 }
206
207
208 static void eaplist_expire(rlm_eap_t *inst, REQUEST *request, time_t timestamp)
209 {
210         int i;
211         eap_handler_t *handler;
212
213         /*
214          *      Check the first few handlers in the list, and delete
215          *      them if they're too old.  We don't need to check them
216          *      all, as incoming requests will quickly cause older
217          *      handlers to be deleted.
218          *
219          */
220         for (i = 0; i < 3; i++) {
221                 handler = inst->session_head;
222                 if (!handler) break;
223
224                 RDEBUG("Expiring EAP session with state "
225                        "0x%02x%02x%02x%02x%02x%02x%02x%02x",
226                        handler->state[0], handler->state[1],
227                        handler->state[2], handler->state[3],
228                        handler->state[4], handler->state[5],
229                        handler->state[6], handler->state[7]);
230
231                 /*
232                  *      Expire entries from the start of the list.
233                  *      They should be the oldest ones.
234                  */
235                 if ((timestamp - handler->timestamp) > (int)inst->timer_limit) {
236                         rbnode_t *node;
237                         node = rbtree_find(inst->session_tree, handler);
238                         rad_assert(node != NULL);
239                         rbtree_delete(inst->session_tree, node);
240
241                         /*
242                          *      handler == inst->session_head
243                          */
244                         inst->session_head = handler->next;
245                         if (handler->next) {
246                                 handler->next->prev = NULL;
247                         } else {
248                                 inst->session_head = NULL;
249                                 inst->session_tail = NULL;
250                         }
251                         talloc_free(handler);
252                 } else {
253                         break;
254                 }
255         }
256 }
257
258 /*
259  *      Add a handler to the set of active sessions.
260  *
261  *      Since we're adding it to the list, we guess that this means
262  *      the packet needs a State attribute.  So add one.
263  */
264 int eaplist_add(rlm_eap_t *inst, eap_handler_t *handler)
265 {
266         int             status = 0;
267         VALUE_PAIR      *state;
268         REQUEST         *request = handler->request;
269
270         /*
271          *      Generate State, since we've been asked to add it to
272          *      the list.
273          */
274         state = pair_make_reply("State", NULL, T_OP_EQ);
275         if (!state) return 0;
276
277         /*
278          *      The time at which this request was made was the time
279          *      at which it was received by the RADIUS server.
280          */
281         handler->timestamp = request->timestamp;
282         handler->status = 1;
283
284         handler->src_ipaddr = request->packet->src_ipaddr;
285         handler->eap_id = handler->eap_ds->request->id;
286
287         /*
288          *      Playing with a data structure shared among threads
289          *      means that we need a lock, to avoid conflict.
290          */
291         PTHREAD_MUTEX_LOCK(&(inst->session_mutex));
292
293         /*
294          *      If we have a DoS attack, discard new sessions.
295          */
296         if (rbtree_num_elements(inst->session_tree) >= inst->max_sessions) {
297                 status = -1;
298                 eaplist_expire(inst, request, handler->timestamp);
299                 goto done;
300         }
301
302         /*
303          *      Create a unique content for the State variable.
304          *      It will be modified slightly per round trip, but less so
305          *      than in 1.x.
306          */
307         if (handler->trips == 0) {
308                 int i;
309
310                 for (i = 0; i < 4; i++) {
311                         uint32_t lvalue;
312
313                         lvalue = eap_rand(&inst->rand_pool);
314
315                         memcpy(handler->state + i * 4, &lvalue,
316                                sizeof(lvalue));
317                 }
318         }
319
320         /*
321          *      Add some more data to distinguish the sessions.
322          */
323         handler->state[4] = handler->trips ^ handler->state[0];
324         handler->state[5] = handler->eap_id ^ handler->state[1];
325         handler->state[6] = handler->type ^ handler->state[2];
326
327         fr_pair_value_memcpy(state, handler->state, sizeof(handler->state));
328
329         /*
330          *      Big-time failure.
331          */
332         status = rbtree_insert(inst->session_tree, handler);
333
334         if (status) {
335                 eap_handler_t *prev;
336
337                 prev = inst->session_tail;
338                 if (prev) {
339                         prev->next = handler;
340                         handler->prev = prev;
341                         handler->next = NULL;
342                         inst->session_tail = handler;
343                 } else {
344                         inst->session_head = inst->session_tail = handler;
345                         handler->next = handler->prev = NULL;
346                 }
347         }
348
349         /*
350          *      Now that we've finished mucking with the list,
351          *      unlock it.
352          */
353  done:
354
355         /*
356          *      We don't need this any more.
357          */
358         if (status > 0) handler->request = NULL;
359
360         PTHREAD_MUTEX_UNLOCK(&(inst->session_mutex));
361
362         if (status <= 0) {
363                 fr_pair_delete_by_num(&request->reply->vps, PW_STATE, 0, TAG_ANY);
364
365                 if (status < 0) {
366                         static time_t last_logged = 0;
367
368                         if (last_logged < handler->timestamp) {
369                                 last_logged = handler->timestamp;
370                                 ERROR("rlm_eap (%s): Too many open sessions. Try increasing \"max_sessions\" "
371                                       "in the EAP module configuration", inst->xlat_name);
372                         }
373                 } else {
374                         ERROR("rlm_eap (%s): Failed to store handler", inst->xlat_name);
375                 }
376                 return 0;
377         }
378
379         RDEBUG("EAP session adding &reply:State = 0x%02x%02x%02x%02x%02x%02x%02x%02x",
380                state->vp_octets[0], state->vp_octets[1], state->vp_octets[2], state->vp_octets[3],
381                state->vp_octets[4], state->vp_octets[5], state->vp_octets[6], state->vp_octets[7]);
382
383         return 1;
384 }
385
386 /*
387  *      Find a a previous EAP-Request sent by us, which matches
388  *      the current EAP-Response.
389  *
390  *      Then, release the handle from the list, and return it to
391  *      the caller.
392  *
393  *      Also since we fill the eap_ds with the present EAP-Response we
394  *      got to free the prev_eapds & move the eap_ds to prev_eapds
395  */
396 eap_handler_t *eaplist_find(rlm_eap_t *inst, REQUEST *request,
397                           eap_packet_raw_t *eap_packet)
398 {
399         VALUE_PAIR      *state;
400         eap_handler_t   *handler, myHandler;
401
402         /*
403          *      We key the sessions off of the 'state' attribute, so it
404          *      must exist.
405          */
406         state = fr_pair_find_by_num(request->packet->vps, PW_STATE, 0, TAG_ANY);
407         if (!state) {
408                 REDEBUG("EAP requires the State attribute to work, but no State exists in the Access-Request packet.");
409                 REDEBUG("The RADIUS client is broken.  No amount of changing FreeRADIUS will fix the RADIUS client.");
410                 return NULL;
411         }
412
413         if (state->vp_length != EAP_STATE_LEN) {
414                 REDEBUG("The RADIUS client has mangled the State attribute, OR you are forcing EAP in the wrong situation");
415                 return NULL;
416         }
417
418         myHandler.src_ipaddr = request->packet->src_ipaddr;
419         myHandler.eap_id = eap_packet->id;
420         memcpy(myHandler.state, state->vp_strvalue, sizeof(myHandler.state));
421
422         /*
423          *      Playing with a data structure shared among threads
424          *      means that we need a lock, to avoid conflict.
425          */
426         PTHREAD_MUTEX_LOCK(&(inst->session_mutex));
427
428         eaplist_expire(inst, request, request->timestamp);
429
430         handler = eaplist_delete(inst, request, &myHandler);
431         PTHREAD_MUTEX_UNLOCK(&(inst->session_mutex));
432
433         /*
434          *      Might not have been there.
435          */
436         if (!handler) {
437                 RERROR("rlm_eap (%s): No EAP session matching state "
438                        "0x%02x%02x%02x%02x%02x%02x%02x%02x",
439                        inst->xlat_name,
440                        state->vp_octets[0], state->vp_octets[1],
441                        state->vp_octets[2], state->vp_octets[3],
442                        state->vp_octets[4], state->vp_octets[5],
443                        state->vp_octets[6], state->vp_octets[7]);
444                 return NULL;
445         }
446
447         if (handler->trips >= 50) {
448                 RERROR("rlm_eap (%s): Aborting! More than 50 roundtrips "
449                        "made in session with state "
450                        "0x%02x%02x%02x%02x%02x%02x%02x%02x",
451                        inst->xlat_name,
452                        state->vp_octets[0], state->vp_octets[1],
453                        state->vp_octets[2], state->vp_octets[3],
454                        state->vp_octets[4], state->vp_octets[5],
455                        state->vp_octets[6], state->vp_octets[7]);
456
457
458                 talloc_free(handler);
459                 return NULL;
460         }
461         handler->trips++;
462
463         RDEBUG("Previous EAP request found for state "
464                "0x%02x%02x%02x%02x%02x%02x%02x%02x, released from the list",
465                 state->vp_octets[0], state->vp_octets[1],
466                 state->vp_octets[2], state->vp_octets[3],
467                 state->vp_octets[4], state->vp_octets[5],
468                 state->vp_octets[6], state->vp_octets[7]);
469
470         /*
471          *      Remember what the previous request was.
472          */
473         eap_ds_free(&(handler->prev_eapds));
474         handler->prev_eapds = handler->eap_ds;
475         handler->eap_ds = NULL;
476
477         return handler;
478 }