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.
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.
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
20 * @brief Multi-packet state handling
25 * @copyright 2014 The FreeRADIUS server project
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/state.h>
31 #include <freeradius-devel/rad_assert.h>
33 static rbtree_t *state_tree;
36 static pthread_mutex_t state_mutex;
38 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
39 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
43 * This is easier than ifdef's throughout the code.
45 #define PTHREAD_MUTEX_LOCK(_x)
46 #define PTHREAD_MUTEX_UNLOCK(_x)
50 typedef struct state_entry_t {
51 uint8_t state[AUTH_VECTOR_LEN];
54 struct state_entry_t *prev;
55 struct state_entry_t *next;
62 void (*free_opaque)(void *opaque);
65 static state_entry_t *state_head = NULL;
66 static state_entry_t *state_tail = NULL;
71 static int state_entry_cmp(void const *one, void const *two)
73 state_entry_t const *a = one;
74 state_entry_t const *b = two;
76 return memcmp(a->state, b->state, sizeof(a->state));
80 * When an entry is free'd, it's removed from the linked list of
85 static void state_entry_free(state_entry_t *entry)
87 state_entry_t *prev, *next;
90 * If we're deleting the whole tree, don't bother doing
93 if (!state_tree) return;
99 rad_assert(state_tail != entry);
101 } else if (state_head) {
102 rad_assert(state_head == entry);
107 rad_assert(state_tail != entry);
109 } else if (state_tail) {
110 rad_assert(state_tail == entry);
115 entry->free_opaque(entry->opaque);
118 #ifdef WITH_VERIFY_PTR
119 (void) talloc_get_type_abort(entry, state_entry_t);
121 rbtree_deletebydata(state_tree, entry);
125 bool fr_state_init(void)
127 #ifdef HAVE_PTHREAD_H
128 if (pthread_mutex_init(&state_mutex, NULL) != 0) {
133 state_tree = rbtree_create(NULL, state_entry_cmp, NULL, 0);
141 void fr_state_delete(void)
145 PTHREAD_MUTEX_LOCK(&state_mutex);
148 * Tell the talloc callback to NOT delete the entry from
149 * the tree. We're deleting the entire tree.
151 my_tree = state_tree;
154 rbtree_free(my_tree);
155 PTHREAD_MUTEX_UNLOCK(&state_mutex);
159 * Create a new entry. Called with the mutex held.
161 static state_entry_t *fr_state_create(RADIUS_PACKET *packet, state_entry_t *old)
165 time_t now = time(NULL);
167 state_entry_t *entry, *next;
170 * Clean up old entries.
172 for (entry = state_head; entry != NULL; entry = next) {
175 if (entry == old) continue;
178 * Too old, we can delete it.
180 if (entry->cleanup < now) {
181 state_entry_free(entry);
186 * Unused. We can delete it, even if now isn't
187 * the time to clean it up.
189 if (!entry->vps && !entry->opaque) {
190 state_entry_free(entry);
198 * Limit the size of the cache based on how many requests
199 * we can handle at the same time.
201 if (rbtree_num_elements(state_tree) >= main_config.max_requests * 2) {
206 * Allocate a new one.
208 entry = talloc_zero(state_tree, state_entry_t);
209 if (!entry) return NULL;
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.
217 entry->cleanup = now + main_config.max_request_time * 10;
220 * Hacks for EAP, until we convert EAP to using the state API.
222 * The EAP module creates it's own State attribute, so we
223 * want to use that one in preference to one we create.
225 vp = pairfind(packet->vps, PW_STATE, 0, TAG_ANY);
228 * If possible, base the new one off of the old one.
231 entry->tries = old->tries + 1;
233 rad_assert(old->vps == NULL);
239 memcpy(entry->state, old->state, sizeof(entry->state));
241 entry->state[1] = entry->state[0] ^ entry->tries;
242 entry->state[3] = entry->state[2] ^ (RADIUSD_VERSION / 10000);
246 * The old one isn't used any more, so we can free it.
248 if (!old->opaque) state_entry_free(old);
252 * 16 octets of randomness should be enough to
253 * have a globally unique state.
255 for (i = 0; i < sizeof(entry->state) / sizeof(x); i++) {
257 memcpy(entry->state + (i * 4), &x, sizeof(x));
262 * If EAP created a State, use that. Otherwise, use the
263 * one we created above.
266 rad_assert(vp->length == sizeof(entry->state));
267 memcpy(entry->state, vp->vp_octets, sizeof(entry->state));
270 vp = paircreate(packet, PW_STATE, 0);
271 pairmemcpy(vp, entry->state, sizeof(entry->state));
272 pairadd(&packet->vps, vp);
275 if (!rbtree_insert(state_tree, entry)) {
281 * Link it to the end of the list, which is implicitely
282 * ordered by cleanup time.
285 entry->prev = entry->next = NULL;
286 state_head = state_tail = entry;
288 rad_assert(state_tail != NULL);
290 entry->prev = state_tail;
291 state_tail->next = entry;
302 * Find the entry, based on the State attribute.
304 static state_entry_t *fr_state_find(RADIUS_PACKET *packet)
307 state_entry_t *entry, my_entry;
309 vp = pairfind(packet->vps, PW_STATE, 0, TAG_ANY);
310 if (!vp) return NULL;
312 if (vp->length != sizeof(my_entry.state)) return NULL;
314 memcpy(my_entry.state, vp->vp_octets, sizeof(my_entry.state));
316 entry = rbtree_finddata(state_tree, &my_entry);
318 #ifdef WITH_VERIFY_PTR
319 if (entry) (void) talloc_get_type_abort(entry, state_entry_t);
326 * Called when sending Access-Reject, so that all State is
329 void fr_state_discard(REQUEST *request, RADIUS_PACKET *original)
331 state_entry_t *entry;
333 pairfree(&request->state);
334 request->state = NULL;
336 PTHREAD_MUTEX_LOCK(&state_mutex);
337 entry = fr_state_find(original);
339 PTHREAD_MUTEX_UNLOCK(&state_mutex);
343 state_entry_free(entry);
344 PTHREAD_MUTEX_UNLOCK(&state_mutex);
349 * Get the VPS from the state.
351 void fr_state_get_vps(REQUEST *request, RADIUS_PACKET *packet)
353 state_entry_t *entry;
355 rad_assert(request->state == NULL);
358 * No State, don't do anything.
360 if (!pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY)) {
361 RDEBUG3("session-state: No State attribute");
365 PTHREAD_MUTEX_LOCK(&state_mutex);
366 entry = fr_state_find(packet);
369 * This has to be done in a mutex lock, because talloc
373 pairfilter(request, &request->state, &entry->vps, 0, 0, TAG_ANY);
374 RDEBUG2("session-state: Found cached attributes");
375 rdebug_pair_list(L_DBG_LVL_1, request, request->state, NULL);
378 RDEBUG2("session-state: No cached attributes");
381 PTHREAD_MUTEX_UNLOCK(&state_mutex);
383 VERIFY_REQUEST(request);
389 * Put request->state into the State attribute. Put the State
390 * attribute into the vps list. Delete the original entry, if it
393 bool fr_state_put_vps(REQUEST *request, RADIUS_PACKET *original, RADIUS_PACKET *packet)
395 state_entry_t *entry, *old;
397 if (!request->state) {
398 RDEBUG3("session-state: Nothing to cache");
402 RDEBUG2("session-state: Saving cached attributes");
403 rdebug_pair_list(L_DBG_LVL_1, request, request->state, NULL);
405 PTHREAD_MUTEX_LOCK(&state_mutex);
408 old = fr_state_find(original);
413 entry = fr_state_create(packet, old);
415 PTHREAD_MUTEX_UNLOCK(&state_mutex);
420 * This has to be done in a mutex lock, because talloc
423 pairfilter(entry, &entry->vps, &request->state, 0, 0, TAG_ANY);
424 PTHREAD_MUTEX_UNLOCK(&state_mutex);
426 rad_assert(request->state == NULL);
427 VERIFY_REQUEST(request);
432 * Find the opaque data associated with a State attribute.
433 * Leave the data in the entry.
435 void *fr_state_find_data(UNUSED REQUEST *request, RADIUS_PACKET *packet)
438 state_entry_t *entry;
440 PTHREAD_MUTEX_LOCK(&state_mutex);
441 entry = fr_state_find(packet);
443 PTHREAD_MUTEX_UNLOCK(&state_mutex);
447 data = entry->opaque;
448 PTHREAD_MUTEX_UNLOCK(&state_mutex);
455 * Get the opaque data associated with a State attribute.
456 * and remove the data from the entry.
458 void *fr_state_get_data(UNUSED REQUEST *request, RADIUS_PACKET *packet)
461 state_entry_t *entry;
463 PTHREAD_MUTEX_LOCK(&state_mutex);
464 entry = fr_state_find(packet);
466 PTHREAD_MUTEX_UNLOCK(&state_mutex);
470 data = entry->opaque;
471 entry->opaque = NULL;
472 PTHREAD_MUTEX_UNLOCK(&state_mutex);
479 * Get the opaque data associated with a State attribute.
480 * and remove the data from the entry.
482 bool fr_state_put_data(UNUSED REQUEST *request, RADIUS_PACKET *original, RADIUS_PACKET *packet,
483 void *data, void (*free_data)(void *))
485 state_entry_t *entry, *old;
487 PTHREAD_MUTEX_LOCK(&state_mutex);
490 old = fr_state_find(original);
495 entry = fr_state_create(packet, old);
497 PTHREAD_MUTEX_UNLOCK(&state_mutex);
502 * If we're moving the data, ensure that we delete it
503 * from the old state.
505 if (old && (old->opaque == data)) {
509 entry->opaque = data;
510 entry->free_opaque = free_data;
512 PTHREAD_MUTEX_UNLOCK(&state_mutex);