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