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