The FreeRADIUS server $Id: f3670dba8951ca10eb4948feb3dc3db9423a334f $
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: 5e5897cd713f5bfc7c6bd04b3a47114008f3e85c $
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: 5e5897cd713f5bfc7c6bd04b3a47114008f3e85c $")
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 { FR_CONF_OFFSET("dedup_key", fr_state_config_t, dedup_key) },
65
67};
68
69
70/** Holds a state value, and associated fr_pair_ts and data
71 *
72 */
73typedef struct {
74 uint64_t id; //!< State number
75 fr_rb_node_t node; //!< Entry in the state rbtree.
76 union {
77 /** Server ID components
78 *
79 * State values should be unique to a given server
80 */
81 struct state_comp {
82 uint8_t tries; //!< Number of rounds so far in this state sequence.
83 uint8_t tx; //!< Bits changed in the tries counter for this round.
84 uint8_t r_0; //!< Random component.
85 uint8_t server_id; //!< Configured server ID. Used for debugging
86 //!< to locate authentication sessions originating
87 //!< from a particular backend authentication server.
88
89 uint32_t context_id; //!< Hash of the current virtual server, xor'd with
90 //!< r1, r2, r3, r4 after the original state value
91 //!< is sent, but before the state entry is inserted
92 //!< into the tree. The receiving virtual server
93 //!< xor's its hash with the received state before
94 //!< performing the lookup. This means one virtual
95 //!< server can't act on a state entry generated by
96 //!< another, even though the state tree is global
97 //!< to all virtual servers.
98
99 uint8_t vx_0; //!< Random component.
100 uint8_t r_1; //!< Random component.
101 uint8_t vx_1; //!< Random component.
102 uint8_t r_2; //!< Random component.
103
104 uint8_t vx_2; //!< Random component.
105 uint8_t vx_3; //!< Random component.
106 uint8_t r_3; //!< Random component.
107 uint8_t r_4; //!< Random component.
108 } state_comp;
109
110 uint8_t state[sizeof(struct state_comp)]; //!< State value in binary.
111 };
112
113 uint64_t seq_start; //!< Number of first request in this sequence.
114 fr_time_t cleanup; //!< When this entry should be cleaned up.
115
116 /*
117 * Should only even be in one at a time
118 */
119 union {
120 fr_dlist_t expire_entry; //!< Entry in the list of things to expire.
121 fr_dlist_t free_entry; //!< Entry in the list of things to free.
122 };
123
124 unsigned int tries;
125
126 fr_pair_t *ctx; //!< for all session specific data.
127
128 fr_dlist_head_t data; //!< Persistable request data, also parented by ctx.
129
130 request_t *thawed; //!< The request that thawed this entry.
131
132 fr_value_box_t const *dedup_key; //!< Key for dedup
133 fr_rb_node_t dedup_node; //!< Entry in the dedup rbtree
135
136/** A child of a fr_state_entry_t
137 *
138 * Children are tracked using the request data of parents.
139 *
140 * request data is added with identifiers that uniquely identify the
141 * subrequest it should be restored to.
142 *
143 * In this way a top level fr_state_entry_t can hold the session
144 * information for multiple children, and the children may hold
145 * state_child_entry_ts for grandchildren.
146 */
147typedef struct {
148 fr_pair_t *ctx; //!< for all session specific data.
149
150 fr_dlist_head_t data; //!< Persistable request data, also parented by ctx.
151
152 request_t *thawed; //!< The request that thawed this entry.
154
156 uint64_t id; //!< Next ID to assign.
157 uint64_t timed_out; //!< Number of states that were cleaned up due to
158 //!< timeout.
159 fr_state_config_t config; //!< a local copy
160
161 fr_rb_tree_t *tree; //!< rbtree used to lookup state value.
162 fr_rb_tree_t *dedup_tree; //!< rbtree used to do dedups
163 fr_dlist_head_t to_expire; //!< Linked list of entries to free.
164
165 pthread_mutex_t mutex; //!< Synchronisation mutex.
166
167 fr_dict_attr_t const *da; //!< Attribute where the state is stored.
168};
169
170#define PTHREAD_MUTEX_LOCK if (state->config.thread_safe) pthread_mutex_lock
171#define PTHREAD_MUTEX_UNLOCK if (state->config.thread_safe) pthread_mutex_unlock
172
173static void state_entry_unlink(fr_state_tree_t *state, fr_state_entry_t *entry);
174
175/** Compare two fr_state_entry_t based on their state value i.e. the value of the attribute
176 *
177 */
178static fr_cmp_ret_t state_entry_cmp(void const *one, void const *two)
179{
180 fr_state_entry_t const *a = one, *b = two;
181 int ret;
182
183 ret = memcmp(a->state, b->state, sizeof(a->state));
184 return CMP(ret, 0);
185}
186
187/** Compare two fr_state_entry_t based on their dedup key
188 *
189 */
190static fr_cmp_ret_t state_dedup_cmp(void const *one, void const *two)
191{
192 fr_state_entry_t const *a = one, *b = two;
193
194 return fr_value_box_cmp(a->dedup_key, b->dedup_key);
195}
196
197
198/** Free the state tree
199 *
200 */
202{
203 fr_state_entry_t *entry;
204
205 if (state->config.thread_safe) pthread_mutex_destroy(&state->mutex);
206
207 DEBUG4("Freeing state tree %p", state);
208
209 while ((entry = fr_dlist_head(&state->to_expire)) != NULL) {
210 state_entry_unlink(state, entry);
211 DEBUG4("Freeing state entry %p (%" PRIu64 ")", entry, entry->id);
212 talloc_free(entry);
213 }
214
215 /*
216 * Free the rbtree
217 */
218 talloc_free(state->tree);
219
220 return 0;
221}
222
223/** Initialise a new state tree
224 *
225 * @param[in] ctx to link the lifecycle of the state tree to.
226 * @param[in] da Attribute used to store and retrieve state from.
227 * @param[in] config the configuration data
228 * @return
229 * - A new state tree.
230 * - NULL on failure.
231 */
233{
234 fr_state_tree_t *state;
235
236 /*
237 * We can only handle 'octets' types.
238 */
239 if (da->type != FR_TYPE_OCTETS) {
240 fr_strerror_printf("Input state attribute '%s' has data type %s instead of 'octets'",
241 da->name, fr_type_to_str(da->type));
242 return NULL;
243 }
244
245 state = talloc_zero(NULL, fr_state_tree_t);
246 if (!state) return 0;
247
248 state->config = *config;
249 state->da = da; /* Remember which attribute we use to load/store state */
250
251 /*
252 * Some systems may start a new session before closing
253 * out the old one. The dedup key lets us find
254 * pre-existing sessions, and close them out.
255 */
256 if (config->dedup_key) {
257 if ((!tmpl_is_attr(config->dedup_key) &&
258 !tmpl_is_xlat(config->dedup_key)) ||
259 tmpl_needs_resolving(config->dedup_key)) {
260 fr_strerror_const("Invalid value for \"dedup_key\" - it must be an attribute reference or a simple expansion");
261 talloc_free(state);
262 return NULL;
263 }
264
265 if (tmpl_async_required(config->dedup_key)) {
266 fr_strerror_const("Invalid value for \"dedup_key\" - it must be a simple expansion, and cannot query external systems such as databases");
267 talloc_free(state);
268 return NULL;
269 }
270 }
271
272 /*
273 * Create a break in the contexts.
274 * We still want this to be freed at the same time
275 * as the parent, but we also need it to be thread
276 * safe, and multiple threads could be using the
277 * tree.
278 */
279 talloc_link_ctx(ctx, state);
280
281 if (state->config.thread_safe && (pthread_mutex_init(&state->mutex, NULL) != 0)) {
282 talloc_free(state);
283 return NULL;
284 }
285
286 fr_dlist_talloc_init(&state->to_expire, fr_state_entry_t, free_entry);
287
288 /*
289 * We need to do controlled freeing of the
290 * rbtree, so that all the state entries
291 * are freed before it's destroyed. Hence
292 * it being parented from the NULL ctx.
293 */
295 if (!state->tree) {
296 talloc_free(state);
297 return NULL;
298 }
299 talloc_set_destructor(state, _state_tree_free);
300
301 if (config->dedup_key) {
302 state->dedup_tree = fr_rb_inline_talloc_alloc(state->tree, fr_state_entry_t, dedup_node, state_dedup_cmp, NULL);
303 if (!state->dedup_tree) {
304 talloc_free(state);
305 return NULL;
306 }
307 }
308
309 return state;
310}
311
312/** Unlink an entry and remove if from the tree
313 *
314 */
315static inline CC_HINT(always_inline)
317{
318 /*
319 * Check the memory is still valid
320 */
321 (void) talloc_get_type_abort(entry, fr_state_entry_t);
322
323 fr_dlist_remove(&state->to_expire, entry);
324 fr_rb_delete(state->tree, entry);
325 if (state->dedup_tree) fr_rb_delete(state->dedup_tree, entry);
326
327 DEBUG4("State ID %" PRIu64 " unlinked", entry->id);
328}
329
330/** Frees any data associated with a state
331 *
332 */
334{
335#ifdef WITH_VERIFY_PTR
336 /*
337 * Verify all state attributes are parented
338 * by the state context.
339 */
340 if (entry->ctx) {
341 fr_pair_list_foreach(&entry->ctx->children, vp) {
342 fr_assert(entry->ctx == talloc_parent(vp));
343 }
344 }
345
346 /*
347 * Ensure any request data is parented by us
348 * so we know it'll be cleaned up.
349 */
350 (void)fr_cond_assert(request_data_verify_parent(entry->ctx, &entry->data));
351#endif
352
353 /*
354 * Should also free any state attributes
355 */
356 if (entry->ctx) TALLOC_FREE(entry->ctx);
357
358 DEBUG4("State ID %" PRIu64 " freed", entry->id);
359
360 return 0;
361}
362
364{
365
366 uint64_t hash;
367
368 /*
369 * Use the supplied State if it's the correct size.
370 */
371 if (vb->vb_length == sizeof(entry->state)) {
372 memcpy(&entry->state, vb->vb_octets, vb->vb_length);
373 return;
374 }
375
376 /*
377 * Otherwise hash the data.
378 */
379 memset(&entry->state, 0, sizeof(entry->state));
380
381 hash = fr_hash64(vb->vb_octets, vb->vb_length);
382 memcpy(&entry->state, &hash, sizeof(hash));
383}
384
385/** Create a new state entry
386 *
387 * @note Called with the mutex held.
388 */
390 fr_pair_list_t *reply_list, fr_state_entry_t *old,
391 fr_value_box_t const *dedup_key)
392{
393 fr_time_t now = fr_time();
394 fr_pair_t *vp;
395 fr_state_entry_t *entry;
396
397 uint64_t timed_out = 0;
398 bool too_many = false;
399 fr_dlist_head_t to_free;
400
401 /*
402 * If we have a previous entry, then it can't be in an
403 * expiry list, and it can't be in the list of states
404 * where we have sent a reply.
405 */
406 fr_assert(!old ||
407 (!fr_dlist_entry_in_list(&old->expire_entry) &&
409
410 /*
411 * If we have a previous entry and a dedup_tree, then we
412 * must have a dedup key, AND the entry must be in the
413 * dedup tree.
414 */
415 fr_assert(!old || !state->dedup_tree || (old->dedup_key && fr_rb_node_inline_in_tree(&old->dedup_node)));
416
417 /*
418 * If there is an old entry, we can't have a dedup_key.
419 */
420 fr_assert(!old || !dedup_key);
421
422 /*
423 * We track a separate free list, as we have to check
424 * expiration with the mutex locked. But we want to free
425 * things with the mutex unlocked.
426 */
427 fr_dlist_init(&to_free, fr_state_entry_t, free_entry);
428
429 /*
430 * Clean up expired entries which have not finished. If
431 * the request fails, then the corresponding entry is
432 * discarded. So the expiration list is only for entries
433 * which have been half-started, and then (many seconds
434 * later) haven't seen a "next" packet.
435 */
436 fr_dlist_foreach(&state->to_expire, fr_state_entry_t, expires) {
437 (void)talloc_get_type_abort(expires, fr_state_entry_t); /* Allow examination */
438
439 /*
440 * It's active (and asserted so above), so it can't be in the expiry list.
441 */
442 fr_assert(expires != old);
443
444 /*
445 * Too old, we can delete it.
446 */
447 if (fr_time_lt(expires->cleanup, now)) {
448 state_entry_unlink(state, expires);
449 fr_dlist_insert_tail(&to_free, expires);
450 timed_out++;
451 continue;
452 }
453
454 break;
455 }
456
457 if (!old) {
458 /*
459 * We're inserting a new session. Limit the
460 * number of sessions based on how many are in
461 * the RB tree. If at least one session has
462 * timed out, then we can definitely add a new
463 * session.
464 *
465 * Note that sessions being processed are removed
466 * from the tree. This means that the maximum
467 * number of sessions might actually be
468 * max_session+num_workers. In practice this
469 * shouldn't be a problem.
470 */
471 too_many = (fr_rb_num_elements(state->tree) >= state->config.max_sessions) && (timed_out == 0);
472
473 /*
474 * If there is a previous session for the same dedup key, then remove the old one from
475 * the dedup tree.
476 */
477 if (dedup_key) {
478 fr_state_entry_t *unfinished;
479
480 /*
481 * A comparator error means we can't tell whether a
482 * previous session exists. The insert into the dedup
483 * tree below fails for the same reason, refusing the
484 * new entry, so here we only report.
485 */
486 if (unlikely(fr_rb_find((void **)&unfinished, state->dedup_tree,
487 &(fr_state_entry_t) { .dedup_key = dedup_key }) < 0)) {
488 RPEDEBUG("Failed checking for an existing session with the same dedup_key");
489 } else if (unfinished) {
490 state_entry_unlink(state, unfinished);
491 fr_dlist_insert_tail(&to_free, unfinished);
492 }
493 }
494 }
495
497
498 if (timed_out > 0) {
499 RWDEBUG("Cleaning up %"PRIu64" timed out state entries", timed_out);
500 state->timed_out += timed_out;
501
502 /*
503 * Now free the unlinked entries.
504 *
505 * We do it here as freeing may involve significantly more
506 * work than just freeing the data.
507 *
508 * If there's request data that was persisted it will now
509 * be freed also, and it may have complex destructors associated
510 * with it.
511 */
512 fr_dlist_talloc_free(&to_free);
513
514 } else if (too_many) {
515 talloc_const_free(dedup_key);
516 RERROR("Failed inserting state entry - At maximum ongoing session limit (%u)",
517 state->config.max_sessions);
518 return NULL;
519 }
520
521 /*
522 * Allocation doesn't need to occur inside the critical region
523 * and would add significantly to contention.
524 */
525 if (!old) {
526 MEM(entry = talloc_zero(NULL, fr_state_entry_t));
527 talloc_set_destructor(entry, _state_entry_free);
528
529 entry->id = state->id++;
530
531 } else {
532 fr_assert(!old->ctx);
533 entry = old;
534 }
535
537
538 /*
539 * Limit the lifetime of this entry based on how long the
540 * server takes to process a request. Doing it this way
541 * isn't perfect, but it's reasonable, and it's one less
542 * thing for an administrator to configure.
543 */
544 entry->cleanup = fr_time_add(now, state->config.timeout);
545
546 /*
547 * Some modules either create their own state, or need to
548 * synthesize it from data in a packet header. If we
549 * have such a state, then use that in preference to
550 * creating a random one.
551 */
552 vp = fr_pair_find_by_da(reply_list, NULL, state->da);
553 if (vp && vp->vp_length) {
554 state_entry_fill(entry, &vp->data);
555
556 } else {
557 if (old) {
558 /*
559 * Just re-use the old state.
560 */
561 entry->tries++;
562
563 if (entry->tries > state->config.max_rounds) {
564 RERROR("Failed tracking state entry - too many rounds (%u)", entry->tries);
565 goto fail;
566 }
567 } else {
568 size_t i;
570
571 if (dedup_key) entry->dedup_key = talloc_steal(entry, dedup_key);
572
573 /*
574 * Get a bunch of random numbers.
575 */
576 for (i = 0; i < sizeof(entry->state); i+= 4) {
577 hash = fr_rand();
578 memcpy(&entry->state[i], &hash, sizeof(hash));
579 }
580
581 /*
582 * Add in a server ID. This lets a "FreeRADIUS
583 * aware" load balancer direct the packet based
584 * on the contents of the State attribute.
585 */
586 entry->state_comp.server_id = state->config.server_id;
587
588 /*
589 * Add our own custom brand of magic.
590 */
591 entry->state_comp.vx_0 = entry->state_comp.r_0 ^
592 ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 24) & 0xff);
593 entry->state_comp.vx_1 = entry->state_comp.r_0 ^
594 ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 16) & 0xff);
595 entry->state_comp.vx_2 = entry->state_comp.r_0 ^
596 ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 8) & 0xff);
597 entry->state_comp.vx_3 = entry->state_comp.r_0 ^
598 (((uint32_t) HEXIFY(RADIUSD_VERSION)) & 0xff);
599 }
600
601 /*
602 * Track the number of round trips, too.
603 */
604 entry->state_comp.tx ^= entry->tries;
605 entry->state_comp.tries = entry->tries ^ entry->state_comp.r_3;
606
607 MEM(vp = fr_pair_afrom_da(request->reply_ctx, state->da));
608 fr_pair_value_memdup(vp, entry->state, sizeof(entry->state), false);
609 fr_pair_append(reply_list, vp);
610 }
611
612 DEBUG4("State ID %" PRIu64 " created, value 0x%pH, expires %pV",
613 entry->id, &vp->data,
615
616 /*
617 * XOR the server hash with four bytes of random context
618 * ID after adding it to the reply, but before inserting
619 * it into the RB rtree. We XOR is again before looking
620 * it up in the tree, to ensure state lookups only
621 * succeed in the virtual server that created the state
622 * value.
623 */
624 entry->state_comp.context_id ^= state->config.context_id;
625
626 PTHREAD_MUTEX_LOCK(&state->mutex);
627
628 if (fr_rb_insert(state->tree, entry) != 0) {
629 fail_unlock:
631 RERROR("Failed inserting state entry - Insertion into state tree failed");
632 fail:
633 fr_pair_delete_by_da(reply_list, state->da);
634 talloc_free(entry);
635 return NULL;
636 }
637
638 /*
639 * Ensure that we can de-duplicate things if the supplicant is misbehaving.
640 */
641 if (state->dedup_tree && !old) {
642 if (fr_rb_insert(state->dedup_tree, entry) != 0) {
643 (void) fr_rb_remove(NULL, state->tree, entry);
644 goto fail_unlock;
645 }
646 }
647
648 /*
649 * Link it to the end of the list, which is implicitly
650 * ordered by cleanup time.
651 */
652 fr_dlist_insert_tail(&state->to_expire, entry);
653
654 entry->thawed = NULL;
655
656 return entry;
657}
658
659/** Find the entry based on the State attribute and remove it from the state tree
660 *
661 */
663{
664 fr_state_entry_t *entry, my_entry;
665
666 state_entry_fill(&my_entry, vb);
667
668 /*
669 * Make it unique for different virtual servers handling the same request
670 */
671 my_entry.state_comp.context_id ^= state->config.context_id;
672
673 fr_rb_remove((void **)&entry, state->tree, &my_entry);
674 if (entry) {
675 (void) talloc_get_type_abort(entry, fr_state_entry_t);
676 fr_dlist_remove(&state->to_expire, entry);
677 }
678
679 return entry;
680}
681
682
683/** Called when sending an Access-Accept/Access-Reject to discard state information
684 *
685 * @param[in] state tree to lookup state in.
686 * @param[in] request to discard state for.
687 */
689{
690 fr_state_entry_t *entry;
691
692 /*
693 * The caller MUST have called fr_state_restore() before
694 * calling this function. If so, there is request data
695 * that points to the state entry.
696 *
697 * This function should only be called from the "outer"
698 * request. Any child request should call
699 * fr_state_discard_child()
700 *
701 * Relying on request data also means that the user can
702 * nuke request.State, and the code will still work.
703 *
704 * Find a pointer to the entry, but leave the request
705 * data associated with the entry. That way when the
706 * request is freed, the entry will also be freed.
707 */
708 entry = request_data_reference(request, state, 0);
709 if (!entry) return;
710
711 /*
712 * Unlink the entry to shrink the state tree, and make
713 * sure that the state is never re-used.
714 *
715 * However, we don't wipe the session-state list, as the
716 * request can still be processed through a "finally"
717 * section. And we want the session state data to be
718 * usable from there.
719 */
720 PTHREAD_MUTEX_LOCK(&state->mutex);
721 state_entry_unlink(state, entry);
723
724 return;
725}
726
727/** Called to discard a state which is synthesized
728 *
729 * Some protocols synthesize a state, which means that there is the
730 * possibility for conflict. i.e. an old state exists which needs to
731 * be discarded. The current request can't restore the old state, it
732 * instead needs to discard it.
733 *
734 * @param[in] state tree to lookup state in.
735 * @param[in] request to discard state for.
736 */
738{
739 fr_pair_t *vp;
740 fr_state_entry_t *entry;
741
742 vp = fr_pair_find_by_da(&request->request_pairs, NULL, state->da);
743 if (!vp) return;
744
745 PTHREAD_MUTEX_LOCK(&state->mutex);
746 entry = state_entry_find_and_unlink(state, &vp->data);
748
749 if (entry) talloc_free(entry);
750}
751
752/** Copy a pointer to the head of the list of state fr_pair_ts (and their ctx) into the request
753 *
754 * @note Does not copy the actual fr_pair_ts. The fr_pair_ts and their context
755 * are transferred between state entries as the conversation progresses.
756 *
757 * @note Called with the mutex free.
758 *
759 * @param[in] state tree to lookup state in.
760 * @param[in] request to restore state for.
761 * @return
762 * - 2 if the state attribute didn't match any known states.
763 * - 1 if no state attribute existed.
764 * - 0 on success (state restored)
765 * - -1 if a state entry has already been thawed by a another request.
766 */
768{
769 fr_state_entry_t *entry;
770 fr_pair_t *vp;
771
772 /*
773 * No State, don't do anything.
774 */
775 vp = fr_pair_find_by_da(&request->request_pairs, NULL, state->da);
776 if (!vp) {
777 RDEBUG3("No request.%s attribute, can't restore session-state", state->da->name);
778 if (request->seq_start == 0) request->seq_start = request->number; /* Need check for fake requests */
779 return 1;
780 }
781
782 PTHREAD_MUTEX_LOCK(&state->mutex);
783 entry = state_entry_find_and_unlink(state, &vp->data);
785 if (!entry) {
786 RDEBUG2("No state entry matching request.%pP found", vp);
787 return 2;
788 }
789
790 /* Probably impossible in the current code */
791 if (unlikely(entry->thawed && (entry->thawed != request))) {
792 RERROR("State entry has already been thawed by a request %"PRIu64, entry->thawed->number);
793 return -2;
794 }
795
796 /*
797 * Discard any existing session state, and replace it
798 * with the cached one.
799 */
800 fr_assert(entry->ctx);
801 talloc_free(request_state_replace(request, entry->ctx));
802 entry->ctx = NULL;
803
804 request->seq_start = entry->seq_start;
805
806 /*
807 * Associate old state with the request
808 *
809 * If the request is freed, it's freed immediately.
810 *
811 * Otherwise, if there's another round, we reuse
812 * the state entry and insert it back into the
813 * tree.
814 */
815 request_data_add(request, state, 0, entry, true, true, false);
816 request_data_restore(request, &entry->data);
817
818 entry->thawed = request;
819
820 if (!fr_pair_list_empty(&request->session_state_pairs)) {
821 RDEBUG2("Restored session-state");
822 log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->session_state_pairs, "session-state.");
823 }
824
825 RDEBUG3("%s - restored", state->da->name);
826
827 /*
828 * Set sequence so that we can prioritize ongoing multi-packet sessions.
829 */
830 request->sequence = entry->tries;
831 REQUEST_VERIFY(request);
832 return 0;
833}
834
835
836/** Transfer ownership of the state fr_pair_ts and ctx, back to a state entry
837 *
838 * Put request->session_state_pairs into the State attribute. Put the State attribute
839 * into the vps list. Delete the original entry, if it exists
840 *
841 * Also creates a new state entry.
842 */
844{
845 fr_state_entry_t *entry, *old;
847 fr_pair_t *state_ctx;
848 fr_value_box_t *dedup_key = NULL;
849
850 old = request_data_get(request, state, 0);
852 request_data_by_persistance(&data, request, true);
853
854 if (fr_pair_list_empty(&request->session_state_pairs) && fr_dlist_empty(&data)) return 0;
855
856 if (!fr_pair_list_empty(&request->session_state_pairs)) {
857 RDEBUG2("Saving session-state");
858 log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->session_state_pairs, "session-state.");
859
860#ifdef WITH_VERIFY_PTR
861 /*
862 * Double check all the session state pairs
863 * are parented correctly, else we'll get
864 * memory errors when we restore.
865 */
866 fr_pair_list_verify(__FILE__, __LINE__, request->session_state_ctx, &request->session_state_pairs, true);
867#endif
868 }
869
870 /*
871 * If there's a dedup tree, then we need to expand the
872 * key, but only if we don't already have a pre-existing state.
873 */
874 if (state->dedup_tree && !old) {
875 fr_value_box_list_t list;
876
877 fr_value_box_list_init(&list);
878
879 if (tmpl_eval(NULL, &list, request, state->config.dedup_key) < 0) {
880 REDEBUG("Failed expanding dedup_key - not doing dedup");
881 } else {
882 dedup_key = fr_value_box_list_pop_head(&list);
883 if (!dedup_key) {
884 RDEBUG("Failed expanding dedup_key - not doing dedup due to empty output");
885 }
886 fr_value_box_list_talloc_free_head(&list);
887 }
888 }
889
890 MEM(state_ctx = request_state_replace(request, NULL));
891
892 /*
893 * Reuses old if possible, and leaves the mutex unlocked on failure.
894 */
895 PTHREAD_MUTEX_LOCK(&state->mutex);
896 entry = state_entry_create(state, request, &request->reply_pairs, old, dedup_key);
897 if (!entry) {
898 talloc_free(request_state_replace(request, state_ctx));
899 request_data_restore(request, &data); /* Put it back again */
900
901#ifdef __COVERITY__
902 /*
903 * Coverity doesn't see that state_entry_create releases
904 * the lock on failure
905 */
907#endif
908 return -1;
909 }
910
911 fr_assert(entry->ctx == NULL);
912 fr_assert(request->session_state_ctx);
913
914 entry->seq_start = request->seq_start;
915 entry->ctx = state_ctx;
916 fr_dlist_move(&entry->data, &data);
918
919 RDEBUG3("%s - saved", state->da->name);
920 REQUEST_VERIFY(request);
921
922 return 0;
923}
924
925/** Free any subrequest request data if the dlist head is freed
926 *
927 */
928static int _free_child_data(state_child_entry_t *child_entry)
929{
930 fr_dlist_talloc_free(&child_entry->data);
931 talloc_free(child_entry->ctx); /* Free the child's session_state_ctx if we own it */
932
933 return 0;
934}
935
936/** Store subrequest's session-state list and persistable request data in its parent
937 *
938 * @param[in] child The child request to retrieve state from.
939 * @param[in] unique_ptr A parent may have multiple subrequests spawned
940 * by different modules. This identifies the module
941 * or other facility that spawned the subrequest.
942 * @param[in] unique_int Further identification.
943 */
944void fr_state_store_in_parent(request_t *child, void const *unique_ptr, int unique_int)
945{
946 state_child_entry_t *child_entry;
947 request_t *request = child; /* Stupid logging */
948
949 if (!fr_cond_assert_msg(child->parent,
950 "Child request must have request->parent set when storing state")) return;
951
952 RDEBUG3("Storing subrequest state in request %s", child->parent->name);
953
954 if ((request_data_by_persistance_count(request, true) > 0) ||
955 !fr_pair_list_empty(&request->session_state_pairs)) {
956 MEM(child_entry = talloc_zero(request->parent->session_state_ctx, state_child_entry_t));
957 request_data_list_init(&child_entry->data);
958 talloc_set_destructor(child_entry, _free_child_data);
959
960 child_entry->ctx = request_state_replace(child, NULL);
961
962 /*
963 * Pull everything out of the child,
964 * add it to our temporary list head...
965 *
966 * request_data_add allocs persistable
967 * request dta in the session_state_ctx
968 * which is why we don't need to copy or
969 * reparent any of this.
970 */
971 request_data_by_persistance(&child_entry->data, request, true);
972
973 /*
974 * ...and add the request_data from
975 * the child back into the parent.
976 */
977 request_data_talloc_add(request->parent, unique_ptr, unique_int,
978 state_child_entry_t, child_entry, true, false, true);
979 }
980}
981
982/** Restore subrequest data from a parent request
983 *
984 * @param[in] child The child request to restore state to.
985 * @param[in] unique_ptr A parent may have multiple subrequests spawned
986 * by different modules. This identifies the module
987 * or other facility that spawned the subrequest.
988 * @param[in] unique_int Further identification.
989 */
990void fr_state_restore_from_parent(request_t *child, void const *unique_ptr, int unique_int)
991{
992 state_child_entry_t *child_entry;
993 request_t *request = child; /* Stupid logging */
994
995 if (!fr_cond_assert_msg(child->parent,
996 "Child request must have request->parent set when restoring state")) return;
997
998
999 child_entry = request_data_get(child->parent, unique_ptr, unique_int);
1000 if (!child_entry) {
1001 RDEBUG3("No child state found in parent %s", child->parent->name);
1002 return;
1003 }
1004
1005 /*
1006 * Shouldn't really be possible unless
1007 * there's a logic bug in this API.
1008 */
1009 if (!fr_cond_assert_msg(!child_entry->thawed,
1010 "Child state entry already thawed by %s - %p",
1011 child_entry->thawed->name, child_entry->thawed)) return;
1012
1013 RDEBUG3("Restoring subrequest state from request %s", child->parent->name);
1014
1015 /*
1016 * If we can restore from the parent, do so
1017 */
1018 fr_assert_msg(child_entry->ctx, "session child entry missing ctx");
1019 talloc_free(request_state_replace(child, child_entry->ctx));
1020 child_entry->ctx = NULL; /* No longer owns the ctx */
1021 child_entry->thawed = child;
1022
1023 request_data_restore(child, &child_entry->data); /* Put all the request data back */
1024
1025 talloc_free(child_entry);
1026}
1027
1028/** Remove state from a child
1029 *
1030 * This is useful for modules like EAP, where we keep a persistent eap_session
1031 * but may call multiple EAP method modules during negotiation, and need to
1032 * discard the state between each module call.
1033 *
1034 * @param[in] parent Holding the child's state.
1035 * @param[in] unique_ptr A parent may have multiple subrequests spawned
1036 * by different modules. This identifies the module
1037 * or other facility that spawned the subrequest.
1038 * @param[in] unique_int Further identification.
1039 */
1040void fr_state_discard_child(request_t *parent, void const *unique_ptr, int unique_int)
1041{
1042 state_child_entry_t *child_entry;
1043 request_t *request = parent; /* Stupid logging */
1044
1045 child_entry = request_data_get(parent, unique_ptr, unique_int);
1046 if (!child_entry) {
1047 RDEBUG3("No child state found in parent %s", parent->name);
1048 return;
1049 }
1050
1051 talloc_free(child_entry);
1052}
#define HEXIFY(b1)
Definition build.h:209
#define RCSID(id)
Definition build.h:560
#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:113
#define unlikely(_x)
Definition build.h:455
#define CONF_PARSER_TERMINATOR
Definition cf_parse.h:673
#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:280
Defines a CONF_PAIR to C data type mapping.
Definition cf_parse.h:610
#define fr_cond_assert(_x)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition debug.h:177
#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:248
#define fr_cond_assert_msg(_x, _fmt,...)
Calls panic_action ifndef NDEBUG, else logs error and evaluates to value of _x.
Definition debug.h:194
#define MEM(x)
Definition debug.h:38
#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
#define fr_dlist_foreach(_list_head, _type, _iter)
Iterate over the contents of a list.
Definition dlist.h:98
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:892
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
Head of a doubly linked list.
Definition dlist.h:51
Entry in a doubly linked list.
Definition dlist.h:41
uint64_t fr_hash64(void const *data, size_t size)
Definition hash.c:959
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:827
#define RWDEBUG(fmt,...)
Definition log.h:378
#define RDEBUG3(fmt,...)
Definition log.h:360
#define RERROR(fmt,...)
Definition log.h:315
#define DEBUG4(_fmt,...)
Definition log.h:272
#define RPEDEBUG(fmt,...)
Definition log.h:393
#define fr_time()
Definition event.c:60
@ L_DBG_LVL_2
2nd highest priority debug messages (-xx | -X).
Definition log.h:68
@ FR_TYPE_OCTETS
Raw octets.
unsigned int uint32_t
unsigned char uint8_t
fr_cmp_ret_t
Result of an ordering comparison.
Definition misc.h:50
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:2894
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:708
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:1298
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:1642
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:291
static const conf_parser_t config[]
Definition base.c:162
#define fr_assert(_expr)
Definition rad_assert.h:37
#define REDEBUG(fmt,...)
#define RDEBUG2(fmt,...)
#define RDEBUG(fmt,...)
uint32_t fr_rand(void)
Return a 32-bit random number.
Definition rand.c:104
uint32_t fr_rb_num_elements(fr_rb_tree_t *tree)
Return how many nodes there are in a tree.
Definition rb.c:807
int fr_rb_remove(void **removed, fr_rb_tree_t *tree, void const *data)
Remove an entry from the tree, without freeing the data.
Definition rb.c:718
int fr_rb_find(void **found, fr_rb_tree_t const *tree, void const *data)
Find an element in the tree, returning the data, not the node.
Definition rb.c:586
int 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:767
int fr_rb_insert(fr_rb_tree_t *tree, void const *data)
Insert data into a tree.
Definition rb.c:637
#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:244
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:312
The main red black tree structure.
Definition rb.h:71
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:513
#define REQUEST_VERIFY(_x)
Definition request.h:310
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.
static unsigned int hash(char const *username, unsigned int tablesize)
Definition rlm_passwd.c:132
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:767
uint64_t id
State number.
Definition state.c:74
void fr_state_discard_child(request_t *parent, void const *unique_ptr, int unique_int)
Remove state from a child.
Definition state.c:1040
uint64_t seq_start
Number of first request in this sequence.
Definition state.c:113
fr_dlist_head_t data
Persistable request data, also parented by ctx.
Definition state.c:128
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:688
#define PTHREAD_MUTEX_UNLOCK
Definition state.c:171
#define PTHREAD_MUTEX_LOCK
Definition state.c:170
static fr_cmp_ret_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:178
fr_dict_attr_t const * da
Attribute where the state is stored.
Definition state.c:167
fr_dlist_head_t data
Persistable request data, also parented by ctx.
Definition state.c:150
unsigned int tries
Definition state.c:124
fr_rb_node_t node
Entry in the state rbtree.
Definition state.c:75
request_t * thawed
The request that thawed this entry.
Definition state.c:152
fr_time_t cleanup
When this entry should be cleaned up.
Definition state.c:114
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:843
static int _state_tree_free(fr_state_tree_t *state)
Free the state tree.
Definition state.c:201
static int _state_entry_free(fr_state_entry_t *entry)
Frees any data associated with a state.
Definition state.c:333
fr_pair_t * ctx
for all session specific data.
Definition state.c:126
pthread_mutex_t mutex
Synchronisation mutex.
Definition state.c:165
static void state_entry_fill(fr_state_entry_t *entry, fr_value_box_t const *vb)
Definition state.c:363
fr_pair_t * ctx
for all session specific data.
Definition state.c:148
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:316
fr_rb_tree_t * tree
rbtree used to lookup state value.
Definition state.c:161
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:990
fr_dlist_head_t to_expire
Linked list of entries to free.
Definition state.c:163
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:662
static fr_cmp_ret_t state_dedup_cmp(void const *one, void const *two)
Compare two fr_state_entry_t based on their dedup key.
Definition state.c:190
const conf_parser_t state_session_config[]
Definition state.c:59
fr_rb_node_t dedup_node
Entry in the dedup rbtree.
Definition state.c:133
void fr_state_discard_by_state(fr_state_tree_t *state, request_t *request)
Called to discard a state which is synthesized.
Definition state.c:737
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:232
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:928
fr_value_box_t const * dedup_key
Key for dedup.
Definition state.c:132
fr_rb_tree_t * dedup_tree
rbtree used to do dedups
Definition state.c:162
uint64_t timed_out
Number of states that were cleaned up due to timeout.
Definition state.c:157
static fr_state_entry_t * state_entry_create(fr_state_tree_t *state, request_t *request, fr_pair_list_t *reply_list, fr_state_entry_t *old, fr_value_box_t const *dedup_key)
Create a new state entry.
Definition state.c:389
fr_state_config_t config
a local copy
Definition state.c:159
uint64_t id
Next ID to assign.
Definition state.c:156
request_t * thawed
The request that thawed this entry.
Definition state.c:130
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:944
Holds a state value, and associated fr_pair_ts and data.
Definition state.c:73
A child of a fr_state_entry_t.
Definition state.c:147
#define tmpl_is_xlat(vpt)
Definition tmpl.h:217
#define tmpl_is_attr(vpt)
Definition tmpl.h:215
bool tmpl_async_required(tmpl_t const *vpt)
Return whether or not async is required for this tmpl.
int tmpl_eval(TALLOC_CTX *ctx, fr_value_box_list_t *out, request_t *request, tmpl_t const *vpt)
Gets the value of a tmpl.
Definition tmpl_eval.c:1104
#define tmpl_needs_resolving(vpt)
Definition tmpl.h:230
bool thread_safe
Definition state.h:47
fr_time_delta_t timeout
idle timeout
Definition state.h:44
uint32_t max_rounds
maximum number of rounds before we give up
Definition state.h:42
uint8_t server_id
for mangling State
Definition state.h:46
tmpl_t * dedup_key
for tracking misbehaving supplicants
Definition state.h:45
uint32_t context_id
internal number to help keep state trees separate
Definition state.h:43
uint32_t max_sessions
maximum number of sessions
Definition state.h:41
fr_pair_t * vp
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:168
static int talloc_const_free(void const *ptr)
Free const'd memory.
Definition talloc.h:288
#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_list_foreach(_list_head, _iter)
Iterate over the contents of a fr_pair_list_t.
Definition pair.h:281
static fr_slen_t parent
Definition pair.h:860
#define fr_strerror_printf(_fmt,...)
Log to thread local error buffer.
Definition strerror.h:64
#define fr_strerror_const(_msg)
Definition strerror.h:223
static char const * fr_type_to_str(fr_type_t type)
Return a static string containing the type name.
Definition types.h:454
fr_cmp_ret_t fr_value_box_cmp(fr_value_box_t const *a, fr_value_box_t const *b)
Compare two values.
Definition value.c:761
static fr_slen_t data
Definition value.h:1367
#define fr_box_time_delta(_val)
Definition value.h:391