Manually pull rlm_securid from the v2.1.x branch
authorAlan T. DeKok <aland@freeradius.org>
Tue, 22 Nov 2011 09:46:16 +0000 (10:46 +0100)
committerAlan T. DeKok <aland@freeradius.org>
Tue, 22 Nov 2011 09:46:16 +0000 (10:46 +0100)
With minor changes for the 3.0 API

src/modules/rlm_securid/Makefile [new file with mode: 0644]
src/modules/rlm_securid/README [new file with mode: 0644]
src/modules/rlm_securid/mem.c [new file with mode: 0644]
src/modules/rlm_securid/rlm_securid.c [new file with mode: 0644]
src/modules/rlm_securid/rlm_securid.h [new file with mode: 0644]
src/modules/rlm_securid/securid [new file with mode: 0644]

diff --git a/src/modules/rlm_securid/Makefile b/src/modules/rlm_securid/Makefile
new file mode 100644 (file)
index 0000000..9fbc74e
--- /dev/null
@@ -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 (file)
index 0000000..ba48346
--- /dev/null
@@ -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 (file)
index 0000000..df40527
--- /dev/null
@@ -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 <aland@networkradius.com>
+ */
+
+#include <freeradius-devel/ident.h>
+#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(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 (file)
index 0000000..afe61d6
--- /dev/null
@@ -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 <aland@networkradius.com>
+ */
+
+#include <freeradius-devel/ident.h>
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <ctype.h>
+
+#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   <Ctrl-D> 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   <Ctrl-D> 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   <Ctrl-D> 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 (file)
index 0000000..475bbae
--- /dev/null
@@ -0,0 +1,90 @@
+#ifndef _RLM_SECURID_H
+#define _RLM_SECURID_H
+
+#include <freeradius-devel/ident.h>
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/rad_assert.h>
+
+#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 (file)
index 0000000..2e821ec
--- /dev/null
@@ -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
+}