diff options
Diffstat (limited to 'src/modules/rlm_securid/mem.c')
-rw-r--r-- | src/modules/rlm_securid/mem.c | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/src/modules/rlm_securid/mem.c b/src/modules/rlm_securid/mem.c new file mode 100644 index 0000000..6e262e8 --- /dev/null +++ b/src/modules/rlm_securid/mem.c @@ -0,0 +1,313 @@ +/* + * mem.c Session handling, mostly taken from src/modules/rlm_eap/mem.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2012 The FreeRADIUS server project + * Copyright 2012 Alan DeKok <aland@networkradius.com> + */ + +#include <stdio.h> +#include "rlm_securid.h" + +static void securid_sessionlist_clean_expired(rlm_securid_t *inst, REQUEST *request, time_t timestamp); + +static SECURID_SESSION* securid_sessionlist_delete(rlm_securid_t *inst, + SECURID_SESSION *session); + +SECURID_SESSION* securid_session_alloc(void) +{ + SECURID_SESSION *session; + + session = rad_malloc(sizeof(SECURID_SESSION)); + memset(session, 0, sizeof(SECURID_SESSION)); + + session->sdiHandle = SDI_HANDLE_NONE; + + return session; +} + +void securid_session_free(UNUSED rlm_securid_t *inst,REQUEST *request, + SECURID_SESSION *session) +{ + if (!session) + return; + + RDEBUG2("Freeing session id=%d identity='%s' state='%s'", + session->session_id,SAFE_STR(session->identity),session->state); + + if (session->identity) { + free(session->identity); + session->identity = NULL; + } + if (session->pin) { + free(session->pin); + session->pin = NULL; + } + + if (session->sdiHandle != SDI_HANDLE_NONE) { + SD_Close(session->sdiHandle); + session->sdiHandle = SDI_HANDLE_NONE; + } + + free(session); +} + + +void securid_sessionlist_free(rlm_securid_t *inst,REQUEST *request) +{ + SECURID_SESSION *node, *next; + + pthread_mutex_lock(&(inst->session_mutex)); + + for (node = inst->session_head; node != NULL; node = next) { + next = node->next; + securid_session_free(inst,request,node); + } + + inst->session_head = inst->session_tail = NULL; + + pthread_mutex_unlock(&(inst->session_mutex)); +} + + + +/* + * Add a session to the set of active sessions. + * + * Since we're adding it to the list, we guess that this means + * the packet needs a State attribute. So add one. + */ +int securid_sessionlist_add(rlm_securid_t *inst,REQUEST *request, SECURID_SESSION *session) +{ + int status = 0; + VALUE_PAIR *state; + + /* + * The time at which this request was made was the time + * at which it was received by the RADIUS server. + */ + session->timestamp = request->timestamp; + + session->src_ipaddr = request->packet->src_ipaddr; + + /* + * Playing with a data structure shared among threads + * means that we need a lock, to avoid conflict. + */ + pthread_mutex_lock(&(inst->session_mutex)); + + /* + * If we have a DoS attack, discard new sessions. + */ + if (rbtree_num_elements(inst->session_tree) >= inst->max_sessions) { + securid_sessionlist_clean_expired(inst, request, session->timestamp); + goto done; + } + + if (session->session_id == 0) { + /* this is a NEW session (we are not inserting an updated session) */ + inst->last_session_id++; + session->session_id = inst->last_session_id; + RDEBUG2("Creating a new session with id=%d\n",session->session_id); + } + + memset(session->state, 0, sizeof(session->state)); + snprintf(session->state,sizeof(session->state)-1,"FRR-CH %d|%d",session->session_id,session->trips+1); + RDEBUG2("Inserting session id=%d identity='%s' state='%s' to the session list", + session->session_id,SAFE_STR(session->identity),session->state); + + + /* + * Generate State, since we've been asked to add it to + * the list. + */ + state = fr_pair_make(request->reply, &request->reply->vps, "State", NULL, T_OP_EQ); + if (!state) return -1; + + fr_pair_value_memcpy(state, session->state, sizeof(session->state)); + + status = rbtree_insert(inst->session_tree, session); + if (status) { + /* tree insert SUCCESS */ + /* insert the session to the linked list of sessions */ + SECURID_SESSION *prev; + + prev = inst->session_tail; + if (prev) { + /* insert to the tail of the list */ + prev->next = session; + session->prev = prev; + session->next = NULL; + inst->session_tail = session; + } else { + /* 1st time */ + inst->session_head = inst->session_tail = session; + session->next = session->prev = NULL; + } + } + + /* + * Now that we've finished mucking with the list, + * unlock it. + */ + done: + pthread_mutex_unlock(&(inst->session_mutex)); + + if (!status) { + fr_pair_list_free(&state); + ERROR("rlm_securid: Failed to store session"); + return -1; + } + + return 0; +} + +/* + * Find existing session if any which matches the State variable in current AccessRequest + * Then, release the session from the list, and return it to + * the caller. + * + */ +SECURID_SESSION *securid_sessionlist_find(rlm_securid_t *inst, REQUEST *request) +{ + VALUE_PAIR *state; + SECURID_SESSION* session; + SECURID_SESSION mySession; + + /* clean expired sessions if any */ + pthread_mutex_lock(&(inst->session_mutex)); + securid_sessionlist_clean_expired(inst, request, request->timestamp); + pthread_mutex_unlock(&(inst->session_mutex)); + + /* + * We key the sessions off of the 'state' attribute + */ + state = fr_pair_find_by_num(request->packet->vps, PW_STATE, 0, TAG_ANY); + if (!state) { + return NULL; + } + + if (state->vp_length != SECURID_STATE_LEN) { + ERROR("rlm_securid: Invalid State variable. length=%d", (int) state->vp_length); + return NULL; + } + + memset(&mySession,0,sizeof(mySession)); + mySession.src_ipaddr = request->packet->src_ipaddr; + memcpy(mySession.state, state->vp_strvalue, sizeof(mySession.state)); + + /* + * Playing with a data structure shared among threads + * means that we need a lock, to avoid conflict. + */ + pthread_mutex_lock(&(inst->session_mutex)); + session = securid_sessionlist_delete(inst, &mySession); + pthread_mutex_unlock(&(inst->session_mutex)); + + /* + * Might not have been there. + */ + if (!session) { + ERROR("rlm_securid: No SECURID session matching the State variable"); + return NULL; + } + + RDEBUG2("Session found identity='%s' state='%s', released from the list", + SAFE_STR(session->identity),session->state); + if (session->trips >= inst->max_trips_per_session) { + RDEBUG2("More than %d authentication packets for this SECURID session. Aborted.",inst->max_trips_per_session); + securid_session_free(inst,request,session); + return NULL; + } + session->trips++; + + return session; +} + + +/************ private functions *************/ +static SECURID_SESSION *securid_sessionlist_delete(rlm_securid_t *inst, SECURID_SESSION *session) +{ + rbnode_t *node; + + node = rbtree_find(inst->session_tree, session); + if (!node) return NULL; + + session = rbtree_node2data(inst->session_tree, node); + + /* + * Delete old session from the tree. + */ + rbtree_delete(inst->session_tree, node); + + /* + * And unsplice it from the linked list. + */ + if (session->prev) { + session->prev->next = session->next; + } else { + inst->session_head = session->next; + } + if (session->next) { + session->next->prev = session->prev; + } else { + inst->session_tail = session->prev; + } + session->prev = session->next = NULL; + + return session; +} + + +static void securid_sessionlist_clean_expired(rlm_securid_t *inst, REQUEST *request, time_t timestamp) +{ + int num_sessions; + SECURID_SESSION *session; + + num_sessions = rbtree_num_elements(inst->session_tree); + RDEBUG2("There are %d sessions in the tree\n",num_sessions); + + /* + * Delete old sessions from the list + * + */ + while((session = inst->session_head)) { + if ((timestamp - session->timestamp) > inst->timer_limit) { + rbnode_t *node; + node = rbtree_find(inst->session_tree, session); + rad_assert(node != NULL); + rbtree_delete(inst->session_tree, node); + + /* + * session == inst->session_head + */ + inst->session_head = session->next; + if (session->next) { + session->next->prev = NULL; + } else { + inst->session_head = NULL; + inst->session_tail = NULL; + } + + RDEBUG2("Cleaning expired session: identity='%s' state='%s'\n", + SAFE_STR(session->identity),session->state); + securid_session_free(inst,request,session); + } else { + /* no need to check all sessions since they are sorted by age */ + break; + } + } +} |