1 /*
2  *	mem.c  Session handling, mostly taken from src/modules/rlm_eap/mem.c
3  *
4  *   This program is free software; you can redistribute it and/or modify
5  *   it under the terms of the GNU General Public License as published by
6  *   the Free Software Foundation; either version 2 of the License, or
7  *   (at your option) any later version.
8  *
9  *   This program is distributed in the hope that it will be useful,
10  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *   GNU General Public License for more details.
13  *
14  *   You should have received a copy of the GNU General Public License
15  *   along with this program; if not, write to the Free Software
16  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
17  *
18  * Copyright 2012  The FreeRADIUS server project
19  * Copyright 2012  Alan DeKok <aland@networkradius.com>
20  */
21 
22 #include <stdio.h>
23 #include "rlm_securid.h"
24 
25 static void		securid_sessionlist_clean_expired(rlm_securid_t *inst, REQUEST *request, time_t timestamp);
26 
27 static SECURID_SESSION* securid_sessionlist_delete(rlm_securid_t *inst,
28 						   SECURID_SESSION *session);
29 
securid_session_alloc(void)30 SECURID_SESSION* securid_session_alloc(void)
31 {
32 	SECURID_SESSION	*session;
33 
34 	session = rad_malloc(sizeof(SECURID_SESSION));
35 	memset(session, 0, sizeof(SECURID_SESSION));
36 
37 	session->sdiHandle = SDI_HANDLE_NONE;
38 
39 	return session;
40 }
41 
securid_session_free(UNUSED rlm_securid_t * inst,REQUEST * request,SECURID_SESSION * session)42 void securid_session_free(UNUSED rlm_securid_t *inst,REQUEST *request,
43 			  SECURID_SESSION *session)
44 {
45 	if (!session)
46 		return;
47 
48 	RDEBUG2("Freeing session id=%d identity='%s' state='%s'",
49 			 session->session_id,SAFE_STR(session->identity),session->state);
50 
51 	if (session->identity) {
52 		free(session->identity);
53 		session->identity = NULL;
54 	}
55 	if (session->pin) {
56 		free(session->pin);
57 		session->pin = NULL;
58 	}
59 
60 	if (session->sdiHandle != SDI_HANDLE_NONE) {
61 		SD_Close(session->sdiHandle);
62 		session->sdiHandle = SDI_HANDLE_NONE;
63 	}
64 
65 	free(session);
66 }
67 
68 
securid_sessionlist_free(rlm_securid_t * inst,REQUEST * request)69 void securid_sessionlist_free(rlm_securid_t *inst,REQUEST *request)
70 {
71 	SECURID_SESSION *node, *next;
72 
73 	pthread_mutex_lock(&(inst->session_mutex));
74 
75 	for (node = inst->session_head; node != NULL; node = next) {
76 		next = node->next;
77 		securid_session_free(inst,request,node);
78 	}
79 
80 	inst->session_head = inst->session_tail = NULL;
81 
82 	pthread_mutex_unlock(&(inst->session_mutex));
83 }
84 
85 
86 
87 /*
88  *	Add a session to the set of active sessions.
89  *
90  *	Since we're adding it to the list, we guess that this means
91  *	the packet needs a State attribute.  So add one.
92  */
securid_sessionlist_add(rlm_securid_t * inst,REQUEST * request,SECURID_SESSION * session)93 int securid_sessionlist_add(rlm_securid_t *inst,REQUEST *request, SECURID_SESSION *session)
94 {
95 	int		status = 0;
96 	VALUE_PAIR	*state;
97 
98 	/*
99 	 *	The time at which this request was made was the time
100 	 *	at which it was received by the RADIUS server.
101 	 */
102 	session->timestamp = request->timestamp;
103 
104 	session->src_ipaddr = request->packet->src_ipaddr;
105 
106 	/*
107 	 *	Playing with a data structure shared among threads
108 	 *	means that we need a lock, to avoid conflict.
109 	 */
110 	pthread_mutex_lock(&(inst->session_mutex));
111 
112 	/*
113 	 *	If we have a DoS attack, discard new sessions.
114 	 */
115 	if (rbtree_num_elements(inst->session_tree) >= inst->max_sessions) {
116 		securid_sessionlist_clean_expired(inst, request, session->timestamp);
117 		goto done;
118 	}
119 
120 	if (session->session_id == 0) {
121 		/* this is a NEW session (we are not inserting an updated session) */
122 		inst->last_session_id++;
123 		session->session_id = inst->last_session_id;
124 		RDEBUG2("Creating a new session with id=%d\n",session->session_id);
125 	}
126 
127 	memset(session->state, 0, sizeof(session->state));
128 	snprintf(session->state,sizeof(session->state)-1,"FRR-CH %d|%d",session->session_id,session->trips+1);
129 	RDEBUG2("Inserting session id=%d identity='%s' state='%s' to the session list",
130 			 session->session_id,SAFE_STR(session->identity),session->state);
131 
132 
133 	/*
134 	 *	Generate State, since we've been asked to add it to
135 	 *	the list.
136 	 */
137 	state = fr_pair_make(request->reply, &request->reply->vps, "State", NULL, T_OP_EQ);
138 	if (!state) return -1;
139 
140 	fr_pair_value_memcpy(state, session->state, sizeof(session->state));
141 
142 	status = rbtree_insert(inst->session_tree, session);
143 	if (status) {
144 		/* tree insert SUCCESS */
145 		/* insert the session to the linked list of sessions */
146 		SECURID_SESSION *prev;
147 
148 		prev = inst->session_tail;
149 		if (prev) {
150 			/* insert to the tail of the list */
151 			prev->next = session;
152 			session->prev = prev;
153 			session->next = NULL;
154 			inst->session_tail = session;
155 		} else {
156 			/* 1st time */
157 			inst->session_head = inst->session_tail = session;
158 			session->next = session->prev = NULL;
159 		}
160 	}
161 
162 	/*
163 	 *	Now that we've finished mucking with the list,
164 	 *	unlock it.
165 	 */
166  done:
167 	pthread_mutex_unlock(&(inst->session_mutex));
168 
169 	if (!status) {
170 		fr_pair_list_free(&state);
171 		ERROR("rlm_securid: Failed to store session");
172 		return -1;
173 	}
174 
175 	return 0;
176 }
177 
178 /*
179  *	Find existing session if any which matches the State variable in current AccessRequest
180  *	Then, release the session from the list, and return it to
181  *	the caller.
182  *
183  */
securid_sessionlist_find(rlm_securid_t * inst,REQUEST * request)184 SECURID_SESSION *securid_sessionlist_find(rlm_securid_t *inst, REQUEST *request)
185 {
186 	VALUE_PAIR	*state;
187 	SECURID_SESSION* session;
188 	SECURID_SESSION mySession;
189 
190 	/* clean expired sessions if any */
191 	pthread_mutex_lock(&(inst->session_mutex));
192 	securid_sessionlist_clean_expired(inst, request, request->timestamp);
193 	pthread_mutex_unlock(&(inst->session_mutex));
194 
195 	/*
196 	 *	We key the sessions off of the 'state' attribute
197 	 */
198 	state = fr_pair_find_by_num(request->packet->vps, PW_STATE, 0, TAG_ANY);
199 	if (!state) {
200 		return NULL;
201 	}
202 
203 	if (state->vp_length != SECURID_STATE_LEN) {
204 	  ERROR("rlm_securid: Invalid State variable. length=%d", (int) state->vp_length);
205 		return NULL;
206 	}
207 
208 	memset(&mySession,0,sizeof(mySession));
209 	mySession.src_ipaddr = request->packet->src_ipaddr;
210 	memcpy(mySession.state, state->vp_strvalue, sizeof(mySession.state));
211 
212 	/*
213 	 *	Playing with a data structure shared among threads
214 	 *	means that we need a lock, to avoid conflict.
215 	 */
216 	pthread_mutex_lock(&(inst->session_mutex));
217 	session = securid_sessionlist_delete(inst, &mySession);
218 	pthread_mutex_unlock(&(inst->session_mutex));
219 
220 	/*
221 	 *	Might not have been there.
222 	 */
223 	if (!session) {
224 		ERROR("rlm_securid: No SECURID session matching the State variable");
225 		return NULL;
226 	}
227 
228 	RDEBUG2("Session found identity='%s' state='%s', released from the list",
229 			 SAFE_STR(session->identity),session->state);
230 	if (session->trips >= inst->max_trips_per_session) {
231 		RDEBUG2("More than %d authentication packets for this SECURID session.  Aborted.",inst->max_trips_per_session);
232 		securid_session_free(inst,request,session);
233 		return NULL;
234 	}
235 	session->trips++;
236 
237 	return session;
238 }
239 
240 
241 /************ private functions *************/
securid_sessionlist_delete(rlm_securid_t * inst,SECURID_SESSION * session)242 static SECURID_SESSION *securid_sessionlist_delete(rlm_securid_t *inst, SECURID_SESSION *session)
243 {
244 	rbnode_t *node;
245 
246 	node = rbtree_find(inst->session_tree, session);
247 	if (!node) return NULL;
248 
249 	session = rbtree_node2data(inst->session_tree, node);
250 
251 	/*
252 	 *	Delete old session from the tree.
253 	 */
254 	rbtree_delete(inst->session_tree, node);
255 
256 	/*
257 	 *	And unsplice it from the linked list.
258 	 */
259 	if (session->prev) {
260 		session->prev->next = session->next;
261 	} else {
262 		inst->session_head = session->next;
263 	}
264 	if (session->next) {
265 		session->next->prev = session->prev;
266 	} else {
267 		inst->session_tail = session->prev;
268 	}
269 	session->prev = session->next = NULL;
270 
271 	return session;
272 }
273 
274 
securid_sessionlist_clean_expired(rlm_securid_t * inst,REQUEST * request,time_t timestamp)275 static void securid_sessionlist_clean_expired(rlm_securid_t *inst, REQUEST *request, time_t timestamp)
276 {
277 	int num_sessions;
278 	SECURID_SESSION *session;
279 
280 	num_sessions = rbtree_num_elements(inst->session_tree);
281 	RDEBUG2("There are %d sessions in the tree\n",num_sessions);
282 
283 	/*
284 	 *	Delete old sessions from the list
285 	 *
286 	 */
287 	while((session = inst->session_head)) {
288 		if ((timestamp - session->timestamp) > inst->timer_limit) {
289 			rbnode_t *node;
290 			node = rbtree_find(inst->session_tree, session);
291 			rad_assert(node != NULL);
292 			rbtree_delete(inst->session_tree, node);
293 
294 			/*
295 			 *	session == inst->session_head
296 			 */
297 			inst->session_head = session->next;
298 			if (session->next) {
299 				session->next->prev = NULL;
300 			} else {
301 				inst->session_head = NULL;
302 				inst->session_tail = NULL;
303 			}
304 
305 			RDEBUG2("Cleaning expired session: identity='%s' state='%s'\n",
306 					  SAFE_STR(session->identity),session->state);
307 			securid_session_free(inst,request,session);
308 		} else {
309 			/* no need to check all sessions since they are sorted by age */
310 			break;
311 		}
312 	}
313 }
314