From: Alan T. DeKok Date: Tue, 22 Nov 2011 09:46:16 +0000 (+0100) Subject: Manually pull rlm_securid from the v2.1.x branch X-Git-Tag: release_3_0_0_beta0~487 X-Git-Url: http://www.project-moonshot.org/gitweb/?a=commitdiff_plain;h=cdcba35ca9b56799450de91c85723a853c522151;p=freeradius.git Manually pull rlm_securid from the v2.1.x branch With minor changes for the 3.0 API --- diff --git a/src/modules/rlm_securid/Makefile b/src/modules/rlm_securid/Makefile new file mode 100644 index 0000000..9fbc74e --- /dev/null +++ b/src/modules/rlm_securid/Makefile @@ -0,0 +1,34 @@ +# +# Makefile +# +# You MUST edit this file by hand to make it work. +# There is NO "configure" script available. +# +# Version: $Id: $ +# + +# +# SET THE TARGET +# +TARGET = +#TARGET = rlm_securid + +SRCS = rlm_securid.c mem.c + +# +# SET THE CORRECT PATH TO THE SECURID FILES +# +ACE_PATH = /path/to/SECURID81 +ARCH = lnx + +HEADERS = rlm_securid.h +RLM_LIBS = -laceclnt + +# +# YOU WILL PROBABLY NEED TO COPY $(ACE_PATH/lib/$(ARCH) to /usr/lib +# +RLM_CFLAGS = -I$(ACE_PATH)/inc -DUNIX + +include ../rules.mak + +$(LT_OBJS): $(HEADERS) diff --git a/src/modules/rlm_securid/README b/src/modules/rlm_securid/README new file mode 100644 index 0000000..ba48346 --- /dev/null +++ b/src/modules/rlm_securid/README @@ -0,0 +1,30 @@ + This module implements SecurID token checking. It should be listed +in the "aythenticate" section. + + The module configuration is in the "securid" file. You will need to +copy it by hand to the raddb/modules/directory. + + There is no "configure" script. Instead, you MUST edit the +"Makefile" by hand in order to build the module. See the Makefile for +instructions. + + Many people will wonder about the license issues involved in +distributing this module. The short answer is that the source can be +distributed, the binaries cannot be distributed. The explanation +is given below. + + This module falls under the GPLv2 license. The primary goal of this +license is largely to ensure that you have access to the source code, +which is included here. A secondary goal of this license is to ensure +that binary distributions can be re-built from the existing source +code. This is done by requiring binary distributions to offer source +code for the GPLd binary, and to distribute ALL DEPENDENT LIBRARIES. + + The RSA libraries are proprietary to RSA, and cannot be distributed. +Therefore, any library (rlm_securid.a, rlm_securid.so, etc.) CANNOT be +distributed by ANYONE. + + The module is still useful, of course. The GPL restriction on +distribution apply only on distribution to third parties. This means +that building the module, and using it within your organization is +allowed under the GPL. diff --git a/src/modules/rlm_securid/mem.c b/src/modules/rlm_securid/mem.c new file mode 100644 index 0000000..df40527 --- /dev/null +++ b/src/modules/rlm_securid/mem.c @@ -0,0 +1,319 @@ +/* + * 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 2011 The FreeRADIUS server project + * Copyright 201 Alan DeKok + */ + +#include +#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(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; + + rad_assert(session != NULL); + rad_assert(request != NULL); + + /* + * 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); + } + 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 = pairmake("State", session->state, T_OP_EQ); + if (!state) return -1; + state->length = SECURID_STATE_LEN; + + + + 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) { + pairfree(&state); + radlog(L_ERR, "rlm_securid: Failed to store session"); + return -1; + } + + pairadd(&(request->reply->vps), state); + + 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 = pairfind(request->packet->vps, PW_STATE); + if (!state) { + return NULL; + } + + if (state->length != SECURID_STATE_LEN) { + radlog(L_ERR,"rlm_securid: Invalid State variable. length=%d",state->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) { + radlog(L_ERR,"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; + } + } +} diff --git a/src/modules/rlm_securid/rlm_securid.c b/src/modules/rlm_securid/rlm_securid.c new file mode 100644 index 0000000..afe61d6 --- /dev/null +++ b/src/modules/rlm_securid/rlm_securid.c @@ -0,0 +1,596 @@ +/* + * rlm_securid.c + * + * Version: $Id: $ + * + * supports "next-token code" and "new-pin" modes + * + * + * 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 2011 The FreeRADIUS server project + * Copyright 2011 Alan DeKok + */ + +#include +#include +#include +#include + +#include "rlm_securid.h" + +typedef enum { + RC_SECURID_AUTH_SUCCESS = 0, + RC_SECURID_AUTH_FAILURE = -3, + RC_SECURID_AUTH_ACCESS_DENIED_FAILURE = -4, + RC_SECURID_AUTH_INVALID_SERVER_FAILURE = -5, + RC_SECURID_AUTH_CHALLENGE = -17 +} + SECURID_AUTH_RC; + + +static const CONF_PARSER module_config[] = { + { "timer_expire", PW_TYPE_INTEGER, + offsetof(rlm_securid_t, timer_limit), NULL, "600"}, + { "max_sessions", PW_TYPE_INTEGER, + offsetof(rlm_securid_t, max_sessions), NULL, "2048"}, + { "max_trips_per_session", PW_TYPE_INTEGER, + offsetof(rlm_securid_t, max_trips_per_session), NULL, NULL}, + { "max_round_trips", PW_TYPE_INTEGER, + offsetof(rlm_securid_t, max_trips_per_session), NULL, "6"}, + { NULL, -1, 0, NULL, NULL } /* end the list */ +}; + + +/* comparison function to find session in the tree */ +static int securid_session_cmp(const void *a, const void *b) +{ + int rcode; + const SECURID_SESSION *one = a; + const SECURID_SESSION *two = b; + + rad_assert(one != NULL); + rad_assert(two != NULL); + + rcode = fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr); + if (rcode != 0) return rcode; + + return memcmp(one->state, two->state, sizeof(one->state)); +} + + +static SECURID_AUTH_RC securidAuth(void *instance, REQUEST *request, + const char* username, + const char* passcode, + char* replyMsgBuffer,int replyMsgBufferSize) +{ + rlm_securid_t *inst = (rlm_securid_t *) instance; + int acmRet; + SD_PIN pinParams; + char newPin[10]; + char format[30]; + SECURID_SESSION *pSecurid_session=NULL; + int rc=-1; + + if (!username) { + radlog(L_ERR, "SecurID username is NULL"); + return RC_SECURID_AUTH_FAILURE; + } + + if (!passcode) { + radlog(L_ERR, "SecurID passcode is NULL for %s user",username); + return RC_SECURID_AUTH_FAILURE; + } + + *replyMsgBuffer = '\0'; + + pSecurid_session = securid_sessionlist_find(inst,request); + if (pSecurid_session == NULL) { + /* securid session not found */ + SDI_HANDLE sdiHandle = SDI_HANDLE_NONE; + + acmRet = SD_Init(&sdiHandle); + if (acmRet != ACM_OK) { + radlog(L_ERR, "Cannot communicate with the ACE/Server"); + return -1; + } + + acmRet = SD_Lock(sdiHandle, (SD_CHAR*)username); + if (acmRet != ACM_OK) { + radlog(L_ERR,"SecurID: Access denied. Name [%s] lock failed.",username); + return -2; + } + + acmRet = SD_Check(sdiHandle, (SD_CHAR*) passcode, + (SD_CHAR*) username); + switch (acmRet) { + case ACM_OK: + /* we are in now */ + RDEBUG("SecurID authentication successful for %s.", + username); + SD_Close(sdiHandle); + + return RC_SECURID_AUTH_SUCCESS; + + case ACM_ACCESS_DENIED: + /* not this time */ + RDEBUG("SecurID Access denied for %s", username); + SD_Close(sdiHandle); + return RC_SECURID_AUTH_ACCESS_DENIED_FAILURE; + + case ACM_INVALID_SERVER: + radlog(L_ERR,"SecurID: Invalid ACE server."); + return RC_SECURID_AUTH_INVALID_SERVER_FAILURE; + + case ACM_NEW_PIN_REQUIRED: + RDEBUG2("SeecurID new pin required for %s", + username); + + /* create a new session */ + pSecurid_session = securid_session_alloc(); + pSecurid_session->sdiHandle = sdiHandle; /* save ACE handle for future use */ + pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE; + pSecurid_session->identity = strdup(username); + + /* Get PIN requirements */ + acmRet = AceGetPinParams(sdiHandle, &pinParams); + + /* If a system-generated PIN is required */ + if (pinParams.Selectable == CANNOT_CHOOSE_PIN) { + /* Prompt user to accept a system generated PIN */ + snprintf(replyMsgBuffer, replyMsgBufferSize, + "\r\nAre you prepared to accept a new system-generated PIN [y/n]?"); + pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_ACCEPT_STATE; + + } else if (pinParams.Selectable == USER_SELECTABLE) { //may be returned by AM 6.x servers. + snprintf(replyMsgBuffer, replyMsgBufferSize, + "\r\nPress 'y' to generate a new PIN\r\nOR\r\n'n'to enter a new PIN yourself [y/n]"); + pSecurid_session->securidSessionState = NEW_PIN_USER_SELECT_STATE; + + } else { + if (pinParams.Alphanumeric) { + strcpy(format, "alphanumeric characters"); + } else { + strcpy(format, "digits"); + } + snprintf(replyMsgBuffer, replyMsgBufferSize, + " \r\n Enter your new PIN of %d to %d %s,\r\n or\r\n to cancel the New PIN procedure:", + pinParams.Min, pinParams.Max, format); + } + + /* insert new session in the session list */ + securid_sessionlist_add(inst,request,pSecurid_session); + + return RC_SECURID_AUTH_CHALLENGE; + + case ACM_NEXT_CODE_REQUIRED: + RDEBUG2("Next securid token code required for %s", + username); + + /* create a new session */ + pSecurid_session = securid_session_alloc(); + pSecurid_session->sdiHandle = sdiHandle; + pSecurid_session->securidSessionState = NEXT_CODE_REQUIRED_STATE; + pSecurid_session->identity = strdup(username); + + /* insert new session in the session list */ + securid_sessionlist_add(inst,request,pSecurid_session); + + strlcpy(replyMsgBuffer, "\r\nPlease Enter the Next Code from Your Token:", replyMsgBufferSize); + return RC_SECURID_AUTH_CHALLENGE; + default: + radlog(L_ERR,"SecurID: Unexpected error from ACE/Agent API acmRet=%d",acmRet); + return RC_SECURID_AUTH_FAILURE; + + + } + } else { + /* existing session found */ + RDEBUG("Continuing previous session found for user [%s]",username); + + /* continue previous session */ + switch (pSecurid_session->securidSessionState) { + case NEXT_CODE_REQUIRED_STATE: + DEBUG2("Securid NEXT_CODE_REQUIRED_STATE: User [%s]",username); + /* next token code mode */ + + acmRet = SD_Next(pSecurid_session->sdiHandle, (SD_CHAR*)passcode); + if (acmRet == ACM_OK) { + radlog(L_INFO,"Next SecurID token accepted for [%s].",pSecurid_session->identity); + rc = RC_SECURID_AUTH_SUCCESS; + + } else { + radlog(L_INFO,"SecurID: Next token rejected for [%s].",pSecurid_session->identity); + rc = RC_SECURID_AUTH_FAILURE; + } + + /* deallocate session */ + securid_session_free(inst,request,pSecurid_session); + return rc; + + case NEW_PIN_REQUIRED_STATE: + RDEBUG2("SecurID NEW_PIN_REQUIRED_STATE for %s", + username); + + /* save the previous pin */ + if (pSecurid_session->pin) { + free(pSecurid_session->pin); + pSecurid_session->pin = NULL; + } + pSecurid_session->pin = strdup(passcode); + + strlcpy(replyMsgBuffer,"\r\n Please re-enter new PIN:", replyMsgBufferSize); + + /* set next state */ + pSecurid_session->securidSessionState = NEW_PIN_USER_CONFIRM_STATE; + + /* insert the updated session in the session list */ + securid_sessionlist_add(inst,request,pSecurid_session); + return RC_SECURID_AUTH_CHALLENGE; + + case NEW_PIN_USER_CONFIRM_STATE: + RDEBUG2("SecurID NEW_PIN_USER_CONFIRM_STATE: User [%s]",username); + /* compare previous pin and current pin */ + if (!pSecurid_session->pin || strcmp(pSecurid_session->pin,passcode)) { + RDEBUG2("Pin confirmation failed. Pins do not match [%s] and [%s]", + SAFE_STR(pSecurid_session->pin), + passcode); + /* pins do not match */ + + /* challenge the user again */ + AceGetPinParams(pSecurid_session->sdiHandle, &pinParams); + if (pinParams.Alphanumeric) { + strcpy(format, "alphanumeric characters"); + } else { + strcpy(format, "digits"); + } + snprintf(replyMsgBuffer, replyMsgBufferSize, + " \r\n Pins do not match--Please try again.\r\n Enter your new PIN of %d to %d %s,\r\n or\r\n to cancel the New PIN procedure:", + pinParams.Min, pinParams.Max, format); + + pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE; + + /* insert the updated session in the session list */ + securid_sessionlist_add(inst,request,pSecurid_session); + rc = RC_SECURID_AUTH_CHALLENGE; + + } else { + /* pins match */ + RDEBUG2("Pin confirmation succeeded. Pins match"); + acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)passcode); + if (acmRet == ACM_NEW_PIN_ACCEPTED) { + RDEBUG("New SecurID pin accepted for %s.",pSecurid_session->identity); + + pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE; + + /* insert the updated session in the session list */ + securid_sessionlist_add(inst,request,pSecurid_session); + + rc = RC_SECURID_AUTH_CHALLENGE; + strlcpy(replyMsgBuffer," \r\n\r\nWait for the code on your card to change, then enter new PIN and TokenCode\r\n\r\nEnter PASSCODE:", replyMsgBufferSize); + } else { + RDEBUG("SecurID: New SecurID pin rejected for %s.",pSecurid_session->identity); + SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); /* cancel PIN */ + + + rc = RC_SECURID_AUTH_FAILURE; + + /* deallocate session */ + securid_session_free(inst, request, + pSecurid_session); + } + } + return rc; + case NEW_PIN_AUTH_VALIDATE_STATE: + acmRet = SD_Check(pSecurid_session->sdiHandle, (SD_CHAR*)passcode, (SD_CHAR*)username); + if (acmRet == ACM_OK) { + RDEBUG("New SecurID passcode accepted for %s.", + pSecurid_session->identity); + rc = RC_SECURID_AUTH_SUCCESS; + + } else { + radlog(L_INFO,"SecurID: New passcode rejected for [%s].",pSecurid_session->identity); + rc = RC_SECURID_AUTH_FAILURE; + } + + /* deallocate session */ + securid_session_free(inst,request,pSecurid_session); + + return rc; + case NEW_PIN_SYSTEM_ACCEPT_STATE: + if (!strcmp(passcode, "y")) { + AceGetSystemPin(pSecurid_session->sdiHandle, newPin); + + /* Save the PIN for the next session + * continuation */ + if (pSecurid_session->pin) { + free(pSecurid_session->pin); + pSecurid_session->pin = NULL; + } + pSecurid_session->pin = strdup(newPin); + + snprintf(replyMsgBuffer, replyMsgBufferSize, + "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?", + newPin); + pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE; + + /* insert the updated session in the + * session list */ + securid_sessionlist_add(inst, request, + pSecurid_session); + + rc = RC_SECURID_AUTH_CHALLENGE; + + } else { + SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); //Cancel new PIN + + /* deallocate session */ + securid_session_free(inst, request, + pSecurid_session); + + rc = RC_SECURID_AUTH_FAILURE; + } + + return rc; + + case NEW_PIN_SYSTEM_CONFIRM_STATE: + acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)pSecurid_session->pin); + if (acmRet == ACM_NEW_PIN_ACCEPTED) { + strlcpy(replyMsgBuffer," \r\n\r\nPin Accepted. Wait for the code on your card to change, then enter new PIN and TokenCode\r\n\r\nEnter PASSCODE:",replyMsgBufferSize); + pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE; + /* insert the updated session in the session list */ + securid_sessionlist_add(inst,request,pSecurid_session); + rc = RC_SECURID_AUTH_CHALLENGE; + + } else { + SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); //Cancel new PIN + strlcpy(replyMsgBuffer," \r\n\r\nPin Rejected. Wait for the code on your card to change, then try again.\r\n\r\nEnter PASSCODE:",replyMsgBufferSize); + /* deallocate session */ + securid_session_free(inst, request, + pSecurid_session); + rc = RC_SECURID_AUTH_FAILURE; + } + + return rc; + + /* USER_SELECTABLE state should be implemented to preserve compatibility with AM 6.x servers, which can return this state */ + case NEW_PIN_USER_SELECT_STATE: + if (!strcmp(passcode, "y")) { + /* User has opted for a system-generated PIN */ + AceGetSystemPin(pSecurid_session->sdiHandle, newPin); + snprintf(replyMsgBuffer, replyMsgBufferSize, + "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?", + newPin); + pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE; + + /* insert the updated session in the session list */ + securid_sessionlist_add(inst, request, + pSecurid_session); + rc = RC_SECURID_AUTH_CHALLENGE; + + } else { + /* User has opted for a user-defined PIN */ + AceGetPinParams(pSecurid_session->sdiHandle, + &pinParams); + if (pinParams.Alphanumeric) { + strcpy(format, "alphanumeric characters"); + } else { + strcpy(format, "digits"); + } + + snprintf(replyMsgBuffer, replyMsgBufferSize, + " \r\n Enter your new PIN of %d to %d %s,\r\n or\r\n to cancel the New PIN procedure:", + pinParams.Min, pinParams.Max, format); + pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE; + + /* insert the updated session in the session list */ + securid_sessionlist_add(inst, request, + pSecurid_session); + rc = RC_SECURID_AUTH_CHALLENGE; + } + + return rc; + + default: + radlog(L_ERR|L_CONS, "rlm_securid: Invalid session state %d for user [%s]", + pSecurid_session->securidSessionState, + username); + break; + } + } + + return 0; + +} + +/******************************************/ +static int securid_detach(void *instance) +{ + rlm_securid_t *inst = (rlm_securid_t *) instance; + + /* delete session tree */ + if (inst->session_tree) { + rbtree_free(inst->session_tree); + inst->session_tree = NULL; + } + + pthread_mutex_destroy(&(inst->session_mutex)); + + free(inst); + return 0; +} + + +static int securid_instantiate(CONF_SECTION *conf, void **instance) +{ + rlm_securid_t *inst; + + /* Set up a storage area for instance data */ + inst = rad_malloc(sizeof(*inst)); + if (!inst) return -1; + memset(inst, 0, sizeof(*inst)); + + /* If the configuration parameters can't be parsed, then fail. */ + if (cf_section_parse(conf, inst, module_config) < 0) { + radlog(L_ERR|L_CONS, "rlm_securid: Unable to parse configuration section."); + securid_detach(inst); + return -1; + } + + /* + * Lookup sessions in the tree. We don't free them in + * the tree, as that's taken care of elsewhere... + */ + inst->session_tree = rbtree_create(securid_session_cmp, NULL, 0); + if (!inst->session_tree) { + radlog(L_ERR|L_CONS, "rlm_securid: Cannot initialize session tree."); + securid_detach(inst); + return -1; + } + + pthread_mutex_init(&(inst->session_mutex), NULL); + + *instance = inst; + return 0; +} + + +/* + * Authenticate the user via one of any well-known password. + */ +static int securid_authenticate(void *instance, REQUEST *request) +{ + int rcode; + rlm_securid_t *inst = instance; + VALUE_PAIR *module_fmsg_vp; + VALUE_PAIR *vp; + char buffer[MAX_STRING_LEN]=""; + const char *username=NULL, *password=NULL; + char module_fmsg[MAX_STRING_LEN]=""; + + /* + * We can only authenticate user requests which HAVE + * a User-Name attribute. + */ + if (!request->username) { + radlog(L_AUTH, "rlm_securid: Attribute \"User-Name\" is required for authentication."); + return RLM_MODULE_INVALID; + } + + if (!request->password) { + radlog_request(L_AUTH, 0, request, "Attribute \"Password\" is required for authentication."); + return RLM_MODULE_INVALID; + } + + /* + * Clear-text passwords are the only ones we support. + */ + if (request->password->attribute != PW_USER_PASSWORD) { + radlog_request(L_AUTH, 0, request, "Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name); + return RLM_MODULE_INVALID; + } + + /* + * The user MUST supply a non-zero-length password. + */ + if (request->password->length == 0) { + snprintf(module_fmsg,sizeof(module_fmsg),"rlm_securid: empty password supplied"); + module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ); + pairadd(&request->packet->vps, module_fmsg_vp); + return RLM_MODULE_INVALID; + } + + /* + * shortcuts + */ + username = request->username->vp_strvalue; + password = request->password->vp_strvalue; + + RDEBUG("User [%s] login attempt with password [%s]", + username, password); + + rcode = securidAuth(inst, request, username, password, + buffer, sizeof(buffer)); + + switch (rcode) { + case RC_SECURID_AUTH_SUCCESS: + rcode = RLM_MODULE_OK; + break; + + case RC_SECURID_AUTH_CHALLENGE: + /* reply with Access-challenge message code (11) */ + + /* Generate Prompt attribute */ + vp = paircreate(PW_PROMPT, 0, PW_TYPE_INTEGER); + + rad_assert(vp != NULL); + vp->vp_integer = 0; /* no echo */ + pairadd(&request->reply->vps, vp); + + /* Mark the packet as a Acceess-Challenge Packet */ + request->reply->code = PW_ACCESS_CHALLENGE; + RDEBUG("Sending Access-Challenge."); + rcode = RLM_MODULE_HANDLED; + break; + + case RC_SECURID_AUTH_FAILURE: + case RC_SECURID_AUTH_ACCESS_DENIED_FAILURE: + case RC_SECURID_AUTH_INVALID_SERVER_FAILURE: + default: + rcode = RLM_MODULE_REJECT; + break; + } + + if (*buffer) { + /* Generate Reply-Message attribute with reply message data */ + vp = pairmake("Reply-Message", buffer, T_OP_EQ); + + /* make sure message ends with '\0' */ + if (vp->length < (int) sizeof(vp->vp_strvalue)) { + vp->vp_strvalue[vp->length] = '\0'; + vp->length++; + } + pairadd(&request->reply->vps,vp); + } + return rcode; +} + + +/* + * The module name should be the only globally exported symbol. + * That is, everything else should be 'static'. + * + * If the module needs to temporarily modify it's instantiation + * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE. + * The server will then take care of ensuring that the module + * is single-threaded. + */ +module_t rlm_securid = { + RLM_MODULE_INIT, + "securid", + RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE, /* type */ + securid_instantiate, /* instantiation */ + securid_detach, /* detach */ + { + securid_authenticate, /* authentication */ + NULL, /* authorization */ + NULL, /* preaccounting */ + NULL, /* accounting */ + NULL, /* checksimul */ + NULL, /* pre-proxy */ + NULL, /* post-proxy */ + NULL /* post-auth */ + }, +}; diff --git a/src/modules/rlm_securid/rlm_securid.h b/src/modules/rlm_securid/rlm_securid.h new file mode 100644 index 0000000..475bbae --- /dev/null +++ b/src/modules/rlm_securid/rlm_securid.h @@ -0,0 +1,90 @@ +#ifndef _RLM_SECURID_H +#define _RLM_SECURID_H + +#include +#include +#include +#include + +#include "acexport.h" + +#define SAFE_STR(s) s==NULL?"EMPTY":s + +typedef enum { + INITIAL_STATE = 0, + NEXT_CODE_REQUIRED_STATE = 100, + NEW_PIN_REQUIRED_STATE = 200, + NEW_PIN_USER_CONFIRM_STATE = 201, + NEW_PIN_AUTH_VALIDATE_STATE = 202 +} +SECURID_SESSION_STATE; + +/* + * SECURID_SESSION is used to identify existing securID sessions + * to continue Next-Token code and New-Pin conversations with a client + * + * next = pointer to next + * state = state attribute from the reply we sent + * state_len = length of data in the state attribute. + * src_ipaddr = client which sent us the RADIUS request containing + * this SecurID conversation. + * timestamp = timestamp when this handler was last used. + * trips = number of trips + * identity = Identity of the user + * request = RADIUS request data structure + */ + +#define SECURID_STATE_LEN 32 +typedef struct _securid_session_t { + struct _securid_session_t *prev, *next; + SDI_HANDLE sdiHandle; + SECURID_SESSION_STATE securidSessionState; + + uint8_t state[SECURID_STATE_LEN]; + + fr_ipaddr_t src_ipaddr; + time_t timestamp; + unsigned int session_id; + int trips; + + char *pin; /* previous pin if user entered it during NEW-PIN mode process */ + char *identity; /* save user's identity name for future use */ + +} SECURID_SESSION; + + +/* + * Define a structure for our module configuration. + * + * These variables do not need to be in a structure, but it's + * a lot cleaner to do so, and a pointer to the structure can + * be used as the instance handle. + * sessions = remembered sessions, in a tree for speed. + * mutex = ensure only one thread is updating the sessions list + */ +typedef struct rlm_securid_t { + pthread_mutex_t session_mutex; + rbtree_t* session_tree; + SECURID_SESSION *session_head, *session_tail; + + unsigned int last_session_id; + + /* + * Configuration items. + */ + int timer_limit; + int max_sessions; + int max_trips_per_session; +} rlm_securid_t; + +/* Memory Management */ +SECURID_SESSION* securid_session_alloc(void); +void securid_session_free(rlm_securid_t *inst, REQUEST *request,SECURID_SESSION *session); + +void securid_sessionlist_free(rlm_securid_t *inst,REQUEST *request); + +int securid_sessionlist_add(rlm_securid_t *inst, REQUEST *request, SECURID_SESSION *session); +SECURID_SESSION* securid_sessionlist_find(rlm_securid_t *inst, REQUEST *request); + + +#endif diff --git a/src/modules/rlm_securid/securid b/src/modules/rlm_securid/securid new file mode 100644 index 0000000..2e821ec --- /dev/null +++ b/src/modules/rlm_securid/securid @@ -0,0 +1,20 @@ +# +# This is the configuration for the "securid" module. +# It should be copied to raddb/modules/ +# + +securid { + # How long the module waits before expiring a session. + # + timer_expire = 600 + + # The sessions are tracked internally. This configuration + # item limits the total number of allowed sessions. + # + max_sessions = 2048 + + # How many round trips are allowed before the authentication + # is forced to fail/ + # + max_round_trips = 6 +}