2 * mem.c Session handling, mostly taken from src/modules/rlm_eap/mem.c
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.
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.
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
18 * Copyright 2012 The FreeRADIUS server project
19 * Copyright 2012 Alan DeKok <aland@networkradius.com>
22 #include <freeradius-devel/ident.h>
24 #include "rlm_securid.h"
26 static void securid_sessionlist_clean_expired(rlm_securid_t *inst, REQUEST *request, time_t timestamp);
28 static SECURID_SESSION* securid_sessionlist_delete(rlm_securid_t *inst,
29 SECURID_SESSION *session);
31 SECURID_SESSION* securid_session_alloc(void)
33 SECURID_SESSION *session;
35 session = rad_malloc(sizeof(SECURID_SESSION));
36 memset(session, 0, sizeof(SECURID_SESSION));
38 session->sdiHandle = SDI_HANDLE_NONE;
43 void securid_session_free(rlm_securid_t *inst,REQUEST *request,
44 SECURID_SESSION *session)
49 RDEBUG2("Freeing session id=%d identity='%s' state='%s'",
50 session->session_id,SAFE_STR(session->identity),session->state);
52 if (session->identity) {
53 free(session->identity);
54 session->identity = NULL;
61 if (session->sdiHandle != SDI_HANDLE_NONE) {
62 SD_Close(session->sdiHandle);
63 session->sdiHandle = SDI_HANDLE_NONE;
70 void securid_sessionlist_free(rlm_securid_t *inst,REQUEST *request)
72 SECURID_SESSION *node, *next;
74 pthread_mutex_lock(&(inst->session_mutex));
76 for (node = inst->session_head; node != NULL; node = next) {
78 securid_session_free(inst,request,node);
81 inst->session_head = inst->session_tail = NULL;
83 pthread_mutex_unlock(&(inst->session_mutex));
89 * Add a session to the set of active sessions.
91 * Since we're adding it to the list, we guess that this means
92 * the packet needs a State attribute. So add one.
94 int securid_sessionlist_add(rlm_securid_t *inst,REQUEST *request,
95 SECURID_SESSION *session)
100 rad_assert(session != NULL);
101 rad_assert(request != NULL);
104 * The time at which this request was made was the time
105 * at which it was received by the RADIUS server.
107 session->timestamp = request->timestamp;
109 session->src_ipaddr = request->packet->src_ipaddr;
112 * Playing with a data structure shared among threads
113 * means that we need a lock, to avoid conflict.
115 pthread_mutex_lock(&(inst->session_mutex));
118 * If we have a DoS attack, discard new sessions.
120 if (rbtree_num_elements(inst->session_tree) >= inst->max_sessions) {
121 securid_sessionlist_clean_expired(inst, request, session->timestamp);
125 if (session->session_id == 0) {
126 /* this is a NEW session (we are not inserting an updated session) */
127 inst->last_session_id++;
128 session->session_id = inst->last_session_id;
129 RDEBUG2("Creating a new session with id=%d\n",session->session_id);
131 snprintf(session->state,sizeof(session->state)-1,"FRR-CH %d|%d",session->session_id,session->trips+1);
132 RDEBUG2("Inserting session id=%d identity='%s' state='%s' to the session list",
133 session->session_id,SAFE_STR(session->identity),session->state);
137 * Generate State, since we've been asked to add it to
140 state = pairmake("State", session->state, T_OP_EQ);
141 if (!state) return -1;
142 state->length = SECURID_STATE_LEN;
146 status = rbtree_insert(inst->session_tree, session);
148 /* tree insert SUCCESS */
149 /* insert the session to the linked list of sessions */
150 SECURID_SESSION *prev;
152 prev = inst->session_tail;
154 /* insert to the tail of the list */
155 prev->next = session;
156 session->prev = prev;
157 session->next = NULL;
158 inst->session_tail = session;
161 inst->session_head = inst->session_tail = session;
162 session->next = session->prev = NULL;
167 * Now that we've finished mucking with the list,
171 pthread_mutex_unlock(&(inst->session_mutex));
175 radlog(L_ERR, "rlm_securid: Failed to store session");
179 pairadd(&(request->reply->vps), state);
185 * Find existing session if any which matches the State variable in current AccessRequest
186 * Then, release the session from the list, and return it to
190 SECURID_SESSION *securid_sessionlist_find(rlm_securid_t *inst, REQUEST *request)
193 SECURID_SESSION* session;
194 SECURID_SESSION mySession;
196 /* clean expired sessions if any */
197 pthread_mutex_lock(&(inst->session_mutex));
198 securid_sessionlist_clean_expired(inst, request, request->timestamp);
199 pthread_mutex_unlock(&(inst->session_mutex));
202 * We key the sessions off of the 'state' attribute
204 state = pairfind(request->packet->vps, PW_STATE);
209 if (state->length != SECURID_STATE_LEN) {
210 radlog(L_ERR,"rlm_securid: Invalid State variable. length=%d",state->length);
214 memset(&mySession,0,sizeof(mySession));
215 mySession.src_ipaddr = request->packet->src_ipaddr;
216 memcpy(mySession.state, state->vp_strvalue, sizeof(mySession.state));
219 * Playing with a data structure shared among threads
220 * means that we need a lock, to avoid conflict.
222 pthread_mutex_lock(&(inst->session_mutex));
223 session = securid_sessionlist_delete(inst, &mySession);
224 pthread_mutex_unlock(&(inst->session_mutex));
227 * Might not have been there.
230 radlog(L_ERR,"rlm_securid: No SECURID session matching the State variable.");
234 RDEBUG2("Session found identity='%s' state='%s', released from the list",
235 SAFE_STR(session->identity),session->state);
236 if (session->trips >= inst->max_trips_per_session) {
237 RDEBUG2("More than %d authentication packets for this SECURID session. Aborted.",inst->max_trips_per_session);
238 securid_session_free(inst,request,session);
247 /************ private functions *************/
248 static SECURID_SESSION *securid_sessionlist_delete(rlm_securid_t *inst, SECURID_SESSION *session)
252 node = rbtree_find(inst->session_tree, session);
253 if (!node) return NULL;
255 session = rbtree_node2data(inst->session_tree, node);
258 * Delete old session from the tree.
260 rbtree_delete(inst->session_tree, node);
263 * And unsplice it from the linked list.
266 session->prev->next = session->next;
268 inst->session_head = session->next;
271 session->next->prev = session->prev;
273 inst->session_tail = session->prev;
275 session->prev = session->next = NULL;
281 static void securid_sessionlist_clean_expired(rlm_securid_t *inst, REQUEST *request, time_t timestamp)
284 SECURID_SESSION *session;
286 num_sessions = rbtree_num_elements(inst->session_tree);
287 RDEBUG2("There are %d sessions in the tree\n",num_sessions);
290 * Delete old sessions from the list
293 while((session = inst->session_head)) {
294 if ((timestamp - session->timestamp) > inst->timer_limit) {
296 node = rbtree_find(inst->session_tree, session);
297 rad_assert(node != NULL);
298 rbtree_delete(inst->session_tree, node);
301 * session == inst->session_head
303 inst->session_head = session->next;
305 session->next->prev = NULL;
307 inst->session_head = NULL;
308 inst->session_tail = NULL;
311 RDEBUG2("Cleaning expired session: identity='%s' state='%s'\n",
312 SAFE_STR(session->identity),session->state);
313 securid_session_free(inst,request,session);
315 /* no need to check all sessions since they are sorted by age */