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 typedef struct state_entry_t {
34 uint8_t state[AUTH_VECTOR_LEN];
37 struct state_entry_t *prev;
38 struct state_entry_t *next;
45 void (*free_opaque)(void *opaque);
51 state_entry_t *head, *tail;
54 pthread_mutex_t mutex;
58 static fr_state_t global_state;
62 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
63 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
67 * This is easier than ifdef's throughout the code.
69 #define PTHREAD_MUTEX_LOCK(_x)
70 #define PTHREAD_MUTEX_UNLOCK(_x)
77 static int state_entry_cmp(void const *one, void const *two)
79 state_entry_t const *a = one;
80 state_entry_t const *b = two;
82 return memcmp(a->state, b->state, sizeof(a->state));
86 * When an entry is free'd, it's removed from the linked list of
91 static void state_entry_free(fr_state_t *state, state_entry_t *entry)
93 state_entry_t *prev, *next;
96 * If we're deleting the whole tree, don't bother doing
99 if (!state || !state->tree) return;
105 rad_assert(state->head != entry);
107 } else if (state->head) {
108 rad_assert(state->head == entry);
113 rad_assert(state->tail != entry);
115 } else if (state->tail) {
116 rad_assert(state->tail == entry);
121 entry->free_opaque(entry->opaque);
124 #ifdef WITH_VERIFY_PTR
125 (void) talloc_get_type_abort(entry, state_entry_t);
127 rbtree_deletebydata(state->tree, entry);
131 fr_state_t *fr_state_init(TALLOC_CTX *ctx)
136 state = &global_state;
137 if (state->tree) return state;
139 state = talloc_zero(ctx, fr_state_t);
140 if (!state) return 0;
143 #ifdef HAVE_PTHREAD_H
144 if (pthread_mutex_init(&state->mutex, NULL) != 0) {
150 state->tree = rbtree_create(NULL, state_entry_cmp, NULL, 0);
159 void fr_state_delete(fr_state_t *state)
165 PTHREAD_MUTEX_LOCK(&state->mutex);
168 * Tell the talloc callback to NOT delete the entry from
169 * the tree. We're deleting the entire tree.
171 my_tree = state->tree;
174 rbtree_free(my_tree);
175 PTHREAD_MUTEX_UNLOCK(&state->mutex);
177 if (state != &global_state) talloc_free(state);
181 * Create a new entry. Called with the mutex held.
183 static state_entry_t *fr_state_create(fr_state_t *state, RADIUS_PACKET *packet, state_entry_t *old)
187 time_t now = time(NULL);
189 state_entry_t *entry, *next;
192 * Clean up old entries.
194 for (entry = state->head; entry != NULL; entry = next) {
197 if (entry == old) continue;
200 * Too old, we can delete it.
202 if (entry->cleanup < now) {
203 state_entry_free(state, entry);
208 * Unused. We can delete it, even if now isn't
209 * the time to clean it up.
211 if (!entry->vps && !entry->opaque) {
212 state_entry_free(state, entry);
220 * Limit the size of the cache based on how many requests
221 * we can handle at the same time.
223 if (rbtree_num_elements(state->tree) >= main_config.max_requests * 2) {
228 * Allocate a new one.
230 entry = talloc_zero(state->tree, state_entry_t);
231 if (!entry) return NULL;
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.
239 entry->cleanup = now + main_config.max_request_time * 10;
242 * Hacks for EAP, until we convert EAP to using the state API.
244 * The EAP module creates it's own State attribute, so we
245 * want to use that one in preference to one we create.
247 vp = fr_pair_find_by_num(packet->vps, PW_STATE, 0, TAG_ANY);
250 * If possible, base the new one off of the old one.
253 entry->tries = old->tries + 1;
255 rad_assert(old->vps == NULL);
261 memcpy(entry->state, old->state, sizeof(entry->state));
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);
270 * The old one isn't used any more, so we can free it.
272 if (!old->opaque) state_entry_free(state, old);
276 * 16 octets of randomness should be enough to
277 * have a globally unique state.
279 for (i = 0; i < sizeof(entry->state) / sizeof(x); i++) {
281 memcpy(entry->state + (i * 4), &x, sizeof(x));
286 * If EAP created a State, use that. Otherwise, use the
287 * one we created above.
290 if (rad_debug_lvl && (vp->vp_length > sizeof(entry->state))) {
291 WARN("State should be %zd octets!",
292 sizeof(entry->state));
294 memcpy(entry->state, vp->vp_octets, sizeof(entry->state));
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);
302 if (!rbtree_insert(state->tree, entry)) {
308 * Link it to the end of the list, which is implicitely
309 * ordered by cleanup time.
312 entry->prev = entry->next = NULL;
313 state->head = state->tail = entry;
315 rad_assert(state->tail != NULL);
317 entry->prev = state->tail;
318 state->tail->next = entry;
329 * Find the entry, based on the State attribute.
331 static state_entry_t *fr_state_find(fr_state_t *state, RADIUS_PACKET *packet)
334 state_entry_t *entry, my_entry;
336 vp = fr_pair_find_by_num(packet->vps, PW_STATE, 0, TAG_ANY);
337 if (!vp) return NULL;
339 if (vp->vp_length != sizeof(my_entry.state)) return NULL;
341 memcpy(my_entry.state, vp->vp_octets, sizeof(my_entry.state));
343 entry = rbtree_finddata(state->tree, &my_entry);
345 #ifdef WITH_VERIFY_PTR
346 if (entry) (void) talloc_get_type_abort(entry, state_entry_t);
353 * Called when sending Access-Reject, so that all State is
356 void fr_state_discard(REQUEST *request, RADIUS_PACKET *original)
358 state_entry_t *entry;
359 fr_state_t *state = &global_state;
361 fr_pair_list_free(&request->state);
362 request->state = NULL;
364 PTHREAD_MUTEX_LOCK(&state->mutex);
365 entry = fr_state_find(state, original);
367 PTHREAD_MUTEX_UNLOCK(&state->mutex);
371 state_entry_free(state, entry);
372 PTHREAD_MUTEX_UNLOCK(&state->mutex);
377 * Get the VPS from the state.
379 void fr_state_get_vps(REQUEST *request, RADIUS_PACKET *packet)
381 state_entry_t *entry;
382 fr_state_t *state = &global_state;
384 rad_assert(request->state == NULL);
387 * No State, don't do anything.
389 if (!fr_pair_find_by_num(request->packet->vps, PW_STATE, 0, TAG_ANY)) {
390 RDEBUG3("session-state: No State attribute");
394 PTHREAD_MUTEX_LOCK(&state->mutex);
395 entry = fr_state_find(state, packet);
398 * This has to be done in a mutex lock, because talloc
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);
407 RDEBUG2("session-state: No cached attributes");
410 PTHREAD_MUTEX_UNLOCK(&state->mutex);
412 VERIFY_REQUEST(request);
418 * Put request->state into the State attribute. Put the State
419 * attribute into the vps list. Delete the original entry, if it
422 bool fr_state_put_vps(REQUEST *request, RADIUS_PACKET *original, RADIUS_PACKET *packet)
424 state_entry_t *entry, *old;
425 fr_state_t *state = &global_state;
427 if (!request->state) {
428 RDEBUG3("session-state: Nothing to cache");
432 RDEBUG2("session-state: Saving cached attributes");
433 rdebug_pair_list(L_DBG_LVL_1, request, request->state, NULL);
435 PTHREAD_MUTEX_LOCK(&state->mutex);
438 old = fr_state_find(state, original);
443 entry = fr_state_create(state, packet, old);
445 PTHREAD_MUTEX_UNLOCK(&state->mutex);
450 * This has to be done in a mutex lock, because talloc
453 fr_pair_list_move_by_num(entry, &entry->vps, &request->state, 0, 0, TAG_ANY);
454 PTHREAD_MUTEX_UNLOCK(&state->mutex);
456 rad_assert(request->state == NULL);
457 VERIFY_REQUEST(request);
462 * Find the opaque data associated with a State attribute.
463 * Leave the data in the entry.
465 void *fr_state_find_data(fr_state_t *state, UNUSED REQUEST *request, RADIUS_PACKET *packet)
468 state_entry_t *entry;
470 if (!state) return false;
472 PTHREAD_MUTEX_LOCK(&state->mutex);
473 entry = fr_state_find(state, packet);
475 PTHREAD_MUTEX_UNLOCK(&state->mutex);
479 data = entry->opaque;
480 PTHREAD_MUTEX_UNLOCK(&state->mutex);
487 * Get the opaque data associated with a State attribute.
488 * and remove the data from the entry.
490 void *fr_state_get_data(fr_state_t *state, UNUSED REQUEST *request, RADIUS_PACKET *packet)
493 state_entry_t *entry;
495 if (!state) return NULL;
497 PTHREAD_MUTEX_LOCK(&state->mutex);
498 entry = fr_state_find(state, packet);
500 PTHREAD_MUTEX_UNLOCK(&state->mutex);
504 data = entry->opaque;
505 entry->opaque = NULL;
506 PTHREAD_MUTEX_UNLOCK(&state->mutex);
513 * Get the opaque data associated with a State attribute.
514 * and remove the data from the entry.
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 *))
519 state_entry_t *entry, *old;
521 if (!state) return false;
523 PTHREAD_MUTEX_LOCK(&state->mutex);
526 old = fr_state_find(state, original);
531 entry = fr_state_create(state, packet, old);
533 PTHREAD_MUTEX_UNLOCK(&state->mutex);
538 * If we're moving the data, ensure that we delete it
539 * from the old state.
541 if (old && (old->opaque == data)) {
545 entry->opaque = data;
546 entry->free_opaque = free_data;
548 PTHREAD_MUTEX_UNLOCK(&state->mutex);