Coverage Report

Created: 2026-11-12 06:42

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