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: f201d51202d32dfcbaac7ac1db664de6a6ef27fa $
19 *
20 * @brief Multi-packet state handling
21 * @file main/state.c
22 *
23 * @ingroup AVP
24 *
25 * @copyright 2014 The FreeRADIUS server project
26 */
27 RCSID("$Id: f201d51202d32dfcbaac7ac1db664de6a6ef27fa $")
28
29 #include <freeradius-devel/radiusd.h>
30 #include <freeradius-devel/state.h>
31 #include <freeradius-devel/md5.h>
32 #include <freeradius-devel/rad_assert.h>
33 #include <freeradius-devel/process.h>
34
35 typedef struct state_entry_t {
36 uint8_t state[AUTH_VECTOR_LEN];
37
38 time_t cleanup;
39 struct state_entry_t *prev;
40 struct state_entry_t *next;
41
42 int tries;
43
44 TALLOC_CTX *ctx;
45 VALUE_PAIR *vps;
46
47 char *server;
48 unsigned int request_number;
49 RADCLIENT *request_client;
50 main_config_t *request_root;
51
52 void *opaque;
53 void (*free_opaque)(void *opaque);
54 } state_entry_t;
55
56 struct fr_state_t {
57 rbtree_t *tree;
58
59 state_entry_t *head, *tail;
60
61 #ifdef HAVE_PTHREAD_H
62 pthread_mutex_t mutex;
63 #endif
64 };
65
66 static fr_state_t global_state;
67
68 #ifdef HAVE_PTHREAD_H
69
70 #define PTHREAD_MUTEX_LOCK pthread_mutex_lock
71 #define PTHREAD_MUTEX_UNLOCK pthread_mutex_unlock
72
73 #else
74 /*
75 * This is easier than ifdef's throughout the code.
76 */
77 #define PTHREAD_MUTEX_LOCK(_x)
78 #define PTHREAD_MUTEX_UNLOCK(_x)
79
80 #endif
81
82 /*
83 * rbtree callback.
84 */
state_entry_cmp(void const * one,void const * two)85 static int state_entry_cmp(void const *one, void const *two)
86 {
87 state_entry_t const *a = one;
88 state_entry_t const *b = two;
89
90 return memcmp(a->state, b->state, sizeof(a->state));
91 }
92
state_entry_link(fr_state_t * state,state_entry_t * entry)93 static bool state_entry_link(fr_state_t *state, state_entry_t *entry)
94 {
95 if (!rbtree_insert(state->tree, entry)) {
96 return false;
97 }
98
99 /*
100 * Link it to the end of the list, which is implicitely
101 * ordered by cleanup time.
102 */
103 if (!state->head) {
104 entry->prev = entry->next = NULL;
105 state->head = state->tail = entry;
106 } else {
107 rad_assert(state->tail != NULL);
108
109 entry->prev = state->tail;
110 state->tail->next = entry;
111
112 entry->next = NULL;
113 state->tail = entry;
114 }
115
116 return true;
117 }
118
state_entry_unlink(fr_state_t * state,state_entry_t * entry)119 static void state_entry_unlink(fr_state_t *state, state_entry_t *entry)
120 {
121 state_entry_t *prev, *next;
122
123 prev = entry->prev;
124 next = entry->next;
125
126 if (prev) {
127 rad_assert(state->head != entry);
128 prev->next = next;
129 } else if (state->head) {
130 rad_assert(state->head == entry);
131 state->head = next;
132 }
133
134 if (next) {
135 rad_assert(state->tail != entry);
136 next->prev = prev;
137 } else if (state->tail) {
138 rad_assert(state->tail == entry);
139 state->tail = prev;
140 }
141
142 rbtree_deletebydata(state->tree, entry);
143 }
144
145 /*
146 * When an entry is free'd, it's removed from the linked list of
147 * cleanup timers.
148 */
state_entry_free(fr_state_t * state,state_entry_t * entry)149 static void state_entry_free(fr_state_t *state, state_entry_t *entry)
150 {
151 /*
152 * If we're deleting the whole tree, don't bother doing
153 * all of the fixups.
154 */
155 if (!state || !state->tree) return;
156
157 state_entry_unlink(state, entry);
158
159 if (entry->opaque) {
160 entry->free_opaque(entry->opaque);
161 }
162
163 #ifdef WITH_VERIFY_PTR
164 (void) talloc_get_type_abort(entry, state_entry_t);
165 #endif
166 if (entry->ctx) talloc_free(entry->ctx);
167
168 talloc_free(entry);
169 }
170
fr_state_init(TALLOC_CTX * ctx)171 fr_state_t *fr_state_init(TALLOC_CTX *ctx)
172 {
173 fr_state_t *state;
174
175 if (!ctx) {
176 state = &global_state;
177 if (state->tree) return state;
178 } else {
179 state = talloc_zero(ctx, fr_state_t);
180 if (!state) return 0;
181 }
182
183 #ifdef HAVE_PTHREAD_H
184 if (pthread_mutex_init(&state->mutex, NULL) != 0) {
185 talloc_free(state);
186 return NULL;
187 }
188 #endif
189
190 state->tree = rbtree_create(NULL, state_entry_cmp, NULL, 0);
191 if (!state->tree) {
192 talloc_free(state);
193 return NULL;
194 }
195
196 return state;
197 }
198
fr_state_delete(fr_state_t * state)199 void fr_state_delete(fr_state_t *state)
200 {
201 rbtree_t *my_tree;
202
203 if (!state) return;
204
205 PTHREAD_MUTEX_LOCK(&state->mutex);
206
207 /*
208 * Tell the talloc callback to NOT delete the entry from
209 * the tree. We're deleting the entire tree.
210 */
211 my_tree = state->tree;
212 state->tree = NULL;
213
214 rbtree_free(my_tree);
215 PTHREAD_MUTEX_UNLOCK(&state->mutex);
216
217 if (state != &global_state) talloc_free(state);
218 }
219
220 /*
221 * Create a fake request, based on what we know about the
222 * session that has expired, and inject it into the server to
223 * allow final logging or cleaning up.
224 */
fr_state_cleanup_request(state_entry_t * entry)225 static REQUEST *fr_state_cleanup_request(state_entry_t *entry)
226 {
227 REQUEST *request;
228 RADIUS_PACKET *packet = NULL;
229 RADIUS_PACKET *reply_packet = NULL;
230 VALUE_PAIR *vp;
231
232 /*
233 * Allocate a new fake request with enough to keep
234 * the rest of the server happy.
235 */
236 request = request_alloc(NULL);
237 if (unlikely(!request)) return NULL;
238
239 packet = rad_alloc(request, false);
240 if (unlikely(!packet)) {
241 error:
242 TALLOC_FREE(reply_packet);
243 TALLOC_FREE(packet);
244 TALLOC_FREE(request);
245 return NULL;
246 }
247
248 reply_packet = rad_alloc(request, false);
249 if (unlikely(!reply_packet)) goto error;
250
251 /*
252 * Move the server from the state entry over to the
253 * request. Clearing it in the state means this
254 * function will never be called again.
255 */
256 request->server = talloc_steal(request, entry->server);
257 entry->server = NULL;
258
259 /*
260 * Build the fake request with the limited
261 * information we have from the state.
262 */
263 request->packet = packet;
264 request->reply = reply_packet;
265 request->number = entry->request_number;
266 request->client = entry->request_client;
267 request->root = entry->request_root;
268 request->handle = rad_postauth;
269
270 /*
271 * Move session-state VPS over
272 */
273 request->state_ctx = entry->ctx;
274 request->state = entry->vps;
275
276 entry->ctx = NULL;
277 entry->vps = NULL;
278
279 /*
280 * Set correct Post-Auth-Type section
281 */
282 fr_pair_delete_by_num(&request->config, PW_POST_AUTH_TYPE, 0, TAG_ANY);
283 vp = pair_make_config("Post-Auth-Type", "Client-Lost", T_OP_SET);
284 if (unlikely(!vp)) goto error;
285
286 VERIFY_REQUEST(request);
287 return request;
288 }
289
290 /*
291 * Check state for old entries that need to be cleaned up. If
292 * they are old enough then move them from the global state
293 * list to a list of entries to clean up (outside the mutex).
294 * Called with the mutex held.
295 */
fr_state_cleanup_find(fr_state_t * state)296 static state_entry_t *fr_state_cleanup_find(fr_state_t *state)
297 {
298 time_t now = time(NULL);
299 state_entry_t *entry, *next;
300 state_entry_t *head = NULL, **tail = &head;
301
302 for (entry = state->head; entry != NULL; entry = next) {
303 next = entry->next;
304
305 /*
306 * Unused. We can delete it, even if now isn't
307 * the time to clean it up.
308 */
309 if (!entry->ctx && !entry->opaque) {
310 state_entry_free(state, entry);
311 continue;
312 }
313
314 /*
315 * Not yet time to clean it up.
316 */
317 if (entry->cleanup > now) {
318 continue;
319 }
320
321 /*
322 * We're not running the "client lost" section.
323 * Just nuke the entry now.
324 */
325 if (!main_config.postauth_client_lost) {
326 state_entry_free(state, entry);
327 continue;
328 }
329
330 /*
331 * Old enough that the request has been removed.
332 * We can add it to the cleanup list.
333 */
334 state_entry_unlink(state, entry);
335 entry->prev = entry->next = NULL;
336 (*tail) = entry;
337 tail = &entry->next;
338 }
339
340 return head;
341 }
342
343 /*
344 * Inject all requests in cleanup list for cleanup post-auth
345 */
fr_state_cleanup(state_entry_t * head)346 static void fr_state_cleanup(state_entry_t *head)
347 {
348 state_entry_t *entry, *next;
349
350 if (!head) return;
351
352 for (entry = head; entry != NULL; entry = next) {
353 REQUEST *request;
354
355 next = entry->next;
356
357 request = fr_state_cleanup_request(entry);
358 if (request) {
359 RDEBUG2("No response from client, cleaning up expired state");
360 RDEBUG2("Restoring &session-state");
361
362 /*
363 * @todo - print out message
364 * saying where the handler was
365 * in the process? i.e. "sent
366 * server cert", etc. This will
367 * require updating the EAP code
368 * to put a new attribute into
369 * the session state list.
370 */
371
372 rdebug_pair_list(L_DBG_LVL_2, request, request->state, "&session-state:");
373
374 request_inject(request);
375 }
376
377 talloc_free(entry);
378 }
379 }
380
381
382 /*
383 * Create a new entry. Called with the mutex held.
384 */
fr_state_entry_create(fr_state_t * state,REQUEST * request,RADIUS_PACKET * packet,state_entry_t * old)385 static state_entry_t *fr_state_entry_create(fr_state_t *state, REQUEST *request, RADIUS_PACKET *packet, state_entry_t *old)
386 {
387 size_t i;
388 uint32_t x;
389 time_t now = time(NULL);
390 VALUE_PAIR *vp;
391 state_entry_t *entry;
392
393 /*
394 * Limit the size of the cache based on how many requests
395 * we can handle at the same time.
396 */
397 if (rbtree_num_elements(state->tree) >= main_config.max_requests * 2) {
398 return NULL;
399 }
400
401 /*
402 * Allocate a new one.
403 */
404 entry = talloc_zero(state->tree, state_entry_t);
405 if (!entry) return NULL;
406
407 /*
408 * Limit the lifetime of this entry based on how long the
409 * server takes to process a request. Doing it this way
410 * isn't perfect, but it's reasonable, and it's one less
411 * thing for an administrator to configure.
412 */
413 entry->cleanup = now + main_config.max_request_time * 2;
414
415 /*
416 * Hacks for EAP, until we convert EAP to using the state API.
417 *
418 * The EAP module creates it's own State attribute, so we
419 * want to use that one in preference to one we create.
420 */
421 vp = fr_pair_find_by_num(packet->vps, PW_STATE, 0, TAG_ANY);
422
423 /*
424 * If possible, base the new one off of the old one.
425 */
426 if (old) {
427 entry->tries = old->tries + 1;
428
429 /*
430 * Track State
431 */
432 if (!vp) {
433 memcpy(entry->state, old->state, sizeof(entry->state));
434
435 entry->state[1] = entry->state[0] ^ entry->tries;
436 entry->state[8] = entry->state[2] ^ ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 16) & 0xff);
437 entry->state[10] = entry->state[2] ^ ((((uint32_t) HEXIFY(RADIUSD_VERSION)) >> 8) & 0xff);
438 entry->state[12] = entry->state[2] ^ (((uint32_t) HEXIFY(RADIUSD_VERSION)) & 0xff);
439 }
440
441 /*
442 * The old one isn't used any more, so we can free it.
443 */
444 if (!old->opaque) state_entry_free(state, old);
445
446 } else if (!vp) {
447 /*
448 * 16 octets of randomness should be enough to
449 * have a globally unique state.
450 */
451 for (i = 0; i < sizeof(entry->state) / sizeof(x); i++) {
452 x = fr_rand();
453 memcpy(entry->state + (i * 4), &x, sizeof(x));
454 }
455 }
456
457 /*
458 * If EAP created a State, use that. Otherwise, use the
459 * one we created above.
460 */
461 if (vp) {
462 /*
463 * Assume our own State first.
464 */
465 if (vp->vp_length == sizeof(entry->state)) {
466 memcpy(entry->state, vp->vp_octets, sizeof(entry->state));
467
468 /*
469 * Too big? Get the MD5 hash, in order
470 * to depend on the entire contents of State.
471 */
472 } else if (vp->vp_length > sizeof(entry->state)) {
473 fr_md5_calc(entry->state, vp->vp_octets, vp->vp_length);
474
475 /*
476 * Too small? Use the whole thing, and
477 * set the rest of entry->state to zero.
478 */
479 } else {
480 memcpy(entry->state, vp->vp_octets, vp->vp_length);
481 memset(&entry->state[vp->vp_length], 0, sizeof(entry->state) - vp->vp_length);
482 }
483 } else {
484 vp = fr_pair_afrom_num(packet, PW_STATE, 0);
485 fr_pair_value_memcpy(vp, entry->state, sizeof(entry->state));
486 fr_pair_add(&packet->vps, vp);
487 }
488
489 /* Make unique for different virtual servers handling same request
490 */
491 if (request->server) {
492 /*
493 * Make unique for different virtual servers handling same request
494 */
495 *((uint32_t *)(&entry->state[4])) ^= fr_hash_string(request->server);
496
497 /*
498 * Copy server to state in case it's needed for cleanup
499 */
500 entry->server = talloc_strdup(entry, request->server);
501 entry->request_number = request->number;
502 entry->request_client = request->client;
503 entry->request_root = request->root;
504 }
505
506 if (!state_entry_link(state, entry)) {
507 talloc_free(entry);
508 return NULL;
509 }
510
511 return entry;
512 }
513
514
515 /*
516 * Find the entry, based on the State attribute.
517 */
fr_state_find(fr_state_t * state,const char * server,RADIUS_PACKET * packet)518 static state_entry_t *fr_state_find(fr_state_t *state, const char *server, RADIUS_PACKET *packet)
519 {
520 VALUE_PAIR *vp;
521 state_entry_t *entry, my_entry;
522
523 vp = fr_pair_find_by_num(packet->vps, PW_STATE, 0, TAG_ANY);
524 if (!vp) return NULL;
525
526 /*
527 * Assume our own State first.
528 */
529 if (vp->vp_length == sizeof(my_entry.state)) {
530 memcpy(my_entry.state, vp->vp_octets, sizeof(my_entry.state));
531
532 /*
533 * Too big? Get the MD5 hash, in order
534 * to depend on the entire contents of State.
535 */
536 } else if (vp->vp_length > sizeof(my_entry.state)) {
537 fr_md5_calc(my_entry.state, vp->vp_octets, vp->vp_length);
538
539 /*
540 * Too small? Use the whole thing, and
541 * set the rest of my_entry.state to zero.
542 */
543 } else {
544 memcpy(my_entry.state, vp->vp_octets, vp->vp_length);
545 memset(&my_entry.state[vp->vp_length], 0, sizeof(my_entry.state) - vp->vp_length);
546 }
547
548 /* Make unique for different virtual servers handling same request
549 */
550 if (server) *((uint32_t *)(&my_entry.state[4])) ^= fr_hash_string(server);
551
552 entry = rbtree_finddata(state->tree, &my_entry);
553
554 #ifdef WITH_VERIFY_PTR
555 if (entry) (void) talloc_get_type_abort(entry, state_entry_t);
556 #endif
557
558 return entry;
559 }
560
561 /*
562 * Called when sending Access-Accept or Access-Reject, so
563 * that all State is discarded.
564 */
fr_state_discard(REQUEST * request,RADIUS_PACKET * original)565 void fr_state_discard(REQUEST *request, RADIUS_PACKET *original)
566 {
567 state_entry_t *entry;
568 fr_state_t *state = &global_state;
569
570 fr_pair_list_free(&request->state);
571 request->state = NULL;
572
573 PTHREAD_MUTEX_LOCK(&state->mutex);
574 entry = fr_state_find(state, request->server, original);
575 if (!entry) {
576 PTHREAD_MUTEX_UNLOCK(&state->mutex);
577 return;
578 }
579
580 state_entry_free(state, entry);
581 PTHREAD_MUTEX_UNLOCK(&state->mutex);
582 return;
583 }
584
585 /*
586 * Get the VPS from the state.
587 */
fr_state_get_vps(REQUEST * request,RADIUS_PACKET * packet)588 void fr_state_get_vps(REQUEST *request, RADIUS_PACKET *packet)
589 {
590 state_entry_t *entry;
591 fr_state_t *state = &global_state;
592 TALLOC_CTX *old_ctx = NULL;
593
594 /*
595 * No State, don't do anything.
596 */
597 if (!fr_pair_find_by_num(request->packet->vps, PW_STATE, 0, TAG_ANY)) {
598 RDEBUG3("session-state: No State attribute");
599 return;
600 }
601
602 rad_assert(request->state == NULL);
603
604 PTHREAD_MUTEX_LOCK(&state->mutex);
605 entry = fr_state_find(state, request->server, packet);
606
607 /*
608 * This has to be done in a mutex lock, because talloc
609 * isn't thread-safe.
610 */
611 if (entry) {
612 RDEBUG2("Restoring &session-state");
613
614 if (request->state_ctx) old_ctx = request->state_ctx;
615
616 request->state_ctx = entry->ctx;
617 request->state = entry->vps;
618
619 entry->ctx = NULL;
620 entry->vps = NULL;
621
622 rdebug_pair_list(L_DBG_LVL_2, request, request->state, "&session-state:");
623
624 } else {
625 RDEBUG2("session-state: No cached attributes");
626 }
627
628 PTHREAD_MUTEX_UNLOCK(&state->mutex);
629
630 /*
631 * Free this outside of the mutex for less contention.
632 */
633 if (old_ctx) talloc_free(old_ctx);
634
635 VERIFY_REQUEST(request);
636 return;
637 }
638
639
640 /*
641 * Put request->state into the State attribute. Put the State
642 * attribute into the vps list. Delete the original entry, if it
643 * exists.
644 */
fr_state_put_vps(REQUEST * request,RADIUS_PACKET * original,RADIUS_PACKET * packet)645 bool fr_state_put_vps(REQUEST *request, RADIUS_PACKET *original, RADIUS_PACKET *packet)
646 {
647 state_entry_t *entry, *old;
648 fr_state_t *state = &global_state;
649 state_entry_t *cleanup_list;
650
651 if (!request->state) {
652 size_t i;
653 uint32_t x;
654 VALUE_PAIR *vp;
655 uint8_t buffer[16];
656
657 RDEBUG3("session-state: Nothing to cache");
658
659 if (packet->code != PW_CODE_ACCESS_CHALLENGE) return true;
660
661 vp = fr_pair_find_by_num(packet->vps, PW_STATE, 0, TAG_ANY);
662 if (vp) return true;
663
664 /*
665 *
666 */
667 for (i = 0; i < sizeof(buffer) / sizeof(x); i++) {
668 x = fr_rand();
669 memcpy(buffer + (i * 4), &x, sizeof(x));
670 }
671
672 vp = fr_pair_afrom_num(packet, PW_STATE, 0);
673 fr_pair_value_memcpy(vp, buffer, sizeof(buffer));
674 fr_pair_add(&packet->vps, vp);
675
676 return true;
677 }
678
679 RDEBUG2("session-state: Saving cached attributes");
680 rdebug_pair_list(L_DBG_LVL_1, request, request->state, NULL);
681
682 PTHREAD_MUTEX_LOCK(&state->mutex);
683
684 cleanup_list = fr_state_cleanup_find(state);
685
686 if (original) {
687 old = fr_state_find(state, request->server, original);
688 } else {
689 old = NULL;
690 }
691
692 /*
693 * Create a new entry and add it to the list.
694 */
695 entry = fr_state_entry_create(state, request, packet, old);
696 if (!entry) {
697 PTHREAD_MUTEX_UNLOCK(&state->mutex);
698 fr_state_cleanup(cleanup_list);
699 return false;
700 }
701
702 rad_assert(entry->ctx == NULL);
703 entry->ctx = request->state_ctx;
704 entry->vps = request->state;
705
706 request->state_ctx = NULL;
707 request->state = NULL;
708
709 PTHREAD_MUTEX_UNLOCK(&state->mutex);
710 fr_state_cleanup(cleanup_list);
711
712 VERIFY_REQUEST(request);
713 return true;
714 }
715