The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
state.c
Go to the documentation of this file.
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: 23cd5cca6bc49c5922556b99c991f0beba32110a $
19 *
20 * @brief Multi-packet state handling
21 * @file src/lib/server/state.c
22 *
23 * @ingroup AVP
24 *
25 * For each round of a multi-round authentication method such as EAP,
26 * or a 2FA method such as OTP, a state entry will be created. The state
27 * entry holds data that should be available during the complete lifecycle
28 * of the authentication attempt.
29 *
30 * When a request is complete, #fr_state_store is called to transfer
31 * ownership of the state fr_pair_ts and state_ctx (which the fr_pair_ts
32 * are allocated in) to a #fr_state_entry_t. This #fr_state_entry_t holds the
33 * value of the State attribute, that will be send out in the response.
34 *
35 * When the next request is received, #fr_state_restore is called to transfer
36 * the fr_pair_ts and state ctx to the new request.
37 *
38 * The ownership of the state_ctx and state fr_pair_ts is transferred as below:
39 *
40 * @verbatim
41 request -> state_entry -> request -> state_entry -> request -> free()
42 \-> reply \-> reply \-> access-reject/access-accept
43 * @endverbatim
44 *
45 * @copyright 2014 The FreeRADIUS server project
46 */
47RCSID("$Id: 23cd5cca6bc49c5922556b99c991f0beba32110a $")
48
49#include <freeradius-devel/server/request.h>
50#include <freeradius-devel/server/request_data.h>
51#include <freeradius-devel/server/state.h>
52
53#include <freeradius-devel/io/listen.h>
54
55#include <freeradius-devel/util/debug.h>
56#include <freeradius-devel/util/md5.h>
57#include <freeradius-devel/util/rand.h>
58
60 { FR_CONF_OFFSET("timeout", fr_state_config_t, timeout), .dflt = "15" },
61 { FR_CONF_OFFSET("max", fr_state_config_t, max_sessions), .dflt = "4096" },
62 { FR_CONF_OFFSET("max_rounds", fr_state_config_t, max_rounds), .dflt = "50" },
63 { FR_CONF_OFFSET("state_server_id", fr_state_config_t, server_id) },
64
66};
67
68
69/** Holds a state value, and associated fr_pair_ts and data
70 *
71 */
72typedef struct {
73 uint64_t id; //!< State number within state heap.
74 fr_rb_node_t node; //!< Entry in the state rbtree.
75 union {
76 /** Server ID components
77 *
78 * State values should be unique to a given server
79 */
80 struct state_comp {
81 uint8_t tries; //!< Number of rounds so far in this state sequence.
82 uint8_t tx; //!< Bits changed in the tries counter for this round.
83 uint8_t r_0; //!< Random component.
84 uint8_t server_id; //!< Configured server ID. Used for debugging
85 //!< to locate authentication sessions originating
86 //!< from a particular backend authentication server.
87
88 uint32_t context_id; //!< Hash of the current virtual server, xor'd with
89 //!< r1, r2, r3, r4 after the original state value
90 //!< is sent, but before the state entry is inserted
91 //!< into the tree. The receiving virtual server
92 //!< xor's its hash with the received state before
93 //!< performing the lookup. This means one virtual
94 //!< server can't act on a state entry generated by
95 //!< another, even though the state tree is global
96 //!< to all virtual servers.
97
98 uint8_t vx_0; //!< Random component.
99 uint8_t r_5; //!< Random component.
100 uint8_t vx_1; //!< Random component.
101 uint8_t r_6; //!< Random component.
102
103 uint8_t vx_2; //!< Random component.
104 uint8_t vx_3; //!< Random component.
105 uint8_t r_8; //!< Random component.
106 uint8_t r_9; //!< Random component.
107 } state_comp;
108
109 uint8_t state[sizeof(struct state_comp)]; //!< State value in binary.
110 };
111
112 uint64_t seq_start; //!< Number of first request in this sequence.
113 fr_time_t cleanup; //!< When this entry should be cleaned up.
114
115 /*
116 * Should only even be in one at a time
117 */
118 union {
119 fr_dlist_t expire_entry; //!< Entry in the list of things to expire.
120 fr_dlist_t free_entry; //!< Entry in the list of things to free.
121 };
122
123 unsigned int tries;
124
125 fr_pair_t *ctx; //!< for all session specific data.
126
127 fr_dlist_head_t data; //!< Persistable request data, also parented by ctx.
128
129 request_t *thawed; //!< The request that thawed this entry.
131
132/** A child of a fr_state_entry_t
133 *
134 * Children are tracked using the request data of parents.
135 *
136 * request data is added with identifiers that uniquely identify the
137 * subrequest it should be restored to.
138 *
139 * In this way a top level fr_state_entry_t can hold the session
140 * information for multiple children, and the children may hold
141 * state_child_entry_ts for grandchildren.
142 */
143typedef struct {
144 fr_pair_t *ctx; //!< for all session specific data.
145
146 fr_dlist_head_t data; //!< Persistable request data, also parented by ctx.
147
148 request_t *thawed; //!< The request that thawed this entry.
150
152 uint64_t id; //!< Next ID to assign.
153 uint64_t timed_out; //!< Number of states that were cleaned up due to
154 //!< timeout.
155 fr_state_config_t config; //!< a local copy
156
157 fr_rb_tree_t *tree; //!< rbtree used to lookup state value.
158 fr_dlist_head_t to_expire; //!< Linked list of entries to free.
159
160 pthread_mutex_t mutex; //!< Synchronisation mutex.
161
162 fr_dict_attr_t const *da; //!< Attribute where the state is stored.
163};
164
165#define PTHREAD_MUTEX_LOCK if (state->config.thread_safe) pthread_mutex_lock
166#define PTHREAD_MUTEX_UNLOCK if (state->config.thread_safe) pthread_mutex_unlock
167
168static void state_entry_unlink(fr_state_tree_t *state, fr_state_entry_t *entry);
169
170/** Compare two fr_state_entry_t based on their state value i.e. the value of the attribute
171 *
172 */
173static int8_t state_entry_cmp(void const *one, void const *two)
174{
175 fr_state_entry_t const *a = one, *b = two;
176 int ret;
177
178 ret = memcmp(a->state, b->state, sizeof(a->state));
179 return CMP(ret, 0);
180}
181
182/** Free the state tree
183 *
184 */
186{
187 fr_state_entry_t *entry;
188
189 if (state->config.thread_safe) pthread_mutex_destroy(&state->mutex);
190
191 DEBUG4("Freeing state tree %p", state);
192
193 while ((entry = fr_dlist_head(&state->to_expire))) {
194 DEBUG4("Freeing state entry %p (%"PRIu64")", entry, entry->id);
195 state_entry_unlink(state, entry);
196 talloc_free(entry);
197 }
198
199 /*
200 * Free the rbtree
201 */
202 talloc_free(state->tree);
203
204 return 0;
205}
206
207/** Initialise a new state tree
208 *
209 * @param[in] ctx to link the lifecycle of the state tree to.
210 * @param[in] da Attribute used to store and retrieve state from.
211 * @param[in] config the configuration data
212 * @return
213 * - A new state tree.
214 * - NULL on failure.
215 */
217{
218 fr_state_tree_t *state;
219
220 /*
221 * We can only handle 'octets' types.
222 */
223 if (da->type != FR_TYPE_OCTETS) {
224 fr_strerror_printf("Input state attribute '%s' has data type %s instead of 'octets'",
225 da->name, fr_type_to_str(da->type));
226 return NULL;
227 }
228
229 state = talloc_zero(NULL, fr_state_tree_t);
230 if (!state) return 0;
231
232 state->config = *config;
233 state->da = da; /* Remember which attribute we use to load/store state */
234
235 /*
236 * Create a break in the contexts.
237 * We still want this to be freed at the same time
238 * as the parent, but we also need it to be thread
239 * safe, and multiple threads could be using the
240 * tree.
241 */
242 talloc_link_ctx(ctx, state);
243
244 if (state->config.thread_safe && (pthread_mutex_init(&state->mutex, NULL) != 0)) {
245 talloc_free(state);
246 return NULL;
247 }
248
249 fr_dlist_talloc_init(&state->to_expire, fr_state_entry_t, free_entry);
250
251 /*
252 * We need to do controlled freeing of the
253 * rbtree, so that all the state entries
254 * are freed before it's destroyed. Hence
255 * it being parented from the NULL ctx.
256 */
258 if (!state->tree) {
259 talloc_free(state);
260 return NULL;
261 }
262 talloc_set_destructor(state, _state_tree_free);
263
264 return state;
265}
266
267/** Unlink an entry and remove if from the tree
268 *
269 */
270static inline CC_HINT(always_inline)
272{
273 /*
274 * Check the memory is still valid
275 */
276 (void) talloc_get_type_abort(entry, fr_state_entry_t);
277
278 fr_dlist_remove(&state->to_expire, entry);
279 fr_rb_delete(state->tree, entry);
280
281 DEBUG4("State ID %" PRIu64 " unlinked", entry->id);
282}
283
284/** Frees any data associated with a state
285 *
286 */
288{
289#ifdef WITH_VERIFY_PTR
290 fr_dcursor_t cursor;
291 fr_pair_t *vp;
292
293 /*
294 * Verify all state attributes are parented
295 * by the state context.
296 */
297 if (entry->ctx) {
298 for (vp = fr_pair_dcursor_init(&cursor, &entry->ctx->children);
299 vp;
300 vp = fr_dcursor_next(&cursor)) {
301 fr_assert(entry->ctx == talloc_parent(vp));
302 }
303 }
304
305 /*
306 * Ensure any request data is parented by us
307 * so we know it'll be cleaned up.
308 */
309 (void)fr_cond_assert(request_data_verify_parent(entry->ctx, &entry->data));
310#endif
311
312 /*
313 * Should also free any state attributes
314 */
315 if (entry->ctx) TALLOC_FREE(entry->ctx);
316
317 DEBUG4("State ID %" PRIu64 " freed", entry->id);
318
319 return 0;
320}
321
322/** Create a new state entry
323 *
324 * @note Called with the mutex held.
325 */
327 fr_pair_list_t *reply_list, fr_state_entry_t *old)
328{
329 size_t i;
330 uint32_t x;
331 fr_time_t now = fr_time();
332 fr_pair_t *vp;
333 fr_state_entry_t *entry, *next;
334
335 uint64_t timed_out = 0;
336 bool too_many = false;
337 fr_dlist_head_t to_free;
338
339 /*
340 * Shouldn't be in any lists if it's being reused
341 */
342 fr_assert(!old ||
343 (!fr_dlist_entry_in_list(&old->expire_entry) &&
345
346 fr_dlist_init(&to_free, fr_state_entry_t, free_entry);
347
348 /*
349 * Clean up expired entries
350 */
351 for (entry = fr_dlist_head(&state->to_expire);
352 entry != NULL;
353 entry = next) {
354 (void)talloc_get_type_abort(entry, fr_state_entry_t); /* Allow examination */
355 next = fr_dlist_next(&state->to_expire, entry); /* Advance *before* potential unlinking */
356
357 if (entry == old) continue;
358
359 /*
360 * Too old, we can delete it.
361 */
362 if (fr_time_lt(entry->cleanup, now)) {
363 state_entry_unlink(state, entry);
364 fr_dlist_insert_tail(&to_free, entry);
365 timed_out++;
366 continue;
367 }
368
369 break;
370 }
371
372 state->timed_out += timed_out;
373
374 if (!old) {
375 /*
376 * We're inserting a new session. Limit the
377 * number of sessions based on how many are in
378 * the RB tree. If at least one session has
379 * timed out, then we can definitely add a new
380 * session.
381 *
382 * Note that sessions being processed are removed
383 * from the tree. This means that the maximum
384 * number of sessions might actually be
385 * max_session+num_workers. In practice this
386 * shouldn't be a problem.
387 */
388 too_many = (fr_rb_num_elements(state->tree) >= state->config.max_sessions) && (timed_out == 0);
389 }
390
392
393 if (timed_out > 0) {
394 RWDEBUG("Cleaning up %"PRIu64" timed out state entries", timed_out);
395
396 /*
397 * Now free the unlinked entries.
398 *
399 * We do it here as freeing may involve significantly more
400 * work than just freeing the data.
401 *
402 * If there's request data that was persisted it will now
403 * be freed also, and it may have complex destructors associated
404 * with it.
405 */
406 while ((entry = fr_dlist_head(&to_free)) != NULL) {
407 fr_dlist_remove(&to_free, entry);
408 talloc_free(entry);
409 }
410
411 } else if (too_many) {
412 RERROR("Failed inserting state entry - At maximum ongoing session limit (%u)",
413 state->config.max_sessions);
414 return NULL;
415 }
416
417 /*
418 * Allocation doesn't need to occur inside the critical region
419 * and would add significantly to contention.
420 */
421 if (!old) {
422 MEM(entry = talloc_zero(NULL, fr_state_entry_t));
423 talloc_set_destructor(entry, _state_entry_free);
424
425 entry->id = state->id++;
426
427 } else {
428 fr_assert(!old->ctx);
429 entry = old;
430 }
431
433
434 /*
435 * Limit the lifetime of this entry based on how long the
436 * server takes to process a request. Doing it this way
437 * isn't perfect, but it's reasonable, and it's one less
438 * thing for an administrator to configure.
439 */
440 entry->cleanup = fr_time_add(now, state->config.timeout);
441
442 /*
443 * Some modules create their own magic
444 * state attributes. If a state value already exists
445 * int the reply, we use that in preference to the
446 * old state.
447 */
448 vp = fr_pair_find_by_da(reply_list, NULL, state->da);
449 if (vp) {
450 if (DEBUG_ENABLED && (vp->vp_length > sizeof(entry->state))) {
451 WARN("State too long, will be truncated. Expected <= %zd bytes, got %zu bytes",
452 sizeof(entry->state), vp->vp_length);
453 }
454
455 /*
456 * Assume our own State first.
457 */
458 if (vp->vp_length == sizeof(entry->state)) {
459 memcpy(entry->state, vp->vp_octets, sizeof(entry->state));
460
461 /*
462 * Too big? Get the MD5 hash, in order
463 * to depend on the entire contents of State.
464 */
465 } else if (vp->vp_length > sizeof(entry->state)) {
466 fr_md5_calc(entry->state, vp->vp_octets, vp->vp_length);
467
468 /*
469 * Too small? Use the whole thing, and
470 * set the rest of my_entry.state to zero.
471 */
472 } else {
473 memcpy(entry->state, vp->vp_octets, vp->vp_length);
474 memset(&entry->state[vp->vp_length], 0, sizeof(entry->state) - vp->vp_length);
475 }
476 } else {
477 /*
478 * Base the new state on the old state if we had one.
479 */
480 if (old) {
481 entry->tries++;
482
483 if (entry->tries > state->config.max_rounds) {
484 RERROR("Failed tracking state entry - too many rounds (%u)", entry->tries);
485 goto fail;
486 }
487
488
489 /*
490 * 16 octets of randomness should be enough to
491 * have a globally unique state.
492 */
493 } else {
494 for (i = 0; i < sizeof(entry->state) / sizeof(x); i++) {
495 x = fr_rand();
496 memcpy(entry->state + (i * 4), &x, sizeof(x));
497 }
498 }
499
500 entry->state_comp.tries = entry->tries + 1;
501
502 entry->state_comp.tx = entry->state_comp.tries ^ entry->tries;
503
504 entry->state_comp.vx_0 = entry->state_comp.r_0 ^
505 ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 24) & 0xff);
506 entry->state_comp.vx_1 = entry->state_comp.r_0 ^
507 ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 16) & 0xff);
508 entry->state_comp.vx_2 = entry->state_comp.r_0 ^
509 ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 8) & 0xff);
510 entry->state_comp.vx_3 = entry->state_comp.r_0 ^
511 (((uint32_t) HEXIFY(RADIUSD_VERSION)) & 0xff);
512
513 /*
514 * Allow a portion of the State attribute to be set,
515 * this is useful for debugging purposes.
516 */
517 entry->state_comp.server_id = state->config.server_id;
518
519 MEM(vp = fr_pair_afrom_da(request->reply_ctx, state->da));
520 fr_pair_value_memdup(vp, entry->state, sizeof(entry->state), false);
521 fr_pair_append(reply_list, vp);
522 }
523
524 DEBUG4("State ID %" PRIu64 " created, value 0x%pH, expires %pV",
525 entry->id, fr_box_octets(entry->state, sizeof(entry->state)),
527
528 PTHREAD_MUTEX_LOCK(&state->mutex);
529
530 /*
531 * XOR the server hash with four bytes of random data.
532 * We XOR is again before resolving, to ensure state lookups
533 * only succeed in the virtual server that created the state
534 * value.
535 */
536 *((uint32_t *)(&entry->state_comp.context_id)) ^= state->config.context_id;
537
538 if (!fr_rb_insert(state->tree, entry)) {
540 RERROR("Failed inserting state entry - Insertion into state tree failed");
541 fail:
542 fr_pair_delete_by_da(reply_list, state->da);
543 talloc_free(entry);
544 return NULL;
545 }
546
547 /*
548 * Link it to the end of the list, which is implicitly
549 * ordered by cleanup time.
550 */
551 fr_dlist_insert_tail(&state->to_expire, entry);
552
553 entry->thawed = NULL;
554
555 return entry;
556}
557
558/** Find the entry based on the State attribute and remove it from the state tree
559 *
560 */
562{
563 fr_state_entry_t *entry, my_entry;
564
565 /*
566 * Assume our own State first.
567 */
568 if (vb->vb_length == sizeof(my_entry.state)) {
569 memcpy(my_entry.state, vb->vb_octets, sizeof(my_entry.state));
570
571 /*
572 * Too big? Get the MD5 hash, in order
573 * to depend on the entire contents of State.
574 */
575 } else if (vb->vb_length > sizeof(my_entry.state)) {
576 fr_md5_calc(my_entry.state, vb->vb_octets, vb->vb_length);
577
578 /*
579 * Too small? Use the whole thing, and
580 * set the rest of my_entry.state to zero.
581 */
582 } else {
583 memcpy(my_entry.state, vb->vb_octets, vb->vb_length);
584 memset(&my_entry.state[vb->vb_length], 0, sizeof(my_entry.state) - vb->vb_length);
585 }
586
587 /*
588 * Make it unique for different virtual servers handling the same request
589 */
590 my_entry.state_comp.context_id ^= state->config.context_id;
591
592 entry = fr_rb_remove(state->tree, &my_entry);
593 if (entry) {
594 (void) talloc_get_type_abort(entry, fr_state_entry_t);
595 fr_dlist_remove(&state->to_expire, entry);
596 }
597
598 return entry;
599}
600
601
602/** Called when sending an Access-Accept/Access-Reject to discard state information
603 *
604 */
606{
607 fr_state_entry_t *entry;
608
609 /*
610 * The caller MUST have called fr_state_restore() before
611 * calling this function. If so, there is request data
612 * that points to the state entry.
613 *
614 * This function should only be called from the "outer"
615 * request. Any child request should call
616 * fr_state_discard_child()
617 *
618 * Relying on request data also means that the user can
619 * nuke request.State, and the code will still work.
620 *
621 * Find a pointer to the entry, but leave the request
622 * data associated with the entry. That way when the
623 * request is freed, the entry will also be freed.
624 */
625 entry = request_data_reference(request, state, 0);
626 if (!entry) return;
627
628 /*
629 * Unlink the entry to shrink the state tree, and make
630 * sure that the state is never re-used.
631 *
632 * However, we don't wipe the session-state list, as the
633 * request can still be processed through a "finally"
634 * section. And we want the session state data to be
635 * usable from there.
636 */
637 PTHREAD_MUTEX_LOCK(&state->mutex);
638 state_entry_unlink(state, entry);
640
641 return;
642}
643
644/** Copy a pointer to the head of the list of state fr_pair_ts (and their ctx) into the request
645 *
646 * @note Does not copy the actual fr_pair_ts. The fr_pair_ts and their context
647 * are transferred between state entries as the conversation progresses.
648 *
649 * @note Called with the mutex free.
650 *
651 * @param[in] state tree to lookup state in.
652 * @param[in] request to restore state for.
653 * @return
654 * - 2 if the state attribute didn't match any known states.
655 * - 1 if no state attribute existed.
656 * - 0 on success (state restored)
657 * - -1 if a state entry has already been thawed by a another request.
658 */
660{
661 fr_state_entry_t *entry;
662 fr_pair_t *vp;
663
664 /*
665 * No State, don't do anything.
666 */
667 vp = fr_pair_find_by_da(&request->request_pairs, NULL, state->da);
668 if (!vp) {
669 RDEBUG3("No request.%s attribute, can't restore session-state", state->da->name);
670 if (request->seq_start == 0) request->seq_start = request->number; /* Need check for fake requests */
671 return 1;
672 }
673
674 PTHREAD_MUTEX_LOCK(&state->mutex);
675 entry = state_entry_find_and_unlink(state, &vp->data);
677 if (!entry) {
678 RDEBUG2("No state entry matching request.%pP found", vp);
679 return 2;
680 }
681
682 /* Probably impossible in the current code */
683 if (unlikely(entry->thawed && (entry->thawed != request))) {
684 RERROR("State entry has already been thawed by a request %"PRIu64, entry->thawed->number);
685 return -2;
686 }
687
688 /*
689 * Discard any existing session state, and replace it
690 * with the cached one.
691 */
692 fr_assert(entry->ctx);
693 talloc_free(request_state_replace(request, entry->ctx));
694 entry->ctx = NULL;
695
696 request->seq_start = entry->seq_start;
697
698 /*
699 * Associate old state with the request
700 *
701 * If the request is freed, it's freed immediately.
702 *
703 * Otherwise, if there's another round, we reuse
704 * the state entry and insert it back into the
705 * tree.
706 */
707 request_data_add(request, state, 0, entry, true, true, false);
708 request_data_restore(request, &entry->data);
709
710 entry->thawed = request;
711
712 if (!fr_pair_list_empty(&request->session_state_pairs)) {
713 RDEBUG2("Restored session-state");
714 log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->session_state_pairs, "session-state.");
715 }
716
717 RDEBUG3("%s - restored", state->da->name);
718
719 /*
720 * Set sequence so that we can prioritize ongoing multi-packet sessions.
721 */
722 request->sequence = entry->tries;
723 REQUEST_VERIFY(request);
724 return 0;
725}
726
727
728/** Transfer ownership of the state fr_pair_ts and ctx, back to a state entry
729 *
730 * Put request->session_state_pairs into the State attribute. Put the State attribute
731 * into the vps list. Delete the original entry, if it exists
732 *
733 * Also creates a new state entry.
734 */
736{
737 fr_state_entry_t *entry, *old;
739 fr_pair_t *state_ctx;
740
741 old = request_data_get(request, state, 0);
743 request_data_by_persistance(&data, request, true);
744
745 if (fr_pair_list_empty(&request->session_state_pairs) && fr_dlist_empty(&data)) return 0;
746
747 if (!fr_pair_list_empty(&request->session_state_pairs)) {
748 RDEBUG2("Saving session-state");
749 log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->session_state_pairs, "session-state.");
750
751#ifdef WITH_VERIFY_PTR
752 /*
753 * Double check all the session state pairs
754 * are parented correctly, else we'll get
755 * memory errors when we restore.
756 */
757 fr_pair_list_verify(__FILE__, __LINE__, request->session_state_ctx, &request->session_state_pairs, true);
758#endif
759 }
760
761 MEM(state_ctx = request_state_replace(request, NULL));
762
763 /*
764 * Reuses old if possible, and leaves the mutex unlocked on failure.
765 */
766 PTHREAD_MUTEX_LOCK(&state->mutex);
767 entry = state_entry_create(state, request, &request->reply_pairs, old);
768 if (!entry) {
770 talloc_free(request_state_replace(request, state_ctx));
771 request_data_restore(request, &data); /* Put it back again */
772 return -1;
773 }
774
775 fr_assert(entry->ctx == NULL);
776 fr_assert(request->session_state_ctx);
777
778 entry->seq_start = request->seq_start;
779 entry->ctx = state_ctx;
780 fr_dlist_move(&entry->data, &data);
782
783 RDEBUG3("%s - saved", state->da->name);
784 REQUEST_VERIFY(request);
785
786 return 0;
787}
788
789/** Free any subrequest request data if the dlist head is freed
790 *
791 */
792static int _free_child_data(state_child_entry_t *child_entry)
793{
794 fr_dlist_talloc_free(&child_entry->data);
795 talloc_free(child_entry->ctx); /* Free the child's session_state_ctx if we own it */
796
797 return 0;
798}
799
800/** Store subrequest's session-state list and persistable request data in its parent
801 *
802 * @param[in] child The child request to retrieve state from.
803 * @param[in] unique_ptr A parent may have multiple subrequests spawned
804 * by different modules. This identifies the module
805 * or other facility that spawned the subrequest.
806 * @param[in] unique_int Further identification.
807 */
808void fr_state_store_in_parent(request_t *child, void const *unique_ptr, int unique_int)
809{
810 state_child_entry_t *child_entry;
811 request_t *request = child; /* Stupid logging */
812
813 if (!fr_cond_assert_msg(child->parent,
814 "Child request must have request->parent set when storing state")) return;
815
816 RDEBUG3("Storing subrequest state in request %s", child->parent->name);
817
818 if ((request_data_by_persistance_count(request, true) > 0) ||
819 !fr_pair_list_empty(&request->session_state_pairs)) {
820 MEM(child_entry = talloc_zero(request->parent->session_state_ctx, state_child_entry_t));
821 request_data_list_init(&child_entry->data);
822 talloc_set_destructor(child_entry, _free_child_data);
823
824 child_entry->ctx = request_state_replace(child, NULL);
825
826 /*
827 * Pull everything out of the child,
828 * add it to our temporary list head...
829 *
830 * request_data_add allocs persistable
831 * request dta in the session_state_ctx
832 * which is why we don't need to copy or
833 * reparent any of this.
834 */
835 request_data_by_persistance(&child_entry->data, request, true);
836
837 /*
838 * ...and add the request_data from
839 * the child back into the parent.
840 */
841 request_data_talloc_add(request->parent, unique_ptr, unique_int,
842 state_child_entry_t, child_entry, true, false, true);
843 }
844}
845
846/** Restore subrequest data from a parent request
847 *
848 * @param[in] child The child request to restore state to.
849 * @param[in] unique_ptr A parent may have multiple subrequests spawned
850 * by different modules. This identifies the module
851 * or other facility that spawned the subrequest.
852 * @param[in] unique_int Further identification.
853 */
854void fr_state_restore_from_parent(request_t *child, void const *unique_ptr, int unique_int)
855{
856 state_child_entry_t *child_entry;
857 request_t *request = child; /* Stupid logging */
858
859 if (!fr_cond_assert_msg(child->parent,
860 "Child request must have request->parent set when restoring state")) return;
861
862
863 child_entry = request_data_get(child->parent, unique_ptr, unique_int);
864 if (!child_entry) {
865 RDEBUG3("No child state found in parent %s", child->parent->name);
866 return;
867 }
868
869 /*
870 * Shouldn't really be possible unless
871 * there's a logic bug in this API.
872 */
873 if (!fr_cond_assert_msg(!child_entry->thawed,
874 "Child state entry already thawed by %s - %p",
875 child_entry->thawed->name, child_entry->thawed)) return;
876
877 RDEBUG3("Restoring subrequest state from request %s", child->parent->name);
878
879 /*
880 * If we can restore from the parent, do so
881 */
882 fr_assert_msg(child_entry->ctx, "session child entry missing ctx");
883 talloc_free(request_state_replace(child, child_entry->ctx));
884 child_entry->ctx = NULL; /* No longer owns the ctx */
885 child_entry->thawed = child;
886
887 request_data_restore(child, &child_entry->data); /* Put all the request data back */
888
889 talloc_free(child_entry);
890}
891
892/** Remove state from a child
893 *
894 * This is useful for modules like EAP, where we keep a persistent eap_session
895 * but may call multiple EAP method modules during negotiation, and need to
896 * discard the state between each module call.
897 *
898 * @param[in] parent Holding the child's state.
899 * @param[in] unique_ptr A parent may have multiple subrequests spawned
900 * by different modules. This identifies the module
901 * or other facility that spawned the subrequest.
902 * @param[in] unique_int Further identification.
903 */
904void fr_state_discard_child(request_t *parent, void const *unique_ptr, int unique_int)
905{
906 state_child_entry_t *child_entry;
907 request_t *request = parent; /* Stupid logging */
908
909 child_entry = request_data_get(parent, unique_ptr, unique_int);
910 if (!child_entry) {
911 RDEBUG3("No child state found in parent %s", parent->name);
912 return;
913 }
914
915 talloc_free(child_entry);
916}
#define HEXIFY(b1)
Definition build.h:190
#define RCSID(id)
Definition build.h:487
#define CMP(_a, _b)
Same as CMP_PREFER_SMALLER use when you don't really care about ordering, you just want an ordering.
Definition build.h:112
#define unlikely(_x)
Definition build.h:383
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:660
#define FR_CONF_OFFSET(_name, _struct, _field)
conf_parser_t which parses a single CONF_PAIR, writing the result to a field in a struct
Definition cf_parse.h:283
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:597
static void * fr_dcursor_next(fr_dcursor_t *cursor)
Advanced the cursor to the next item.
Definition dcursor.h:290
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition debug.h:131
#define fr_assert_msg(_x, _msg,...)
Calls panic_action ifndef NDEBUG, else logs error and causes the server to exit immediately with code...
Definition debug.h:202
#define fr_cond_assert_msg(_x, _fmt,...)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition debug.h:148
#define MEM(x)
Definition debug.h:36
#define fr_dlist_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition dlist.h:242
static void * fr_dlist_head(fr_dlist_head_t const *list_head)
Return the HEAD item of a list or NULL if the list is empty.
Definition dlist.h:468
static void * fr_dlist_remove(fr_dlist_head_t *list_head, void *ptr)
Remove an item from the list.
Definition dlist.h:620
static bool fr_dlist_entry_in_list(fr_dlist_t const *entry)
Check if a list entry is part of a list.
Definition dlist.h:145
static void fr_dlist_talloc_free(fr_dlist_head_t *head)
Free all items in a doubly linked list (with talloc)
Definition dlist.h:890
static bool fr_dlist_empty(fr_dlist_head_t const *list_head)
Check whether a list has any items.
Definition dlist.h:483
static int fr_dlist_insert_tail(fr_dlist_head_t *list_head, void *ptr)
Insert an item into the tail of a list.
Definition dlist.h:360
static int fr_dlist_move(fr_dlist_head_t *list_dst, fr_dlist_head_t *list_src)
Merge two lists, inserting the source at the tail of the destination.
Definition dlist.h:745
#define fr_dlist_talloc_init(_head, _type, _field)
Initialise the head structure of a doubly linked list.
Definition dlist.h:257
static void * fr_dlist_next(fr_dlist_head_t const *list_head, void const *ptr)
Get the next item in a list.
Definition dlist.h:537
Head of a doubly linked list.
Definition dlist.h:51
Entry in a doubly linked list.
Definition dlist.h:41
talloc_free(hp)
void log_request_pair_list(fr_log_lvl_t lvl, request_t *request, fr_pair_t const *parent, fr_pair_list_t const *vps, char const *prefix)
Print a fr_pair_list_t.
Definition log.c:821
#define RWDEBUG(fmt,...)
Definition log.h:361
#define RDEBUG3(fmt,...)
Definition log.h:343
#define RERROR(fmt,...)
Definition log.h:298
#define DEBUG4(_fmt,...)
Definition log.h:267
#define DEBUG_ENABLED
True if global debug level 1 messages are enabled.
Definition log.h:257
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition log.h:71
@ FR_TYPE_OCTETS
Raw octets.
unsigned int uint32_t
void fr_md5_calc(uint8_t out[static MD5_DIGEST_LENGTH], uint8_t const *in, size_t inlen)
Perform a single digest operation on a single input buffer.
unsigned char uint8_t
int fr_pair_value_memdup(fr_pair_t *vp, uint8_t const *src, size_t len, bool tainted)
Copy data into an "octets" data type.
Definition pair.c:2944
fr_pair_t * fr_pair_find_by_da(fr_pair_list_t const *list, fr_pair_t const *prev, fr_dict_attr_t const *da)
Find the first pair with a matching da.
Definition pair.c:703
int fr_pair_append(fr_pair_list_t *list, fr_pair_t *to_add)
Add a VP to the end of the list.
Definition pair.c:1348
int fr_pair_delete_by_da(fr_pair_list_t *list, fr_dict_attr_t const *da)
Delete matching pairs from the specified list.
Definition pair.c:1692
fr_pair_t * fr_pair_afrom_da(TALLOC_CTX *ctx, fr_dict_attr_t const *da)
Dynamically allocate a new attribute and assign a fr_dict_attr_t.
Definition pair.c:289
static const conf_parser_t config[]
Definition base.c:172
#define fr_assert(_expr)
Definition rad_assert.h:38
#define RDEBUG2(fmt,...)
Definition radclient.h:54
#define WARN(fmt,...)
Definition radclient.h:47
uint32_t fr_rand(void)
Return a 32-bit random number.
Definition rand.c:105
uint32_t fr_rb_num_elements(fr_rb_tree_t *tree)
Return how many nodes there are in a tree.
Definition rb.c:781
void * fr_rb_remove(fr_rb_tree_t *tree, void const *data)
Remove an entry from the tree, without freeing the data.
Definition rb.c:695
bool fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
Definition rb.c:626
bool fr_rb_delete(fr_rb_tree_t *tree, void const *data)
Remove node and free data (if a free function was specified)
Definition rb.c:741
#define fr_rb_inline_talloc_alloc(_ctx, _type, _field, _data_cmp, _data_free)
Allocs a red black that verifies elements are of a specific talloc type.
Definition rb.h:246
static bool fr_rb_node_inline_in_tree(fr_rb_node_t const *node)
Check to see if an item is in a tree by examining its inline fr_rb_node_t.
Definition rb.h:314
The main red black tree structure.
Definition rb.h:73
fr_pair_t * request_state_replace(request_t *request, fr_pair_t *new_state)
Replace the session_state_ctx with a new one.
Definition request.c:511
#define REQUEST_VERIFY(_x)
Definition request.h:309
int request_data_by_persistance_count(request_t *request, bool persist)
Return how many request data entries exist of a given persistence.
int request_data_by_persistance(fr_dlist_head_t *out, request_t *request, bool persist)
Loop over all the request data, pulling out ones matching persist state.
void request_data_list_init(fr_dlist_head_t *data)
void request_data_restore(request_t *request, fr_dlist_head_t *in)
Add request data back to a request.
void * request_data_reference(request_t *request, void const *unique_ptr, int unique_int)
Get opaque data from a request without removing it.
void * request_data_get(request_t *request, void const *unique_ptr, int unique_int)
Get opaque data from a request.
#define request_data_talloc_add(_request, _unique_ptr, _unique_int, _type, _opaque, _free_on_replace, _free_on_parent, _persist)
Add opaque data to a request_t.
#define request_data_add(_request, _unique_ptr, _unique_int, _opaque, _free_on_replace, _free_on_parent, _persist)
Add opaque data to a request_t.
int fr_state_restore(fr_state_tree_t *state, request_t *request)
Copy a pointer to the head of the list of state fr_pair_ts (and their ctx) into the request.
Definition state.c:659
uint64_t id
State number within state heap.
Definition state.c:73
void fr_state_discard_child(request_t *parent, void const *unique_ptr, int unique_int)
Remove state from a child.
Definition state.c:904
uint64_t seq_start
Number of first request in this sequence.
Definition state.c:112
fr_dlist_head_t data
Persistable request data, also parented by ctx.
Definition state.c:127
void fr_state_discard(fr_state_tree_t *state, request_t *request)
Called when sending an Access-Accept/Access-Reject to discard state information.
Definition state.c:605
#define PTHREAD_MUTEX_UNLOCK
Definition state.c:166
#define PTHREAD_MUTEX_LOCK
Definition state.c:165
fr_dict_attr_t const * da
Attribute where the state is stored.
Definition state.c:162
fr_dlist_head_t data
Persistable request data, also parented by ctx.
Definition state.c:146
unsigned int tries
Definition state.c:123
fr_rb_node_t node
Entry in the state rbtree.
Definition state.c:74
request_t * thawed
The request that thawed this entry.
Definition state.c:148
fr_time_t cleanup
When this entry should be cleaned up.
Definition state.c:113
int fr_state_store(fr_state_tree_t *state, request_t *request)
Transfer ownership of the state fr_pair_ts and ctx, back to a state entry.
Definition state.c:735
static int _state_tree_free(fr_state_tree_t *state)
Free the state tree.
Definition state.c:185
static int _state_entry_free(fr_state_entry_t *entry)
Frees any data associated with a state.
Definition state.c:287
static int8_t state_entry_cmp(void const *one, void const *two)
Compare two fr_state_entry_t based on their state value i.e.
Definition state.c:173
fr_pair_t * ctx
for all session specific data.
Definition state.c:125
pthread_mutex_t mutex
Synchronisation mutex.
Definition state.c:160
fr_pair_t * ctx
for all session specific data.
Definition state.c:144
static void state_entry_unlink(fr_state_tree_t *state, fr_state_entry_t *entry)
Unlink an entry and remove if from the tree.
Definition state.c:271
fr_rb_tree_t * tree
rbtree used to lookup state value.
Definition state.c:157
void fr_state_restore_from_parent(request_t *child, void const *unique_ptr, int unique_int)
Restore subrequest data from a parent request.
Definition state.c:854
fr_dlist_head_t to_expire
Linked list of entries to free.
Definition state.c:158
static fr_state_entry_t * state_entry_find_and_unlink(fr_state_tree_t *state, fr_value_box_t const *vb)
Find the entry based on the State attribute and remove it from the state tree.
Definition state.c:561
const conf_parser_t state_session_config[]
Definition state.c:59
fr_state_tree_t * fr_state_tree_init(TALLOC_CTX *ctx, fr_dict_attr_t const *da, fr_state_config_t const *config)
Initialise a new state tree.
Definition state.c:216
static int _free_child_data(state_child_entry_t *child_entry)
Free any subrequest request data if the dlist head is freed.
Definition state.c:792
uint64_t timed_out
Number of states that were cleaned up due to timeout.
Definition state.c:153
fr_state_config_t config
a local copy
Definition state.c:155
uint64_t id
Next ID to assign.
Definition state.c:152
request_t * thawed
The request that thawed this entry.
Definition state.c:129
void fr_state_store_in_parent(request_t *child, void const *unique_ptr, int unique_int)
Store subrequest's session-state list and persistable request data in its parent.
Definition state.c:808
Holds a state value, and associated fr_pair_ts and data.
Definition state.c:72
A child of a fr_state_entry_t.
Definition state.c:143
bool thread_safe
Definition state.h:45
fr_time_delta_t timeout
idle timeout
Definition state.h:43
uint32_t max_rounds
maximum number of rounds before we give up
Definition state.h:41
uint8_t server_id
for mangling State
Definition state.h:44
uint32_t context_id
internal number to help keep state trees separate
Definition state.h:42
uint32_t max_sessions
maximum number of sessions
Definition state.h:40
fr_pair_t * vp
#define fr_time()
Allow us to arbitrarily manipulate time.
Definition state_test.c:8
static void state_entry_create(void)
Test functions that read from dbuffs.
Definition state_test.c:15
Stores an attribute, a value and various bits of other data.
Definition pair.h:68
int talloc_link_ctx(TALLOC_CTX *parent, TALLOC_CTX *child)
Link two different parent and child contexts, so the child is freed before the parent.
Definition talloc.c:167
#define fr_time_add(_a, _b)
Add a time/time delta together.
Definition time.h:196
#define fr_time_sub(_a, _b)
Subtract one time from another.
Definition time.h:229
#define fr_time_lt(_a, _b)
Definition time.h:239
"server local" time.
Definition time.h:69
bool fr_pair_list_empty(fr_pair_list_t const *list)
Is a valuepair list empty.
#define fr_pair_dcursor_init(_cursor, _list)
Initialises a special dcursor with callbacks that will maintain the attr sublists correctly.
Definition pair.h:605
static fr_slen_t parent
Definition pair.h:859
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
static char const * fr_type_to_str(fr_type_t type)
Return a static string containing the type name.
Definition types.h:455
static fr_slen_t data
Definition value.h:1334
#define fr_box_time_delta(_val)
Definition value.h:366
#define fr_box_octets(_val, _len)
Definition value.h:311