/src/freeradius-server/src/lib/server/state.c
Line | Count | Source |
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: be6f1e625ab166732c9a1fcd12b77d32d9270604 $ |
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 | | */ |
47 | | RCSID("$Id: be6f1e625ab166732c9a1fcd12b77d32d9270604 $") |
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 | | |
59 | | const conf_parser_t state_session_config[] = { |
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 | | |
66 | | CONF_PARSER_TERMINATOR |
67 | | }; |
68 | | |
69 | | |
70 | | /** Holds a state value, and associated fr_pair_ts and data |
71 | | * |
72 | | */ |
73 | | typedef 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 |
134 | | } fr_state_entry_t; |
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 | | */ |
147 | | typedef 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. |
153 | | } state_child_entry_t; |
154 | | |
155 | | struct fr_state_tree_s { |
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 | 0 | #define PTHREAD_MUTEX_LOCK if (state->config.thread_safe) pthread_mutex_lock |
171 | 0 | #define PTHREAD_MUTEX_UNLOCK if (state->config.thread_safe) pthread_mutex_unlock |
172 | | |
173 | | static 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 | | */ |
178 | | static int8_t state_entry_cmp(void const *one, void const *two) |
179 | 0 | { |
180 | 0 | fr_state_entry_t const *a = one, *b = two; |
181 | 0 | int ret; |
182 | |
|
183 | 0 | ret = memcmp(a->state, b->state, sizeof(a->state)); |
184 | 0 | return CMP(ret, 0); |
185 | 0 | } |
186 | | |
187 | | /** Compare two fr_state_entry_t based on their dedup key |
188 | | * |
189 | | */ |
190 | | static int8_t state_dedup_cmp(void const *one, void const *two) |
191 | 0 | { |
192 | 0 | fr_state_entry_t const *a = one, *b = two; |
193 | |
|
194 | 0 | return fr_value_box_cmp(a->dedup_key, b->dedup_key); |
195 | 0 | } |
196 | | |
197 | | |
198 | | /** Free the state tree |
199 | | * |
200 | | */ |
201 | | static int _state_tree_free(fr_state_tree_t *state) |
202 | 0 | { |
203 | 0 | fr_state_entry_t *entry; |
204 | |
|
205 | 0 | if (state->config.thread_safe) pthread_mutex_destroy(&state->mutex); |
206 | |
|
207 | 0 | DEBUG4("Freeing state tree %p", state); |
208 | |
|
209 | 0 | while ((entry = fr_dlist_head(&state->to_expire)) != NULL) { |
210 | 0 | state_entry_unlink(state, entry); |
211 | 0 | DEBUG4("Freeing state entry %p (%" PRIu64 ")", entry, entry->id); |
212 | 0 | talloc_free(entry); |
213 | 0 | } |
214 | | |
215 | | /* |
216 | | * Free the rbtree |
217 | | */ |
218 | 0 | talloc_free(state->tree); |
219 | |
|
220 | 0 | return 0; |
221 | 0 | } |
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 | | */ |
232 | | fr_state_tree_t *fr_state_tree_init(TALLOC_CTX *ctx, fr_dict_attr_t const *da, fr_state_config_t const *config) |
233 | 0 | { |
234 | 0 | fr_state_tree_t *state; |
235 | | |
236 | | /* |
237 | | * We can only handle 'octets' types. |
238 | | */ |
239 | 0 | if (da->type != FR_TYPE_OCTETS) { |
240 | 0 | fr_strerror_printf("Input state attribute '%s' has data type %s instead of 'octets'", |
241 | 0 | da->name, fr_type_to_str(da->type)); |
242 | 0 | return NULL; |
243 | 0 | } |
244 | | |
245 | 0 | state = talloc_zero(NULL, fr_state_tree_t); |
246 | 0 | if (!state) return 0; |
247 | | |
248 | 0 | state->config = *config; |
249 | 0 | 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 | 0 | if (config->dedup_key) { |
257 | 0 | if ((!tmpl_is_attr(config->dedup_key) && |
258 | 0 | !tmpl_is_xlat(config->dedup_key)) || |
259 | 0 | tmpl_needs_resolving(config->dedup_key)) { |
260 | 0 | fr_strerror_const("Invalid value for \"dedup_key\" - it must be an attribute reference or a simple expansion"); |
261 | 0 | return NULL; |
262 | 0 | } |
263 | | |
264 | 0 | if (tmpl_async_required(config->dedup_key)) { |
265 | 0 | fr_strerror_const("Invalid value for \"dedup_key\" - it must be a simple expansion, and cannot query external systems such as databases"); |
266 | 0 | return NULL; |
267 | 0 | } |
268 | 0 | } |
269 | | |
270 | | /* |
271 | | * Create a break in the contexts. |
272 | | * We still want this to be freed at the same time |
273 | | * as the parent, but we also need it to be thread |
274 | | * safe, and multiple threads could be using the |
275 | | * tree. |
276 | | */ |
277 | 0 | talloc_link_ctx(ctx, state); |
278 | |
|
279 | 0 | if (state->config.thread_safe && (pthread_mutex_init(&state->mutex, NULL) != 0)) { |
280 | 0 | talloc_free(state); |
281 | 0 | return NULL; |
282 | 0 | } |
283 | | |
284 | 0 | fr_dlist_talloc_init(&state->to_expire, fr_state_entry_t, free_entry); |
285 | | |
286 | | /* |
287 | | * We need to do controlled freeing of the |
288 | | * rbtree, so that all the state entries |
289 | | * are freed before it's destroyed. Hence |
290 | | * it being parented from the NULL ctx. |
291 | | */ |
292 | 0 | state->tree = fr_rb_inline_talloc_alloc(NULL, fr_state_entry_t, node, state_entry_cmp, NULL); |
293 | 0 | if (!state->tree) { |
294 | 0 | talloc_free(state); |
295 | 0 | return NULL; |
296 | 0 | } |
297 | 0 | talloc_set_destructor(state, _state_tree_free); |
298 | |
|
299 | 0 | if (config->dedup_key) { |
300 | 0 | state->dedup_tree = fr_rb_inline_talloc_alloc(state->tree, fr_state_entry_t, dedup_node, state_dedup_cmp, NULL); |
301 | 0 | if (!state->dedup_tree) { |
302 | 0 | talloc_free(state); |
303 | 0 | return NULL; |
304 | 0 | } |
305 | 0 | } |
306 | | |
307 | 0 | return state; |
308 | 0 | } |
309 | | |
310 | | /** Unlink an entry and remove if from the tree |
311 | | * |
312 | | */ |
313 | | static inline CC_HINT(always_inline) |
314 | | void state_entry_unlink(fr_state_tree_t *state, fr_state_entry_t *entry) |
315 | 0 | { |
316 | | /* |
317 | | * Check the memory is still valid |
318 | | */ |
319 | 0 | (void) talloc_get_type_abort(entry, fr_state_entry_t); |
320 | |
|
321 | 0 | fr_dlist_remove(&state->to_expire, entry); |
322 | 0 | fr_rb_delete(state->tree, entry); |
323 | 0 | if (state->dedup_tree) fr_rb_delete(state->dedup_tree, entry); |
324 | |
|
325 | 0 | DEBUG4("State ID %" PRIu64 " unlinked", entry->id); |
326 | 0 | } |
327 | | |
328 | | /** Frees any data associated with a state |
329 | | * |
330 | | */ |
331 | | static int _state_entry_free(fr_state_entry_t *entry) |
332 | 0 | { |
333 | 0 | #ifdef WITH_VERIFY_PTR |
334 | 0 | fr_dcursor_t cursor; |
335 | 0 | fr_pair_t *vp; |
336 | | |
337 | | /* |
338 | | * Verify all state attributes are parented |
339 | | * by the state context. |
340 | | */ |
341 | 0 | if (entry->ctx) { |
342 | 0 | for (vp = fr_pair_dcursor_init(&cursor, &entry->ctx->children); |
343 | 0 | vp; |
344 | 0 | vp = fr_dcursor_next(&cursor)) { |
345 | 0 | fr_assert(entry->ctx == talloc_parent(vp)); |
346 | 0 | } |
347 | 0 | } |
348 | | |
349 | | /* |
350 | | * Ensure any request data is parented by us |
351 | | * so we know it'll be cleaned up. |
352 | | */ |
353 | 0 | (void)fr_cond_assert(request_data_verify_parent(entry->ctx, &entry->data)); |
354 | 0 | #endif |
355 | | |
356 | | /* |
357 | | * Should also free any state attributes |
358 | | */ |
359 | 0 | if (entry->ctx) TALLOC_FREE(entry->ctx); |
360 | |
|
361 | 0 | DEBUG4("State ID %" PRIu64 " freed", entry->id); |
362 | |
|
363 | 0 | return 0; |
364 | 0 | } |
365 | | |
366 | | static void state_entry_fill(fr_state_entry_t *entry, fr_value_box_t const *vb) |
367 | 0 | { |
368 | |
|
369 | 0 | uint64_t hash; |
370 | | |
371 | | /* |
372 | | * Use the supplied State if it's the correct size. |
373 | | */ |
374 | 0 | if (vb->vb_length == sizeof(entry->state)) { |
375 | 0 | memcpy(&entry->state, vb->vb_octets, vb->vb_length); |
376 | 0 | return; |
377 | 0 | } |
378 | | |
379 | | /* |
380 | | * Otherwise hash the data. |
381 | | */ |
382 | 0 | memset(&entry->state, 0, sizeof(entry->state)); |
383 | |
|
384 | 0 | hash = fr_hash64(vb->vb_octets, vb->vb_length); |
385 | 0 | memcpy(&entry->state, &hash, sizeof(hash)); |
386 | 0 | } |
387 | | |
388 | | /** Create a new state entry |
389 | | * |
390 | | * @note Called with the mutex held. |
391 | | */ |
392 | | static fr_state_entry_t *state_entry_create(fr_state_tree_t *state, request_t *request, |
393 | | fr_pair_list_t *reply_list, fr_state_entry_t *old, |
394 | | fr_value_box_t const *dedup_key) |
395 | 0 | { |
396 | 0 | fr_time_t now = fr_time(); |
397 | 0 | fr_pair_t *vp; |
398 | 0 | fr_state_entry_t *entry; |
399 | |
|
400 | 0 | uint64_t timed_out = 0; |
401 | 0 | bool too_many = false; |
402 | 0 | fr_dlist_head_t to_free; |
403 | | |
404 | | /* |
405 | | * If we have a previous entry, then it can't be in an |
406 | | * expiry list, and it can't be in the list of states |
407 | | * where we have sent a reply. |
408 | | */ |
409 | 0 | fr_assert(!old || |
410 | 0 | (!fr_dlist_entry_in_list(&old->expire_entry) && |
411 | 0 | !fr_rb_node_inline_in_tree(&old->node))); |
412 | | |
413 | | /* |
414 | | * If we have a previous entry and a dedup_tree, then we |
415 | | * must have a dedup key, AND the entry must be in the |
416 | | * dedup tree. |
417 | | */ |
418 | 0 | fr_assert(!old || !state->dedup_tree || (old->dedup_key && fr_rb_node_inline_in_tree(&old->dedup_node))); |
419 | | |
420 | | /* |
421 | | * If there is an old entry, we can't have a dedup_key. |
422 | | */ |
423 | 0 | fr_assert(!old || !dedup_key); |
424 | | |
425 | | /* |
426 | | * We track a separate free list, as we have to check |
427 | | * expiration with the mutex locked. But we want to free |
428 | | * things with the mutex unlocked. |
429 | | */ |
430 | 0 | fr_dlist_init(&to_free, fr_state_entry_t, free_entry); |
431 | | |
432 | | /* |
433 | | * Clean up expired entries which have not finished. If |
434 | | * the request fails, then the corresponding entry is |
435 | | * discarded. So the expiration list is only for entries |
436 | | * which have been half-started, and then (many seconds |
437 | | * later) haven't seen a "next" packet. |
438 | | */ |
439 | 0 | fr_dlist_foreach(&state->to_expire, fr_state_entry_t, expires) { |
440 | 0 | (void)talloc_get_type_abort(expires, fr_state_entry_t); /* Allow examination */ |
441 | | |
442 | | /* |
443 | | * It's active (and asserted so above), so it can't be in the expiry list. |
444 | | */ |
445 | 0 | fr_assert(expires != old); |
446 | | |
447 | | /* |
448 | | * Too old, we can delete it. |
449 | | */ |
450 | 0 | if (fr_time_lt(expires->cleanup, now)) { |
451 | 0 | state_entry_unlink(state, expires); |
452 | 0 | fr_dlist_insert_tail(&to_free, expires); |
453 | 0 | timed_out++; |
454 | 0 | continue; |
455 | 0 | } |
456 | | |
457 | 0 | break; |
458 | 0 | } |
459 | |
|
460 | 0 | if (!old) { |
461 | | /* |
462 | | * We're inserting a new session. Limit the |
463 | | * number of sessions based on how many are in |
464 | | * the RB tree. If at least one session has |
465 | | * timed out, then we can definitely add a new |
466 | | * session. |
467 | | * |
468 | | * Note that sessions being processed are removed |
469 | | * from the tree. This means that the maximum |
470 | | * number of sessions might actually be |
471 | | * max_session+num_workers. In practice this |
472 | | * shouldn't be a problem. |
473 | | */ |
474 | 0 | too_many = (fr_rb_num_elements(state->tree) >= state->config.max_sessions) && (timed_out == 0); |
475 | | |
476 | | /* |
477 | | * If there is a previous session for the same dedup key, then remove the old one from |
478 | | * the dedup tree. |
479 | | */ |
480 | 0 | if (dedup_key) { |
481 | 0 | fr_state_entry_t *unfinished; |
482 | |
|
483 | 0 | unfinished = fr_rb_find(state->dedup_tree, &(fr_state_entry_t) { .dedup_key = dedup_key }); |
484 | 0 | if (unfinished) { |
485 | 0 | state_entry_unlink(state, unfinished); |
486 | 0 | fr_dlist_insert_tail(&to_free, unfinished); |
487 | 0 | } |
488 | 0 | } |
489 | 0 | } |
490 | |
|
491 | 0 | PTHREAD_MUTEX_UNLOCK(&state->mutex); |
492 | |
|
493 | 0 | if (timed_out > 0) { |
494 | 0 | RWDEBUG("Cleaning up %"PRIu64" timed out state entries", timed_out); |
495 | 0 | state->timed_out += timed_out; |
496 | | |
497 | | /* |
498 | | * Now free the unlinked entries. |
499 | | * |
500 | | * We do it here as freeing may involve significantly more |
501 | | * work than just freeing the data. |
502 | | * |
503 | | * If there's request data that was persisted it will now |
504 | | * be freed also, and it may have complex destructors associated |
505 | | * with it. |
506 | | */ |
507 | 0 | fr_dlist_talloc_free(&to_free); |
508 | |
|
509 | 0 | } else if (too_many) { |
510 | 0 | talloc_const_free(dedup_key); |
511 | 0 | RERROR("Failed inserting state entry - At maximum ongoing session limit (%u)", |
512 | 0 | state->config.max_sessions); |
513 | 0 | return NULL; |
514 | 0 | } |
515 | | |
516 | | /* |
517 | | * Allocation doesn't need to occur inside the critical region |
518 | | * and would add significantly to contention. |
519 | | */ |
520 | 0 | if (!old) { |
521 | 0 | MEM(entry = talloc_zero(NULL, fr_state_entry_t)); |
522 | 0 | talloc_set_destructor(entry, _state_entry_free); |
523 | |
|
524 | 0 | entry->id = state->id++; |
525 | |
|
526 | 0 | } else { |
527 | 0 | fr_assert(!old->ctx); |
528 | 0 | entry = old; |
529 | 0 | } |
530 | |
|
531 | 0 | request_data_list_init(&entry->data); |
532 | | |
533 | | /* |
534 | | * Limit the lifetime of this entry based on how long the |
535 | | * server takes to process a request. Doing it this way |
536 | | * isn't perfect, but it's reasonable, and it's one less |
537 | | * thing for an administrator to configure. |
538 | | */ |
539 | 0 | entry->cleanup = fr_time_add(now, state->config.timeout); |
540 | | |
541 | | /* |
542 | | * Some modules either create their own state, or need to |
543 | | * synthesize it from data in a packet header. If we |
544 | | * have such a state, then use that in preference to |
545 | | * creating a random one. |
546 | | */ |
547 | 0 | vp = fr_pair_find_by_da(reply_list, NULL, state->da); |
548 | 0 | if (vp && vp->vp_length) { |
549 | 0 | state_entry_fill(entry, &vp->data); |
550 | |
|
551 | 0 | } else { |
552 | 0 | if (old) { |
553 | | /* |
554 | | * Just re-use the old state. |
555 | | */ |
556 | 0 | entry->tries++; |
557 | |
|
558 | 0 | if (entry->tries > state->config.max_rounds) { |
559 | 0 | RERROR("Failed tracking state entry - too many rounds (%u)", entry->tries); |
560 | 0 | goto fail; |
561 | 0 | } |
562 | 0 | } else { |
563 | 0 | size_t i; |
564 | 0 | uint32_t hash; |
565 | |
|
566 | 0 | if (dedup_key) entry->dedup_key = talloc_steal(entry, dedup_key); |
567 | | |
568 | | /* |
569 | | * Get a bunch of random numbers. |
570 | | */ |
571 | 0 | for (i = 0; i < sizeof(entry->state); i+= 4) { |
572 | 0 | hash = fr_rand(); |
573 | 0 | memcpy(&entry->state[i], &hash, sizeof(hash)); |
574 | 0 | } |
575 | | |
576 | | /* |
577 | | * Add in a server ID. This lets a "FreeRADIUS |
578 | | * aware" load balancer direct the packet based |
579 | | * on the contents of the State attribute. |
580 | | */ |
581 | 0 | entry->state_comp.server_id = state->config.server_id; |
582 | | |
583 | | /* |
584 | | * Add our own custom brand of magic. |
585 | | */ |
586 | 0 | entry->state_comp.vx_0 = entry->state_comp.r_0 ^ |
587 | 0 | ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 24) & 0xff); |
588 | 0 | entry->state_comp.vx_1 = entry->state_comp.r_0 ^ |
589 | 0 | ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 16) & 0xff); |
590 | 0 | entry->state_comp.vx_2 = entry->state_comp.r_0 ^ |
591 | 0 | ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 8) & 0xff); |
592 | 0 | entry->state_comp.vx_3 = entry->state_comp.r_0 ^ |
593 | 0 | (((uint32_t) HEXIFY(RADIUSD_VERSION)) & 0xff); |
594 | 0 | } |
595 | | |
596 | | /* |
597 | | * Track the number of round trips, too. |
598 | | */ |
599 | 0 | entry->state_comp.tx ^= entry->tries; |
600 | 0 | entry->state_comp.tries = entry->tries ^ entry->state_comp.r_3; |
601 | |
|
602 | 0 | MEM(vp = fr_pair_afrom_da(request->reply_ctx, state->da)); |
603 | 0 | fr_pair_value_memdup(vp, entry->state, sizeof(entry->state), false); |
604 | 0 | fr_pair_append(reply_list, vp); |
605 | 0 | } |
606 | | |
607 | 0 | DEBUG4("State ID %" PRIu64 " created, value 0x%pH, expires %pV", |
608 | 0 | entry->id, &vp->data, |
609 | 0 | fr_box_time_delta(fr_time_sub(entry->cleanup, now))); |
610 | | |
611 | | /* |
612 | | * XOR the server hash with four bytes of random context |
613 | | * ID after adding it to the reply, but before inserting |
614 | | * it into the RB rtree. We XOR is again before looking |
615 | | * it up in the tree, to ensure state lookups only |
616 | | * succeed in the virtual server that created the state |
617 | | * value. |
618 | | */ |
619 | 0 | entry->state_comp.context_id ^= state->config.context_id; |
620 | |
|
621 | 0 | PTHREAD_MUTEX_LOCK(&state->mutex); |
622 | |
|
623 | 0 | if (!fr_rb_insert(state->tree, entry)) { |
624 | 0 | fail_unlock: |
625 | 0 | PTHREAD_MUTEX_UNLOCK(&state->mutex); |
626 | 0 | RERROR("Failed inserting state entry - Insertion into state tree failed"); |
627 | 0 | fail: |
628 | 0 | fr_pair_delete_by_da(reply_list, state->da); |
629 | 0 | talloc_free(entry); |
630 | 0 | return NULL; |
631 | 0 | } |
632 | | |
633 | | /* |
634 | | * Ensure that we can de-duplicate things if the supplicant is misbehaving. |
635 | | */ |
636 | 0 | if (state->dedup_tree && !old) { |
637 | 0 | if (!fr_rb_insert(state->dedup_tree, entry)) { |
638 | 0 | (void) fr_rb_remove(state->tree, entry); |
639 | 0 | goto fail_unlock; |
640 | 0 | } |
641 | 0 | } |
642 | | |
643 | | /* |
644 | | * Link it to the end of the list, which is implicitly |
645 | | * ordered by cleanup time. |
646 | | */ |
647 | 0 | fr_dlist_insert_tail(&state->to_expire, entry); |
648 | |
|
649 | 0 | entry->thawed = NULL; |
650 | |
|
651 | 0 | return entry; |
652 | 0 | } |
653 | | |
654 | | /** Find the entry based on the State attribute and remove it from the state tree |
655 | | * |
656 | | */ |
657 | | static fr_state_entry_t *state_entry_find_and_unlink(fr_state_tree_t *state, fr_value_box_t const *vb) |
658 | 0 | { |
659 | 0 | fr_state_entry_t *entry, my_entry; |
660 | |
|
661 | 0 | state_entry_fill(&my_entry, vb); |
662 | | |
663 | | /* |
664 | | * Make it unique for different virtual servers handling the same request |
665 | | */ |
666 | 0 | my_entry.state_comp.context_id ^= state->config.context_id; |
667 | |
|
668 | 0 | entry = fr_rb_remove(state->tree, &my_entry); |
669 | 0 | if (entry) { |
670 | 0 | (void) talloc_get_type_abort(entry, fr_state_entry_t); |
671 | 0 | fr_dlist_remove(&state->to_expire, entry); |
672 | 0 | } |
673 | |
|
674 | 0 | return entry; |
675 | 0 | } |
676 | | |
677 | | |
678 | | /** Called when sending an Access-Accept/Access-Reject to discard state information |
679 | | * |
680 | | */ |
681 | | void fr_state_discard(fr_state_tree_t *state, request_t *request) |
682 | 0 | { |
683 | 0 | fr_state_entry_t *entry; |
684 | | |
685 | | /* |
686 | | * The caller MUST have called fr_state_restore() before |
687 | | * calling this function. If so, there is request data |
688 | | * that points to the state entry. |
689 | | * |
690 | | * This function should only be called from the "outer" |
691 | | * request. Any child request should call |
692 | | * fr_state_discard_child() |
693 | | * |
694 | | * Relying on request data also means that the user can |
695 | | * nuke request.State, and the code will still work. |
696 | | * |
697 | | * Find a pointer to the entry, but leave the request |
698 | | * data associated with the entry. That way when the |
699 | | * request is freed, the entry will also be freed. |
700 | | */ |
701 | 0 | entry = request_data_reference(request, state, 0); |
702 | 0 | if (!entry) return; |
703 | | |
704 | | /* |
705 | | * Unlink the entry to shrink the state tree, and make |
706 | | * sure that the state is never re-used. |
707 | | * |
708 | | * However, we don't wipe the session-state list, as the |
709 | | * request can still be processed through a "finally" |
710 | | * section. And we want the session state data to be |
711 | | * usable from there. |
712 | | */ |
713 | 0 | PTHREAD_MUTEX_LOCK(&state->mutex); |
714 | 0 | state_entry_unlink(state, entry); |
715 | 0 | PTHREAD_MUTEX_UNLOCK(&state->mutex); |
716 | |
|
717 | 0 | return; |
718 | 0 | } |
719 | | |
720 | | /** Copy a pointer to the head of the list of state fr_pair_ts (and their ctx) into the request |
721 | | * |
722 | | * @note Does not copy the actual fr_pair_ts. The fr_pair_ts and their context |
723 | | * are transferred between state entries as the conversation progresses. |
724 | | * |
725 | | * @note Called with the mutex free. |
726 | | * |
727 | | * @param[in] state tree to lookup state in. |
728 | | * @param[in] request to restore state for. |
729 | | * @return |
730 | | * - 2 if the state attribute didn't match any known states. |
731 | | * - 1 if no state attribute existed. |
732 | | * - 0 on success (state restored) |
733 | | * - -1 if a state entry has already been thawed by a another request. |
734 | | */ |
735 | | int fr_state_restore(fr_state_tree_t *state, request_t *request) |
736 | 0 | { |
737 | 0 | fr_state_entry_t *entry; |
738 | 0 | fr_pair_t *vp; |
739 | | |
740 | | /* |
741 | | * No State, don't do anything. |
742 | | */ |
743 | 0 | vp = fr_pair_find_by_da(&request->request_pairs, NULL, state->da); |
744 | 0 | if (!vp) { |
745 | 0 | RDEBUG3("No request.%s attribute, can't restore session-state", state->da->name); |
746 | 0 | if (request->seq_start == 0) request->seq_start = request->number; /* Need check for fake requests */ |
747 | 0 | return 1; |
748 | 0 | } |
749 | | |
750 | 0 | PTHREAD_MUTEX_LOCK(&state->mutex); |
751 | 0 | entry = state_entry_find_and_unlink(state, &vp->data); |
752 | 0 | PTHREAD_MUTEX_UNLOCK(&state->mutex); |
753 | 0 | if (!entry) { |
754 | 0 | RDEBUG2("No state entry matching request.%pP found", vp); |
755 | 0 | return 2; |
756 | 0 | } |
757 | | |
758 | | /* Probably impossible in the current code */ |
759 | 0 | if (unlikely(entry->thawed && (entry->thawed != request))) { |
760 | 0 | RERROR("State entry has already been thawed by a request %"PRIu64, entry->thawed->number); |
761 | 0 | return -2; |
762 | 0 | } |
763 | | |
764 | | /* |
765 | | * Discard any existing session state, and replace it |
766 | | * with the cached one. |
767 | | */ |
768 | 0 | fr_assert(entry->ctx); |
769 | 0 | talloc_free(request_state_replace(request, entry->ctx)); |
770 | 0 | entry->ctx = NULL; |
771 | |
|
772 | 0 | request->seq_start = entry->seq_start; |
773 | | |
774 | | /* |
775 | | * Associate old state with the request |
776 | | * |
777 | | * If the request is freed, it's freed immediately. |
778 | | * |
779 | | * Otherwise, if there's another round, we reuse |
780 | | * the state entry and insert it back into the |
781 | | * tree. |
782 | | */ |
783 | 0 | request_data_add(request, state, 0, entry, true, true, false); |
784 | 0 | request_data_restore(request, &entry->data); |
785 | |
|
786 | 0 | entry->thawed = request; |
787 | |
|
788 | 0 | if (!fr_pair_list_empty(&request->session_state_pairs)) { |
789 | 0 | RDEBUG2("Restored session-state"); |
790 | 0 | log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->session_state_pairs, "session-state."); |
791 | 0 | } |
792 | |
|
793 | 0 | RDEBUG3("%s - restored", state->da->name); |
794 | | |
795 | | /* |
796 | | * Set sequence so that we can prioritize ongoing multi-packet sessions. |
797 | | */ |
798 | 0 | request->sequence = entry->tries; |
799 | 0 | REQUEST_VERIFY(request); |
800 | 0 | return 0; |
801 | 0 | } |
802 | | |
803 | | |
804 | | /** Transfer ownership of the state fr_pair_ts and ctx, back to a state entry |
805 | | * |
806 | | * Put request->session_state_pairs into the State attribute. Put the State attribute |
807 | | * into the vps list. Delete the original entry, if it exists |
808 | | * |
809 | | * Also creates a new state entry. |
810 | | */ |
811 | | int fr_state_store(fr_state_tree_t *state, request_t *request) |
812 | 0 | { |
813 | 0 | fr_state_entry_t *entry, *old; |
814 | 0 | fr_dlist_head_t data; |
815 | 0 | fr_pair_t *state_ctx; |
816 | 0 | fr_value_box_t *dedup_key = NULL; |
817 | |
|
818 | 0 | old = request_data_get(request, state, 0); |
819 | 0 | request_data_list_init(&data); |
820 | 0 | request_data_by_persistance(&data, request, true); |
821 | |
|
822 | 0 | if (fr_pair_list_empty(&request->session_state_pairs) && fr_dlist_empty(&data)) return 0; |
823 | | |
824 | 0 | if (!fr_pair_list_empty(&request->session_state_pairs)) { |
825 | 0 | RDEBUG2("Saving session-state"); |
826 | 0 | log_request_pair_list(L_DBG_LVL_2, request, NULL, &request->session_state_pairs, "session-state."); |
827 | |
|
828 | 0 | #ifdef WITH_VERIFY_PTR |
829 | | /* |
830 | | * Double check all the session state pairs |
831 | | * are parented correctly, else we'll get |
832 | | * memory errors when we restore. |
833 | | */ |
834 | 0 | fr_pair_list_verify(__FILE__, __LINE__, request->session_state_ctx, &request->session_state_pairs, true); |
835 | 0 | #endif |
836 | 0 | } |
837 | | |
838 | | /* |
839 | | * If there's a dedup tree, then we need to expand the |
840 | | * key, but only if we don't already have a pre-existing state. |
841 | | */ |
842 | 0 | if (state->dedup_tree && !old) { |
843 | 0 | fr_value_box_list_t list; |
844 | |
|
845 | 0 | fr_value_box_list_init(&list); |
846 | |
|
847 | 0 | if (tmpl_eval(NULL, &list, request, state->config.dedup_key) < 0) { |
848 | 0 | REDEBUG("Failed expanding dedup_key - not doing dedup"); |
849 | 0 | } else { |
850 | 0 | dedup_key = fr_value_box_list_pop_head(&list); |
851 | 0 | if (!dedup_key) { |
852 | 0 | RDEBUG("Failed expanding dedup_key - not doing dedup due to empty output"); |
853 | 0 | } |
854 | 0 | fr_value_box_list_talloc_free_head(&list); |
855 | 0 | } |
856 | 0 | } |
857 | |
|
858 | 0 | MEM(state_ctx = request_state_replace(request, NULL)); |
859 | | |
860 | | /* |
861 | | * Reuses old if possible, and leaves the mutex unlocked on failure. |
862 | | */ |
863 | 0 | PTHREAD_MUTEX_LOCK(&state->mutex); |
864 | 0 | entry = state_entry_create(state, request, &request->reply_pairs, old, dedup_key); |
865 | 0 | if (!entry) { |
866 | 0 | talloc_free(request_state_replace(request, state_ctx)); |
867 | 0 | request_data_restore(request, &data); /* Put it back again */ |
868 | |
|
869 | | #ifdef __COVERITY__ |
870 | | /* |
871 | | * Coverity doesn't see that state_entry_create releases |
872 | | * the lock on failure |
873 | | */ |
874 | | PTHREAD_MUTEX_UNLOCK(&state->mutex) |
875 | | #endif |
876 | 0 | return -1; |
877 | 0 | } |
878 | | |
879 | 0 | fr_assert(entry->ctx == NULL); |
880 | 0 | fr_assert(request->session_state_ctx); |
881 | |
|
882 | 0 | entry->seq_start = request->seq_start; |
883 | 0 | entry->ctx = state_ctx; |
884 | 0 | fr_dlist_move(&entry->data, &data); |
885 | 0 | PTHREAD_MUTEX_UNLOCK(&state->mutex); |
886 | |
|
887 | 0 | RDEBUG3("%s - saved", state->da->name); |
888 | 0 | REQUEST_VERIFY(request); |
889 | |
|
890 | 0 | return 0; |
891 | 0 | } |
892 | | |
893 | | /** Free any subrequest request data if the dlist head is freed |
894 | | * |
895 | | */ |
896 | | static int _free_child_data(state_child_entry_t *child_entry) |
897 | 0 | { |
898 | 0 | fr_dlist_talloc_free(&child_entry->data); |
899 | 0 | talloc_free(child_entry->ctx); /* Free the child's session_state_ctx if we own it */ |
900 | |
|
901 | 0 | return 0; |
902 | 0 | } |
903 | | |
904 | | /** Store subrequest's session-state list and persistable request data in its parent |
905 | | * |
906 | | * @param[in] child The child request to retrieve state from. |
907 | | * @param[in] unique_ptr A parent may have multiple subrequests spawned |
908 | | * by different modules. This identifies the module |
909 | | * or other facility that spawned the subrequest. |
910 | | * @param[in] unique_int Further identification. |
911 | | */ |
912 | | void fr_state_store_in_parent(request_t *child, void const *unique_ptr, int unique_int) |
913 | 0 | { |
914 | 0 | state_child_entry_t *child_entry; |
915 | 0 | request_t *request = child; /* Stupid logging */ |
916 | |
|
917 | 0 | if (!fr_cond_assert_msg(child->parent, |
918 | 0 | "Child request must have request->parent set when storing state")) return; |
919 | | |
920 | 0 | RDEBUG3("Storing subrequest state in request %s", child->parent->name); |
921 | |
|
922 | 0 | if ((request_data_by_persistance_count(request, true) > 0) || |
923 | 0 | !fr_pair_list_empty(&request->session_state_pairs)) { |
924 | 0 | MEM(child_entry = talloc_zero(request->parent->session_state_ctx, state_child_entry_t)); |
925 | 0 | request_data_list_init(&child_entry->data); |
926 | 0 | talloc_set_destructor(child_entry, _free_child_data); |
927 | |
|
928 | 0 | child_entry->ctx = request_state_replace(child, NULL); |
929 | | |
930 | | /* |
931 | | * Pull everything out of the child, |
932 | | * add it to our temporary list head... |
933 | | * |
934 | | * request_data_add allocs persistable |
935 | | * request dta in the session_state_ctx |
936 | | * which is why we don't need to copy or |
937 | | * reparent any of this. |
938 | | */ |
939 | 0 | request_data_by_persistance(&child_entry->data, request, true); |
940 | | |
941 | | /* |
942 | | * ...and add the request_data from |
943 | | * the child back into the parent. |
944 | | */ |
945 | 0 | request_data_talloc_add(request->parent, unique_ptr, unique_int, |
946 | 0 | state_child_entry_t, child_entry, true, false, true); |
947 | 0 | } |
948 | 0 | } |
949 | | |
950 | | /** Restore subrequest data from a parent request |
951 | | * |
952 | | * @param[in] child The child request to restore state to. |
953 | | * @param[in] unique_ptr A parent may have multiple subrequests spawned |
954 | | * by different modules. This identifies the module |
955 | | * or other facility that spawned the subrequest. |
956 | | * @param[in] unique_int Further identification. |
957 | | */ |
958 | | void fr_state_restore_from_parent(request_t *child, void const *unique_ptr, int unique_int) |
959 | 0 | { |
960 | 0 | state_child_entry_t *child_entry; |
961 | 0 | request_t *request = child; /* Stupid logging */ |
962 | |
|
963 | 0 | if (!fr_cond_assert_msg(child->parent, |
964 | 0 | "Child request must have request->parent set when restoring state")) return; |
965 | | |
966 | | |
967 | 0 | child_entry = request_data_get(child->parent, unique_ptr, unique_int); |
968 | 0 | if (!child_entry) { |
969 | 0 | RDEBUG3("No child state found in parent %s", child->parent->name); |
970 | 0 | return; |
971 | 0 | } |
972 | | |
973 | | /* |
974 | | * Shouldn't really be possible unless |
975 | | * there's a logic bug in this API. |
976 | | */ |
977 | 0 | if (!fr_cond_assert_msg(!child_entry->thawed, |
978 | 0 | "Child state entry already thawed by %s - %p", |
979 | 0 | child_entry->thawed->name, child_entry->thawed)) return; |
980 | | |
981 | 0 | RDEBUG3("Restoring subrequest state from request %s", child->parent->name); |
982 | | |
983 | | /* |
984 | | * If we can restore from the parent, do so |
985 | | */ |
986 | 0 | fr_assert_msg(child_entry->ctx, "session child entry missing ctx"); |
987 | 0 | talloc_free(request_state_replace(child, child_entry->ctx)); |
988 | 0 | child_entry->ctx = NULL; /* No longer owns the ctx */ |
989 | 0 | child_entry->thawed = child; |
990 | |
|
991 | 0 | request_data_restore(child, &child_entry->data); /* Put all the request data back */ |
992 | |
|
993 | 0 | talloc_free(child_entry); |
994 | 0 | } |
995 | | |
996 | | /** Remove state from a child |
997 | | * |
998 | | * This is useful for modules like EAP, where we keep a persistent eap_session |
999 | | * but may call multiple EAP method modules during negotiation, and need to |
1000 | | * discard the state between each module call. |
1001 | | * |
1002 | | * @param[in] parent Holding the child's state. |
1003 | | * @param[in] unique_ptr A parent may have multiple subrequests spawned |
1004 | | * by different modules. This identifies the module |
1005 | | * or other facility that spawned the subrequest. |
1006 | | * @param[in] unique_int Further identification. |
1007 | | */ |
1008 | | void fr_state_discard_child(request_t *parent, void const *unique_ptr, int unique_int) |
1009 | 0 | { |
1010 | 0 | state_child_entry_t *child_entry; |
1011 | 0 | request_t *request = parent; /* Stupid logging */ |
1012 | |
|
1013 | 0 | child_entry = request_data_get(parent, unique_ptr, unique_int); |
1014 | 0 | if (!child_entry) { |
1015 | 0 | RDEBUG3("No child state found in parent %s", parent->name); |
1016 | 0 | return; |
1017 | 0 | } |
1018 | | |
1019 | 0 | talloc_free(child_entry); |
1020 | 0 | } |