Merge pull request #1133 from jpereira/devel/add-sql_fields-freetds
[freeradius.git] / src / main / state.c
1 /*
2  *   This program is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License as published by
4  *   the Free Software Foundation; either version 2 of the License, or
5  *   (at your option) any later version.
6  *
7  *   This program is distributed in the hope that it will be useful,
8  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  *   GNU General Public License for more details.
11  *
12  *   You should have received a copy of the GNU General Public License
13  *   along with this program; if not, write to the Free Software
14  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15  */
16
17 /**
18  * $Id$
19  *
20  * @brief Multi-packet state handling
21  * @file main/state.c
22  *
23  * @ingroup AVP
24  *
25  * @copyright 2014 The FreeRADIUS server project
26  */
27 RCSID("$Id$")
28
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/state.h>
31 #include <freeradius-devel/rad_assert.h>
32
33 typedef struct state_entry_t {
34         uint8_t         state[AUTH_VECTOR_LEN];
35
36         time_t          cleanup;
37         struct state_entry_t *prev;
38         struct state_entry_t *next;
39
40         int             tries;
41
42         VALUE_PAIR      *vps;
43
44         void            *opaque;
45         void            (*free_opaque)(void *opaque);
46 } state_entry_t;
47
48 struct fr_state_t {
49         rbtree_t *tree;
50
51         state_entry_t *head, *tail;
52
53 #ifdef HAVE_PTHREAD_H
54         pthread_mutex_t mutex;
55 #endif
56 };
57
58 static fr_state_t global_state;
59
60 #ifdef HAVE_PTHREAD_H
61
62 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
63 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
64
65 #else
66 /*
67  *      This is easier than ifdef's throughout the code.
68  */
69 #define PTHREAD_MUTEX_LOCK(_x)
70 #define PTHREAD_MUTEX_UNLOCK(_x)
71
72 #endif
73
74 /*
75  *      rbtree callback.
76  */
77 static int state_entry_cmp(void const *one, void const *two)
78 {
79         state_entry_t const *a = one;
80         state_entry_t const *b = two;
81
82         return memcmp(a->state, b->state, sizeof(a->state));
83 }
84
85 /*
86  *      When an entry is free'd, it's removed from the linked list of
87  *      cleanup times.
88  *
89  *      Note that
90  */
91 static void state_entry_free(fr_state_t *state, state_entry_t *entry)
92 {
93         state_entry_t *prev, *next;
94
95         /*
96          *      If we're deleting the whole tree, don't bother doing
97          *      all of the fixups.
98          */
99         if (!state || !state->tree) return;
100
101         prev = entry->prev;
102         next = entry->next;
103
104         if (prev) {
105                 rad_assert(state->head != entry);
106                 prev->next = next;
107         } else if (state->head) {
108                 rad_assert(state->head == entry);
109                 state->head = next;
110         }
111
112         if (next) {
113                 rad_assert(state->tail != entry);
114                 next->prev = prev;
115         } else if (state->tail) {
116                 rad_assert(state->tail == entry);
117                 state->tail = prev;
118         }
119
120         if (entry->opaque) {
121                 entry->free_opaque(entry->opaque);
122         }
123
124 #ifdef WITH_VERIFY_PTR
125         (void) talloc_get_type_abort(entry, state_entry_t);
126 #endif
127         rbtree_deletebydata(state->tree, entry);
128         talloc_free(entry);
129 }
130
131 fr_state_t *fr_state_init(TALLOC_CTX *ctx)
132 {
133         fr_state_t *state;
134
135         if (!ctx) {
136                 state = &global_state;
137                 if (state->tree) return state;
138         } else {
139                 state = talloc_zero(ctx, fr_state_t);
140                 if (!state) return 0;
141         }
142
143 #ifdef HAVE_PTHREAD_H
144         if (pthread_mutex_init(&state->mutex, NULL) != 0) {
145                 talloc_free(state);
146                 return NULL;
147         }
148 #endif
149
150         state->tree = rbtree_create(NULL, state_entry_cmp, NULL, 0);
151         if (!state->tree) {
152                 talloc_free(state);
153                 return NULL;
154         }
155
156         return state;
157 }
158
159 void fr_state_delete(fr_state_t *state)
160 {
161         rbtree_t *my_tree;
162
163         if (!state) return;
164
165         PTHREAD_MUTEX_LOCK(&state->mutex);
166
167         /*
168          *      Tell the talloc callback to NOT delete the entry from
169          *      the tree.  We're deleting the entire tree.
170          */
171         my_tree = state->tree;
172         state->tree = NULL;
173
174         rbtree_free(my_tree);
175         PTHREAD_MUTEX_UNLOCK(&state->mutex);
176
177         if (state != &global_state) talloc_free(state);
178 }
179
180 /*
181  *      Create a new entry.  Called with the mutex held.
182  */
183 static state_entry_t *fr_state_create(fr_state_t *state, RADIUS_PACKET *packet, state_entry_t *old)
184 {
185         size_t i;
186         uint32_t x;
187         time_t now = time(NULL);
188         VALUE_PAIR *vp;
189         state_entry_t *entry, *next;
190
191         /*
192          *      Clean up old entries.
193          */
194         for (entry = state->head; entry != NULL; entry = next) {
195                 next = entry->next;
196
197                 if (entry == old) continue;
198
199                 /*
200                  *      Too old, we can delete it.
201                  */
202                 if (entry->cleanup < now) {
203                         state_entry_free(state, entry);
204                         continue;
205                 }
206
207                 /*
208                  *      Unused.  We can delete it, even if now isn't
209                  *      the time to clean it up.
210                  */
211                 if (!entry->vps && !entry->opaque) {
212                         state_entry_free(state, entry);
213                         continue;
214                 }
215
216                 break;
217         }
218
219         /*
220          *      Limit the size of the cache based on how many requests
221          *      we can handle at the same time.
222          */
223         if (rbtree_num_elements(state->tree) >= main_config.max_requests * 2) {
224                 return NULL;
225         }
226
227         /*
228          *      Allocate a new one.
229          */
230         entry = talloc_zero(state->tree, state_entry_t);
231         if (!entry) return NULL;
232
233         /*
234          *      Limit the lifetime of this entry based on how long the
235          *      server takes to process a request.  Doing it this way
236          *      isn't perfect, but it's reasonable, and it's one less
237          *      thing for an administrator to configure.
238          */
239         entry->cleanup = now + main_config.max_request_time * 10;
240
241         /*
242          *      Hacks for EAP, until we convert EAP to using the state API.
243          *
244          *      The EAP module creates it's own State attribute, so we
245          *      want to use that one in preference to one we create.
246          */
247         vp = fr_pair_find_by_num(packet->vps, PW_STATE, 0, TAG_ANY);
248
249         /*
250          *      If possible, base the new one off of the old one.
251          */
252         if (old) {
253                 entry->tries = old->tries + 1;
254
255                 rad_assert(old->vps == NULL);
256
257                 /*
258                  *      Track State
259                  */
260                 if (!vp) {
261                         memcpy(entry->state, old->state, sizeof(entry->state));
262
263                         entry->state[1] = entry->state[0] ^ entry->tries;
264                         entry->state[8] = entry->state[2] ^ ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 16) & 0xff);
265                         entry->state[10] = entry->state[2] ^ ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 8) & 0xff);
266                         entry->state[12] = entry->state[2] ^ (((uint32_t) HEXIFY(RADIUSD_VERSION)) & 0xff);
267                 }
268
269                 /*
270                  *      The old one isn't used any more, so we can free it.
271                  */
272                 if (!old->opaque) state_entry_free(state, old);
273
274         } else if (!vp) {
275                 /*
276                  *      16 octets of randomness should be enough to
277                  *      have a globally unique state.
278                  */
279                 for (i = 0; i < sizeof(entry->state) / sizeof(x); i++) {
280                         x = fr_rand();
281                         memcpy(entry->state + (i * 4), &x, sizeof(x));
282                 }
283         }
284
285         /*
286          *      If EAP created a State, use that.  Otherwise, use the
287          *      one we created above.
288          */
289         if (vp) {
290                 if (rad_debug_lvl && (vp->vp_length > sizeof(entry->state))) {
291                         WARN("State should be %zd octets!",
292                              sizeof(entry->state));
293                 }
294                 memcpy(entry->state, vp->vp_octets, sizeof(entry->state));
295
296         } else {
297                 vp = fr_pair_afrom_num(packet, PW_STATE, 0);
298                 fr_pair_value_memcpy(vp, entry->state, sizeof(entry->state));
299                 fr_pair_add(&packet->vps, vp);
300         }
301
302         if (!rbtree_insert(state->tree, entry)) {
303                 talloc_free(entry);
304                 return NULL;
305         }
306
307         /*
308          *      Link it to the end of the list, which is implicitely
309          *      ordered by cleanup time.
310          */
311         if (!state->head) {
312                 entry->prev = entry->next = NULL;
313                 state->head = state->tail = entry;
314         } else {
315                 rad_assert(state->tail != NULL);
316
317                 entry->prev = state->tail;
318                 state->tail->next = entry;
319
320                 entry->next = NULL;
321                 state->tail = entry;
322         }
323
324         return entry;
325 }
326
327
328 /*
329  *      Find the entry, based on the State attribute.
330  */
331 static state_entry_t *fr_state_find(fr_state_t *state, RADIUS_PACKET *packet)
332 {
333         VALUE_PAIR *vp;
334         state_entry_t *entry, my_entry;
335
336         vp = fr_pair_find_by_num(packet->vps, PW_STATE, 0, TAG_ANY);
337         if (!vp) return NULL;
338
339         if (vp->vp_length != sizeof(my_entry.state)) return NULL;
340
341         memcpy(my_entry.state, vp->vp_octets, sizeof(my_entry.state));
342
343         entry = rbtree_finddata(state->tree, &my_entry);
344
345 #ifdef WITH_VERIFY_PTR
346         if (entry)  (void) talloc_get_type_abort(entry, state_entry_t);
347 #endif
348
349         return entry;
350 }
351
352 /*
353  *      Called when sending Access-Reject, so that all State is
354  *      discarded.
355  */
356 void fr_state_discard(REQUEST *request, RADIUS_PACKET *original)
357 {
358         state_entry_t *entry;
359         fr_state_t *state = &global_state;
360
361         fr_pair_list_free(&request->state);
362         request->state = NULL;
363
364         PTHREAD_MUTEX_LOCK(&state->mutex);
365         entry = fr_state_find(state, original);
366         if (!entry) {
367                 PTHREAD_MUTEX_UNLOCK(&state->mutex);
368                 return;
369         }
370
371         state_entry_free(state, entry);
372         PTHREAD_MUTEX_UNLOCK(&state->mutex);
373         return;
374 }
375
376 /*
377  *      Get the VPS from the state.
378  */
379 void fr_state_get_vps(REQUEST *request, RADIUS_PACKET *packet)
380 {
381         state_entry_t *entry;
382         fr_state_t *state = &global_state;
383
384         rad_assert(request->state == NULL);
385
386         /*
387          *      No State, don't do anything.
388          */
389         if (!fr_pair_find_by_num(request->packet->vps, PW_STATE, 0, TAG_ANY)) {
390                 RDEBUG3("session-state: No State attribute");
391                 return;
392         }
393
394         PTHREAD_MUTEX_LOCK(&state->mutex);
395         entry = fr_state_find(state, packet);
396
397         /*
398          *      This has to be done in a mutex lock, because talloc
399          *      isn't thread-safe.
400          */
401         if (entry) {
402                 fr_pair_list_move_by_num(request, &request->state, &entry->vps, 0, 0, TAG_ANY);
403                 RDEBUG2("session-state: Found cached attributes");
404                 rdebug_pair_list(L_DBG_LVL_1, request, request->state, NULL);
405
406         } else {
407                 RDEBUG2("session-state: No cached attributes");
408         }
409
410         PTHREAD_MUTEX_UNLOCK(&state->mutex);
411
412         VERIFY_REQUEST(request);
413         return;
414 }
415
416
417 /*
418  *      Put request->state into the State attribute.  Put the State
419  *      attribute into the vps list.  Delete the original entry, if it
420  *      exists.
421  */
422 bool fr_state_put_vps(REQUEST *request, RADIUS_PACKET *original, RADIUS_PACKET *packet)
423 {
424         state_entry_t *entry, *old;
425         fr_state_t *state = &global_state;
426
427         if (!request->state) {
428                 RDEBUG3("session-state: Nothing to cache");
429                 return true;
430         }
431
432         RDEBUG2("session-state: Saving cached attributes");
433         rdebug_pair_list(L_DBG_LVL_1, request, request->state, NULL);
434
435         PTHREAD_MUTEX_LOCK(&state->mutex);
436
437         if (original) {
438                 old = fr_state_find(state, original);
439         } else {
440                 old = NULL;
441         }
442
443         entry = fr_state_create(state, packet, old);
444         if (!entry) {
445                 PTHREAD_MUTEX_UNLOCK(&state->mutex);
446                 return false;
447         }
448
449         /*
450          *      This has to be done in a mutex lock, because talloc
451          *      isn't thread-safe.
452          */
453         fr_pair_list_move_by_num(entry, &entry->vps, &request->state, 0, 0, TAG_ANY);
454         PTHREAD_MUTEX_UNLOCK(&state->mutex);
455
456         rad_assert(request->state == NULL);
457         VERIFY_REQUEST(request);
458         return true;
459 }
460
461 /*
462  *      Find the opaque data associated with a State attribute.
463  *      Leave the data in the entry.
464  */
465 void *fr_state_find_data(fr_state_t *state, UNUSED REQUEST *request, RADIUS_PACKET *packet)
466 {
467         void *data;
468         state_entry_t *entry;
469
470         if (!state) return false;
471
472         PTHREAD_MUTEX_LOCK(&state->mutex);
473         entry = fr_state_find(state, packet);
474         if (!entry) {
475                 PTHREAD_MUTEX_UNLOCK(&state->mutex);
476                 return NULL;
477         }
478
479         data = entry->opaque;
480         PTHREAD_MUTEX_UNLOCK(&state->mutex);
481
482         return data;
483 }
484
485
486 /*
487  *      Get the opaque data associated with a State attribute.
488  *      and remove the data from the entry.
489  */
490 void *fr_state_get_data(fr_state_t *state, UNUSED REQUEST *request, RADIUS_PACKET *packet)
491 {
492         void *data;
493         state_entry_t *entry;
494
495         if (!state) return NULL;
496
497         PTHREAD_MUTEX_LOCK(&state->mutex);
498         entry = fr_state_find(state, packet);
499         if (!entry) {
500                 PTHREAD_MUTEX_UNLOCK(&state->mutex);
501                 return NULL;
502         }
503
504         data = entry->opaque;
505         entry->opaque = NULL;
506         PTHREAD_MUTEX_UNLOCK(&state->mutex);
507
508         return data;
509 }
510
511
512 /*
513  *      Get the opaque data associated with a State attribute.
514  *      and remove the data from the entry.
515  */
516 bool fr_state_put_data(fr_state_t *state, UNUSED REQUEST *request, RADIUS_PACKET *original, RADIUS_PACKET *packet,
517                        void *data, void (*free_data)(void *))
518 {
519         state_entry_t *entry, *old;
520
521         if (!state) return false;
522
523         PTHREAD_MUTEX_LOCK(&state->mutex);
524
525         if (original) {
526                 old = fr_state_find(state, original);
527         } else {
528                 old = NULL;
529         }
530
531         entry = fr_state_create(state, packet, old);
532         if (!entry) {
533                 PTHREAD_MUTEX_UNLOCK(&state->mutex);
534                 return false;
535         }
536
537         /*
538          *      If we're moving the data, ensure that we delete it
539          *      from the old state.
540          */
541         if (old && (old->opaque == data)) {
542                 old->opaque = NULL;
543         }
544
545         entry->opaque = data;
546         entry->free_opaque = free_data;
547
548         PTHREAD_MUTEX_UNLOCK(&state->mutex);
549         return true;
550 }