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