#include <freeradius-devel/ident.h>
#include <freeradius-devel/radiusd.h>
#include <freeradius-devel/modules.h>
#include <ctype.h>
#include "rlm_securid.h"
#define PW_ATTRIBUTE_NAME_PROMPT 76
#define PW_ATTRIBUTE_VALUE_NO_ECHO 0
#define PW_ATTRIBUTE_VALUE_ECHO 1
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 }
};
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;
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;
}
memset(replyMsgBuffer,replyMsgBufferSize,0);
pSecurid_session = securid_sessionlist_find(inst,request);
if (pSecurid_session == NULL) {
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:
radlog(L_INFO,"SecurID authentication successful for [%s].",username);
SD_Close(sdiHandle);
return RC_SECURID_AUTH_SUCCESS;
case ACM_ACCESS_DENIED:
radlog(L_AUTH, "rlm_securid: [%s] Access denied",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:
DEBUG2("New securid pin required for user [%s]",username);
pSecurid_session = securid_session_alloc();
pSecurid_session->sdiHandle = sdiHandle;
pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
pSecurid_session->identity = strdup(username);
securid_sessionlist_add(inst,request,pSecurid_session);
strncpy(replyMsgBuffer," \r\n Enter your new PIN, containing 4 to 6 digits,\r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",replyMsgBufferSize-1);
return RC_SECURID_AUTH_CHALLENGE;
case ACM_NEXT_CODE_REQUIRED:
DEBUG2("Next securid token code required for user [%s]",username);
pSecurid_session = securid_session_alloc();
pSecurid_session->sdiHandle = sdiHandle;
pSecurid_session->securidSessionState = NEXT_CODE_REQUIRED_STATE;
pSecurid_session->identity = strdup(username);
securid_sessionlist_add(inst,request,pSecurid_session);
strncpy(replyMsgBuffer,"\r\nPlease Enter the Next Code from Your Token:",replyMsgBufferSize-1);
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 {
RDEBUG("Continuing previous session found for user [%s]",username);
switch (pSecurid_session->securidSessionState) {
case NEXT_CODE_REQUIRED_STATE:
DEBUG2("Securid NEXT_CODE_REQUIRED_STATE: User [%s]",username);
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;
}
securid_session_free(inst,request,pSecurid_session);
return rc;
case NEW_PIN_REQUIRED_STATE:
DEBUG2("SecurID NEW_PIN_REQUIRED_STATE: User [%s]",username);
if (pSecurid_session->pin) {
free(pSecurid_session->pin);
pSecurid_session->pin = NULL;
}
pSecurid_session->pin = strdup(passcode);
strncpy(replyMsgBuffer,"\r\n Please re-enter new PIN:",replyMsgBufferSize-1);
pSecurid_session->securidSessionState = NEW_PIN_USER_CONFIRM_STATE;
securid_sessionlist_add(inst,request,pSecurid_session);
return RC_SECURID_AUTH_CHALLENGE;
case NEW_PIN_USER_CONFIRM_STATE:
DEBUG2("SecurID NEW_PIN_USER_CONFIRM_STATE: User [%s]",username);
if (!pSecurid_session->pin || strcmp(pSecurid_session->pin,passcode)) {
DEBUG2("Pin confirmation failed. Pins do not match [%s] and [%s]",
SAFE_STR(pSecurid_session->pin),
passcode);
strncpy(replyMsgBuffer," \r\nPINs do not match. Please try again.\r\n\r\n Enter your new PIN, containing 4 to 6 digits,\r\n or\r\n <Ctrl-D> to cancel the New PIN procedure: ",replyMsgBufferSize-1);
pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
securid_sessionlist_add(inst,request,pSecurid_session);
rc = RC_SECURID_AUTH_CHALLENGE;
} else {
DEBUG2("Pin confirmation succeeded. Pins match");
acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)passcode);
if (acmRet == ACM_NEW_PIN_ACCEPTED)
{
radlog(L_INFO,"New SecurID pin accepted for [%s].",pSecurid_session->identity);
pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
securid_sessionlist_add(inst,request,pSecurid_session);
rc = RC_SECURID_AUTH_CHALLENGE;
strncpy(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-1);
} else {
radlog(L_INFO,"SecurID: New SecurID pin rejected for [%s].",pSecurid_session->identity);
SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)"");
rc = RC_SECURID_AUTH_FAILURE;
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) {
radlog(L_INFO,"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;
}
securid_session_free(inst,request,pSecurid_session);
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;
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;
inst = rad_malloc(sizeof(*inst));
if (!inst) return -1;
memset(inst, 0, sizeof(*inst));
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;
}
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;
}
static int securid_authenticate(void *instance, REQUEST *request)
{
int rc;
int moduleRC;
rlm_securid_t *inst = instance;
VALUE_PAIR *module_fmsg_vp;
VALUE_PAIR *vp_replyPrompt=NULL;
VALUE_PAIR *vp_replyMessage=NULL;
char replyMsgBuffer[MAX_STRING_LEN]="";
const char *username=NULL, *password=NULL;
char module_fmsg[MAX_STRING_LEN]="";
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;
}
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;
}
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;
}
username = request->username->vp_strvalue;
password = request->password->vp_strvalue;
RDEBUG("User [%s] login attempt with password [%s]",username,password);
rc = securidAuth(inst,request,username,password,replyMsgBuffer,sizeof(replyMsgBuffer));
switch (rc) {
case RC_SECURID_AUTH_SUCCESS:
if (replyMsgBuffer[0] != '\0') {
vp_replyMessage = pairmake("Reply-Message", replyMsgBuffer, T_OP_EQ);
if (vp_replyMessage->length < (int) sizeof(vp_replyMessage->vp_strvalue))
{
vp_replyMessage->vp_strvalue[vp_replyMessage->length] = '\0';
vp_replyMessage->length++;
}
pairadd(&request->reply->vps,vp_replyMessage);
}
moduleRC = RLM_MODULE_OK;
break;
case RC_SECURID_AUTH_CHALLENGE:
vp_replyPrompt = paircreate(PW_ATTRIBUTE_NAME_PROMPT , PW_TYPE_INTEGER);
rad_assert(vp_replyPrompt != NULL);
vp_replyPrompt->vp_integer = PW_ATTRIBUTE_VALUE_NO_ECHO;
pairadd(&request->reply->vps,vp_replyPrompt);
vp_replyMessage = pairmake("Reply-Message", replyMsgBuffer, T_OP_EQ);
if (vp_replyMessage->length < (int) sizeof(vp_replyMessage->vp_strvalue)) {
vp_replyMessage->vp_strvalue[vp_replyMessage->length] = '\0';
vp_replyMessage->length++;
}
pairadd(&request->reply->vps,vp_replyMessage);
request->reply->code = PW_ACCESS_CHALLENGE;
RDEBUG("Sending Access-Challenge.");
moduleRC = RLM_MODULE_HANDLED;
break;
case RC_SECURID_AUTH_FAILURE:
case RC_SECURID_AUTH_ACCESS_DENIED_FAILURE:
case RC_SECURID_AUTH_INVALID_SERVER_FAILURE:
default:
if (replyMsgBuffer[0] != '\0') {
vp_replyMessage = pairmake("Reply-Message", replyMsgBuffer, T_OP_EQ);
if (vp_replyMessage->length < (int) sizeof(vp_replyMessage->vp_strvalue)) {
vp_replyMessage->vp_strvalue[vp_replyMessage->length] = '\0';
vp_replyMessage->length++;
}
pairadd(&request->reply->vps,vp_replyMessage);
}
moduleRC = RLM_MODULE_REJECT;
break;
}
return moduleRC;
}
module_t rlm_securid = {
RLM_MODULE_INIT,
"securid",
RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,
securid_instantiate,
securid_detach,
{
securid_authenticate,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL
},
};