update of Copyright date in recently updated files
[freeradius.git] / src / modules / rlm_securid / mem.c
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 <freeradius-devel/ident.h>
23 #include <stdio.h>
24 #include "rlm_securid.h"
25
26 static void             securid_sessionlist_clean_expired(rlm_securid_t *inst, REQUEST *request, time_t timestamp);
27
28 static SECURID_SESSION* securid_sessionlist_delete(rlm_securid_t *inst,
29                                                    SECURID_SESSION *session);
30
31 SECURID_SESSION* securid_session_alloc(void)
32 {
33         SECURID_SESSION *session;
34
35         session = rad_malloc(sizeof(SECURID_SESSION));
36         memset(session, 0, sizeof(SECURID_SESSION));
37
38         session->sdiHandle = SDI_HANDLE_NONE;
39
40         return session;
41 }
42
43 void securid_session_free(rlm_securid_t *inst,REQUEST *request,
44                           SECURID_SESSION *session)
45 {
46         if (!session)
47                 return;
48
49         RDEBUG2("Freeing session id=%d identity='%s' state='%s'",
50                          session->session_id,SAFE_STR(session->identity),session->state);
51
52         if (session->identity) {
53                 free(session->identity);
54                 session->identity = NULL;
55         }
56         if (session->pin) {
57                 free(session->pin);
58                 session->pin = NULL;
59         }
60
61         if (session->sdiHandle != SDI_HANDLE_NONE) {
62                 SD_Close(session->sdiHandle);
63                 session->sdiHandle = SDI_HANDLE_NONE;
64         }
65
66         free(session);
67 }
68
69
70 void securid_sessionlist_free(rlm_securid_t *inst,REQUEST *request)
71 {
72         SECURID_SESSION *node, *next;
73
74         pthread_mutex_lock(&(inst->session_mutex));
75
76         for (node = inst->session_head; node != NULL; node = next) {
77                 next = node->next;
78                 securid_session_free(inst,request,node);
79         }
80
81         inst->session_head = inst->session_tail = NULL;
82
83         pthread_mutex_unlock(&(inst->session_mutex));
84 }
85
86
87
88 /*
89  *      Add a session to the set of active sessions.
90  *
91  *      Since we're adding it to the list, we guess that this means
92  *      the packet needs a State attribute.  So add one.
93  */
94 int securid_sessionlist_add(rlm_securid_t *inst,REQUEST *request,
95                             SECURID_SESSION *session)
96 {
97         int             status = 0;
98         VALUE_PAIR      *state;
99
100         rad_assert(session != NULL);
101         rad_assert(request != NULL);
102
103         /*
104          *      The time at which this request was made was the time
105          *      at which it was received by the RADIUS server.
106          */
107         session->timestamp = request->timestamp;
108
109         session->src_ipaddr = request->packet->src_ipaddr;
110
111         /*
112          *      Playing with a data structure shared among threads
113          *      means that we need a lock, to avoid conflict.
114          */
115         pthread_mutex_lock(&(inst->session_mutex));
116
117         /*
118          *      If we have a DoS attack, discard new sessions.
119          */
120         if (rbtree_num_elements(inst->session_tree) >= inst->max_sessions) {
121                 securid_sessionlist_clean_expired(inst, request, session->timestamp);
122                 goto done;
123         }
124         
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);
130         }
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);
134
135
136         /*
137          *      Generate State, since we've been asked to add it to
138          *      the list.
139          */
140         state = pairmake("State", session->state, T_OP_EQ);
141         if (!state) return -1;
142         state->length = SECURID_STATE_LEN;
143
144
145
146         status = rbtree_insert(inst->session_tree, session);
147         if (status) {
148                 /* tree insert SUCCESS */
149                 /* insert the session to the linked list of sessions */
150                 SECURID_SESSION *prev;
151
152                 prev = inst->session_tail;
153                 if (prev) {
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;
159                 } else {
160                         /* 1st time */
161                         inst->session_head = inst->session_tail = session;
162                         session->next = session->prev = NULL;
163                 }
164         }
165
166         /*
167          *      Now that we've finished mucking with the list,
168          *      unlock it.
169          */
170  done:
171         pthread_mutex_unlock(&(inst->session_mutex));
172
173         if (!status) {
174                 pairfree(&state);
175                 radlog(L_ERR, "rlm_securid: Failed to store session");
176                 return -1;
177         }
178
179         pairadd(&(request->reply->vps), state);
180
181         return 0;
182 }
183
184 /*
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
187  *      the caller.
188  *
189  */
190 SECURID_SESSION *securid_sessionlist_find(rlm_securid_t *inst, REQUEST *request)
191 {
192         VALUE_PAIR      *state;
193         SECURID_SESSION* session;
194         SECURID_SESSION mySession;
195         
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));
200
201         /*
202          *      We key the sessions off of the 'state' attribute
203          */
204         state = pairfind(request->packet->vps, PW_STATE);
205         if (!state) {
206                 return NULL;
207         }
208
209         if (state->length != SECURID_STATE_LEN) {
210                 radlog(L_ERR,"rlm_securid: Invalid State variable. length=%d",state->length);
211                 return NULL;
212         }
213
214         memset(&mySession,0,sizeof(mySession));
215         mySession.src_ipaddr = request->packet->src_ipaddr;
216         memcpy(mySession.state, state->vp_strvalue, sizeof(mySession.state));
217
218         /*
219          *      Playing with a data structure shared among threads
220          *      means that we need a lock, to avoid conflict.
221          */
222         pthread_mutex_lock(&(inst->session_mutex));
223         session = securid_sessionlist_delete(inst, &mySession);
224         pthread_mutex_unlock(&(inst->session_mutex));
225
226         /*
227          *      Might not have been there.
228          */
229         if (!session) {
230                 radlog(L_ERR,"rlm_securid: No SECURID session matching the State variable.");
231                 return NULL;
232         }
233
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);
239                 return NULL;
240         }
241         session->trips++;
242
243         return session;
244 }
245
246
247 /************ private functions *************/
248 static SECURID_SESSION *securid_sessionlist_delete(rlm_securid_t *inst, SECURID_SESSION *session)
249 {
250         rbnode_t *node;
251
252         node = rbtree_find(inst->session_tree, session);
253         if (!node) return NULL;
254
255         session = rbtree_node2data(inst->session_tree, node);
256
257         /*
258          *      Delete old session from the tree.
259          */
260         rbtree_delete(inst->session_tree, node);
261         
262         /*
263          *      And unsplice it from the linked list.
264          */
265         if (session->prev) {
266                 session->prev->next = session->next;
267         } else {
268                 inst->session_head = session->next;
269         }
270         if (session->next) {
271                 session->next->prev = session->prev;
272         } else {
273                 inst->session_tail = session->prev;
274         }
275         session->prev = session->next = NULL;
276
277         return session;
278 }
279
280
281 static void securid_sessionlist_clean_expired(rlm_securid_t *inst, REQUEST *request, time_t timestamp)
282 {
283         int num_sessions;
284         SECURID_SESSION *session;
285
286         num_sessions = rbtree_num_elements(inst->session_tree);
287         RDEBUG2("There are %d sessions in the tree\n",num_sessions);
288
289         /*
290          *      Delete old sessions from the list
291          *
292          */
293         while((session = inst->session_head)) {
294                 if ((timestamp - session->timestamp) > inst->timer_limit) {
295                         rbnode_t *node;
296                         node = rbtree_find(inst->session_tree, session);
297                         rad_assert(node != NULL);
298                         rbtree_delete(inst->session_tree, node);
299
300                         /*
301                          *      session == inst->session_head
302                          */
303                         inst->session_head = session->next;
304                         if (session->next) {
305                                 session->next->prev = NULL;
306                         } else {
307                                 inst->session_head = NULL;
308                                 inst->session_tail = NULL;
309                         }
310
311                         RDEBUG2("Cleaning expired session: identity='%s' state='%s'\n",
312                                           SAFE_STR(session->identity),session->state);
313                         securid_session_free(inst,request,session);
314                 } else {
315                         /* no need to check all sessions since they are sorted by age */
316                         break;
317                 }
318         }
319 }