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;
46 void (*free_opaque)(void *opaque);
52 state_entry_t *head, *tail;
55 pthread_mutex_t mutex;
59 static fr_state_t global_state;
63 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
64 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
68 * This is easier than ifdef's throughout the code.
70 #define PTHREAD_MUTEX_LOCK(_x)
71 #define PTHREAD_MUTEX_UNLOCK(_x)
78 static int state_entry_cmp(void const *one, void const *two)
80 state_entry_t const *a = one;
81 state_entry_t const *b = two;
83 return memcmp(a->state, b->state, sizeof(a->state));
87 * When an entry is free'd, it's removed from the linked list of
92 static void state_entry_free(fr_state_t *state, state_entry_t *entry)
94 state_entry_t *prev, *next;
97 * If we're deleting the whole tree, don't bother doing
100 if (!state || !state->tree) return;
106 rad_assert(state->head != entry);
108 } else if (state->head) {
109 rad_assert(state->head == entry);
114 rad_assert(state->tail != entry);
116 } else if (state->tail) {
117 rad_assert(state->tail == entry);
122 entry->free_opaque(entry->opaque);
125 #ifdef WITH_VERIFY_PTR
126 (void) talloc_get_type_abort(entry, state_entry_t);
128 rbtree_deletebydata(state->tree, entry);
130 if (entry->ctx) talloc_free(entry->ctx);
135 fr_state_t *fr_state_init(TALLOC_CTX *ctx)
140 state = &global_state;
141 if (state->tree) return state;
143 state = talloc_zero(ctx, fr_state_t);
144 if (!state) return 0;
147 #ifdef HAVE_PTHREAD_H
148 if (pthread_mutex_init(&state->mutex, NULL) != 0) {
154 state->tree = rbtree_create(NULL, state_entry_cmp, NULL, 0);
163 void fr_state_delete(fr_state_t *state)
169 PTHREAD_MUTEX_LOCK(&state->mutex);
172 * Tell the talloc callback to NOT delete the entry from
173 * the tree. We're deleting the entire tree.
175 my_tree = state->tree;
178 rbtree_free(my_tree);
179 PTHREAD_MUTEX_UNLOCK(&state->mutex);
181 if (state != &global_state) talloc_free(state);
185 * Create a new entry. Called with the mutex held.
187 static state_entry_t *fr_state_create(fr_state_t *state, const char *server, RADIUS_PACKET *packet, state_entry_t *old)
191 time_t now = time(NULL);
193 state_entry_t *entry, *next;
196 * Clean up old entries.
198 for (entry = state->head; entry != NULL; entry = next) {
201 if (entry == old) continue;
204 * Too old, we can delete it.
206 if (entry->cleanup < now) {
207 state_entry_free(state, entry);
212 * Unused. We can delete it, even if now isn't
213 * the time to clean it up.
215 if (!entry->ctx && !entry->opaque) {
216 state_entry_free(state, entry);
224 * Limit the size of the cache based on how many requests
225 * we can handle at the same time.
227 if (rbtree_num_elements(state->tree) >= main_config.max_requests * 2) {
232 * Allocate a new one.
234 entry = talloc_zero(state->tree, state_entry_t);
235 if (!entry) return NULL;
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.
243 entry->cleanup = now + main_config.max_request_time * 10;
246 * Hacks for EAP, until we convert EAP to using the state API.
248 * The EAP module creates it's own State attribute, so we
249 * want to use that one in preference to one we create.
251 vp = fr_pair_find_by_num(packet->vps, PW_STATE, 0, TAG_ANY);
254 * If possible, base the new one off of the old one.
257 entry->tries = old->tries + 1;
263 memcpy(entry->state, old->state, sizeof(entry->state));
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);
272 * The old one isn't used any more, so we can free it.
274 if (!old->opaque) state_entry_free(state, old);
278 * 16 octets of randomness should be enough to
279 * have a globally unique state.
281 for (i = 0; i < sizeof(entry->state) / sizeof(x); i++) {
283 memcpy(entry->state + (i * 4), &x, sizeof(x));
288 * If EAP created a State, use that. Otherwise, use the
289 * one we created above.
292 if (rad_debug_lvl && (vp->vp_length > sizeof(entry->state))) {
293 WARN("State should be %zd octets!",
294 sizeof(entry->state));
296 memcpy(entry->state, vp->vp_octets, sizeof(entry->state));
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);
304 /* Make unique for different virtual servers handling same request
306 if (server) *((uint32_t *)(&entry->state[4])) ^= fr_hash_string(server);
308 if (!rbtree_insert(state->tree, entry)) {
314 * Link it to the end of the list, which is implicitely
315 * ordered by cleanup time.
318 entry->prev = entry->next = NULL;
319 state->head = state->tail = entry;
321 rad_assert(state->tail != NULL);
323 entry->prev = state->tail;
324 state->tail->next = entry;
335 * Find the entry, based on the State attribute.
337 static state_entry_t *fr_state_find(fr_state_t *state, const char *server, RADIUS_PACKET *packet)
340 state_entry_t *entry, my_entry;
342 vp = fr_pair_find_by_num(packet->vps, PW_STATE, 0, TAG_ANY);
343 if (!vp) return NULL;
345 if (vp->vp_length != sizeof(my_entry.state)) return NULL;
347 memcpy(my_entry.state, vp->vp_octets, sizeof(my_entry.state));
349 /* Make unique for different virtual servers handling same request
351 if (server) *((uint32_t *)(&my_entry.state[4])) ^= fr_hash_string(server);
353 entry = rbtree_finddata(state->tree, &my_entry);
355 #ifdef WITH_VERIFY_PTR
356 if (entry) (void) talloc_get_type_abort(entry, state_entry_t);
363 * Called when sending Access-Reject, so that all State is
366 void fr_state_discard(REQUEST *request, RADIUS_PACKET *original)
368 state_entry_t *entry;
369 fr_state_t *state = &global_state;
371 fr_pair_list_free(&request->state);
372 request->state = NULL;
374 PTHREAD_MUTEX_LOCK(&state->mutex);
375 entry = fr_state_find(state, request->server, original);
377 PTHREAD_MUTEX_UNLOCK(&state->mutex);
381 state_entry_free(state, entry);
382 PTHREAD_MUTEX_UNLOCK(&state->mutex);
387 * Get the VPS from the state.
389 void fr_state_get_vps(REQUEST *request, RADIUS_PACKET *packet)
391 state_entry_t *entry;
392 fr_state_t *state = &global_state;
393 TALLOC_CTX *old_ctx = NULL;
395 rad_assert(request->state == NULL);
398 * No State, don't do anything.
400 if (!fr_pair_find_by_num(request->packet->vps, PW_STATE, 0, TAG_ANY)) {
401 RDEBUG3("session-state: No State attribute");
405 PTHREAD_MUTEX_LOCK(&state->mutex);
406 entry = fr_state_find(state, request->server, packet);
409 * This has to be done in a mutex lock, because talloc
413 RDEBUG2("Restoring &session-state");
415 if (request->state_ctx) old_ctx = request->state_ctx;
417 request->state_ctx = entry->ctx;
418 request->state = entry->vps;
423 rdebug_pair_list(L_DBG_LVL_2, request, request->state, "&session-state:");
426 RDEBUG2("session-state: No cached attributes");
429 PTHREAD_MUTEX_UNLOCK(&state->mutex);
432 * Free this outside of the mutex for less contention.
434 if (old_ctx) talloc_free(old_ctx);
436 VERIFY_REQUEST(request);
442 * Put request->state into the State attribute. Put the State
443 * attribute into the vps list. Delete the original entry, if it
446 bool fr_state_put_vps(REQUEST *request, RADIUS_PACKET *original, RADIUS_PACKET *packet)
448 state_entry_t *entry, *old;
449 fr_state_t *state = &global_state;
451 if (!request->state) {
452 RDEBUG3("session-state: Nothing to cache");
456 RDEBUG2("session-state: Saving cached attributes");
457 rdebug_pair_list(L_DBG_LVL_1, request, request->state, NULL);
459 PTHREAD_MUTEX_LOCK(&state->mutex);
462 old = fr_state_find(state, request->server, original);
467 entry = fr_state_create(state, request->server, packet, old);
469 PTHREAD_MUTEX_UNLOCK(&state->mutex);
473 rad_assert(entry->ctx == NULL);
474 entry->ctx = request->state_ctx;
475 entry->vps = request->state;
477 request->state_ctx = NULL;
478 request->state = NULL;
480 PTHREAD_MUTEX_UNLOCK(&state->mutex);
482 rad_assert(request->state == NULL);
483 VERIFY_REQUEST(request);
488 * Find the opaque data associated with a State attribute.
489 * Leave the data in the entry.
491 void *fr_state_find_data(fr_state_t *state, REQUEST *request, RADIUS_PACKET *packet)
494 state_entry_t *entry;
496 if (!state) return false;
498 PTHREAD_MUTEX_LOCK(&state->mutex);
499 entry = fr_state_find(state, request->server, packet);
501 PTHREAD_MUTEX_UNLOCK(&state->mutex);
505 data = entry->opaque;
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 void *fr_state_get_data(fr_state_t *state, REQUEST *request, RADIUS_PACKET *packet)
519 state_entry_t *entry;
521 if (!state) return NULL;
523 PTHREAD_MUTEX_LOCK(&state->mutex);
524 entry = fr_state_find(state, request->server, packet);
526 PTHREAD_MUTEX_UNLOCK(&state->mutex);
530 data = entry->opaque;
531 entry->opaque = NULL;
532 PTHREAD_MUTEX_UNLOCK(&state->mutex);
539 * Get the opaque data associated with a State attribute.
540 * and remove the data from the entry.
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 *))
545 state_entry_t *entry, *old;
547 if (!state) return false;
549 PTHREAD_MUTEX_LOCK(&state->mutex);
552 old = fr_state_find(state, request->server, original);
557 entry = fr_state_create(state, request->server, packet, old);
559 PTHREAD_MUTEX_UNLOCK(&state->mutex);
564 * If we're moving the data, ensure that we delete it
565 * from the old state.
567 if (old && (old->opaque == data)) {
571 entry->opaque = data;
572 entry->free_opaque = free_data;
574 PTHREAD_MUTEX_UNLOCK(&state->mutex);