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