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[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);
248 * The old one isn't used any more, so we can free it.
250 if (!old->opaque) state_entry_free(old);
254 * 16 octets of randomness should be enough to
255 * have a globally unique state.
257 for (i = 0; i < sizeof(entry->state) / sizeof(x); i++) {
259 memcpy(entry->state + (i * 4), &x, sizeof(x));
264 * If EAP created a State, use that. Otherwise, use the
265 * one we created above.
268 if (debug_flag && (vp->vp_length > sizeof(entry->state))) {
269 WARN("State should be %zd octets!",
270 sizeof(entry->state));
272 memcpy(entry->state, vp->vp_octets, sizeof(entry->state));
275 vp = paircreate(packet, PW_STATE, 0);
276 pairmemcpy(vp, entry->state, sizeof(entry->state));
277 pairadd(&packet->vps, vp);
280 if (!rbtree_insert(state_tree, entry)) {
286 * Link it to the end of the list, which is implicitely
287 * ordered by cleanup time.
290 entry->prev = entry->next = NULL;
291 state_head = state_tail = entry;
293 rad_assert(state_tail != NULL);
295 entry->prev = state_tail;
296 state_tail->next = entry;
307 * Find the entry, based on the State attribute.
309 static state_entry_t *fr_state_find(RADIUS_PACKET *packet)
312 state_entry_t *entry, my_entry;
314 vp = pairfind(packet->vps, PW_STATE, 0, TAG_ANY);
315 if (!vp) return NULL;
317 if (vp->vp_length != sizeof(my_entry.state)) return NULL;
319 memcpy(my_entry.state, vp->vp_octets, sizeof(my_entry.state));
321 entry = rbtree_finddata(state_tree, &my_entry);
323 #ifdef WITH_VERIFY_PTR
324 if (entry) (void) talloc_get_type_abort(entry, state_entry_t);
331 * Called when sending Access-Reject, so that all State is
334 void fr_state_discard(REQUEST *request, RADIUS_PACKET *original)
336 state_entry_t *entry;
338 pairfree(&request->state);
339 request->state = NULL;
341 PTHREAD_MUTEX_LOCK(&state_mutex);
342 entry = fr_state_find(original);
344 PTHREAD_MUTEX_UNLOCK(&state_mutex);
348 state_entry_free(entry);
349 PTHREAD_MUTEX_UNLOCK(&state_mutex);
354 * Get the VPS from the state.
356 void fr_state_get_vps(REQUEST *request, RADIUS_PACKET *packet)
358 state_entry_t *entry;
360 rad_assert(request->state == NULL);
363 * No State, don't do anything.
365 if (!pairfind(request->packet->vps, PW_STATE, 0, TAG_ANY)) {
366 RDEBUG3("session-state: No State attribute");
370 PTHREAD_MUTEX_LOCK(&state_mutex);
371 entry = fr_state_find(packet);
374 * This has to be done in a mutex lock, because talloc
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);
383 RDEBUG2("session-state: No cached attributes");
386 PTHREAD_MUTEX_UNLOCK(&state_mutex);
388 VERIFY_REQUEST(request);
394 * Put request->state into the State attribute. Put the State
395 * attribute into the vps list. Delete the original entry, if it
398 bool fr_state_put_vps(REQUEST *request, RADIUS_PACKET *original, RADIUS_PACKET *packet)
400 state_entry_t *entry, *old;
402 if (!request->state) {
403 RDEBUG3("session-state: Nothing to cache");
407 RDEBUG2("session-state: Saving cached attributes");
408 rdebug_pair_list(L_DBG_LVL_1, request, request->state, NULL);
410 PTHREAD_MUTEX_LOCK(&state_mutex);
413 old = fr_state_find(original);
418 entry = fr_state_create(packet, old);
420 PTHREAD_MUTEX_UNLOCK(&state_mutex);
425 * This has to be done in a mutex lock, because talloc
428 pairfilter(entry, &entry->vps, &request->state, 0, 0, TAG_ANY);
429 PTHREAD_MUTEX_UNLOCK(&state_mutex);
431 rad_assert(request->state == NULL);
432 VERIFY_REQUEST(request);
437 * Find the opaque data associated with a State attribute.
438 * Leave the data in the entry.
440 void *fr_state_find_data(UNUSED REQUEST *request, RADIUS_PACKET *packet)
443 state_entry_t *entry;
445 PTHREAD_MUTEX_LOCK(&state_mutex);
446 entry = fr_state_find(packet);
448 PTHREAD_MUTEX_UNLOCK(&state_mutex);
452 data = entry->opaque;
453 PTHREAD_MUTEX_UNLOCK(&state_mutex);
460 * Get the opaque data associated with a State attribute.
461 * and remove the data from the entry.
463 void *fr_state_get_data(UNUSED REQUEST *request, RADIUS_PACKET *packet)
466 state_entry_t *entry;
468 PTHREAD_MUTEX_LOCK(&state_mutex);
469 entry = fr_state_find(packet);
471 PTHREAD_MUTEX_UNLOCK(&state_mutex);
475 data = entry->opaque;
476 entry->opaque = NULL;
477 PTHREAD_MUTEX_UNLOCK(&state_mutex);
484 * Get the opaque data associated with a State attribute.
485 * and remove the data from the entry.
487 bool fr_state_put_data(UNUSED REQUEST *request, RADIUS_PACKET *original, RADIUS_PACKET *packet,
488 void *data, void (*free_data)(void *))
490 state_entry_t *entry, *old;
492 PTHREAD_MUTEX_LOCK(&state_mutex);
495 old = fr_state_find(original);
500 entry = fr_state_create(packet, old);
502 PTHREAD_MUTEX_UNLOCK(&state_mutex);
507 * If we're moving the data, ensure that we delete it
508 * from the old state.
510 if (old && (old->opaque == data)) {
514 entry->opaque = data;
515 entry->free_opaque = free_data;
517 PTHREAD_MUTEX_UNLOCK(&state_mutex);