/* * 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 */ #include #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; } } }