diff options
Diffstat (limited to 'src/modules/rlm_securid/rlm_securid.c')
-rw-r--r-- | src/modules/rlm_securid/rlm_securid.c | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/src/modules/rlm_securid/rlm_securid.c b/src/modules/rlm_securid/rlm_securid.c new file mode 100644 index 0000000..ddb9bba --- /dev/null +++ b/src/modules/rlm_securid/rlm_securid.c @@ -0,0 +1,563 @@ +/* + * This program is 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 + */ + +/** + * $Id$ + * @file rlm_securid.c + * @brief Supports auth against SecurID servers using OTP h/w tokens. + * + * Supports "next-token code" and "new-pin" modes. + * + * @copyright 2012 The FreeRADIUS server project + * @copyright 2012 Alan DeKok <aland@networkradius.com> + */ +#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", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_securid_t, timer_limit), "600" }, + { "max_sessions", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_securid_t, max_sessions), "2048" }, + { "max_trips_per_session", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_securid_t, max_trips_per_session), NULL }, + { "max_round_trips", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_securid_t, max_trips_per_session), "6" }, + CONF_PARSER_TERMINATOR +}; + + +static SD_CHAR empty_pin[] = ""; + +/* comparison function to find session in the tree */ +static int securid_session_cmp(void const *a, void const *b) +{ + int rcode; + SECURID_SESSION const *one = a; + SECURID_SESSION const *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, + char const *username, + char const *passcode, + char *replyMsgBuffer, size_t replyMsgBufferSize) +{ + rlm_securid_t *inst = (rlm_securid_t *) instance; + int acm_ret; + SD_PIN pin_params; + char new_pin[10]; + char format[30]; + SECURID_SESSION *securid_session = NULL; + int rc = -1; + + SD_CHAR *securid_user, *securid_pass; + + if (!username) { + ERROR("SecurID username is NULL"); + return RC_SECURID_AUTH_FAILURE; + } + + if (!passcode) { + ERROR("SecurID passcode is NULL for %s user", username); + return RC_SECURID_AUTH_FAILURE; + } + + memcpy(&securid_user, &username, sizeof(securid_user)); + memcpy(&securid_pass, &passcode, sizeof(securid_pass)); + + *replyMsgBuffer = '\0'; + + securid_session = securid_sessionlist_find(inst, request); + if (!securid_session) { + /* securid session not found */ + SDI_HANDLE sdiHandle = SDI_HANDLE_NONE; + + acm_ret = SD_Init(&sdiHandle); + if (acm_ret != ACM_OK) { + ERROR("Cannot communicate with the ACE/Server"); + return -1; + } + + acm_ret = SD_Lock(sdiHandle, securid_user); + if (acm_ret != ACM_OK) { + ERROR("SecurID: Access denied. Name [%s] lock failed", username); + return -2; + } + + acm_ret = SD_Check(sdiHandle, securid_pass, securid_user); + switch (acm_ret) { + 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: + ERROR("SecurID: Invalid ACE server"); + return RC_SECURID_AUTH_INVALID_SERVER_FAILURE; + + case ACM_NEW_PIN_REQUIRED: + RDEBUG2("SecurID new pin required for %s", username); + + /* create a new session */ + securid_session = securid_session_alloc(); + securid_session->sdiHandle = sdiHandle; /* save ACE handle for future use */ + securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE; + securid_session->identity = strdup(username); + + /* Get PIN requirements */ + (void) AceGetPinParams(sdiHandle, &pin_params); + + /* If a system-generated PIN is required */ + if (pin_params.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]?"); + securid_session->securidSessionState = NEW_PIN_SYSTEM_ACCEPT_STATE; + + } else if (pin_params.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]"); + securid_session->securidSessionState = NEW_PIN_USER_SELECT_STATE; + + } else { + if (pin_params.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:", + pin_params.Min, pin_params.Max, format); + } + + /* insert new session in the session list */ + securid_sessionlist_add(inst, request, securid_session); + + return RC_SECURID_AUTH_CHALLENGE; + + case ACM_NEXT_CODE_REQUIRED: + RDEBUG2("Next securid token code required for %s", + username); + + /* create a new session */ + securid_session = securid_session_alloc(); + securid_session->sdiHandle = sdiHandle; + securid_session->securidSessionState = NEXT_CODE_REQUIRED_STATE; + securid_session->identity = strdup(username); + + /* insert new session in the session list */ + securid_sessionlist_add(inst, request, securid_session); + + strlcpy(replyMsgBuffer, "\r\nPlease Enter the Next Code from Your Token:", replyMsgBufferSize); + return RC_SECURID_AUTH_CHALLENGE; + + default: + ERROR("SecurID: Unexpected error from ACE/Agent API acm_ret=%d", acm_ret); + securid_session_free(inst, request, securid_session); + return RC_SECURID_AUTH_FAILURE; + + + } + } else { + /* existing session found */ + RDEBUG("Continuing previous session found for user [%s]", username); + + /* continue previous session */ + switch (securid_session->securidSessionState) { + case NEXT_CODE_REQUIRED_STATE: + DEBUG2("Securid NEXT_CODE_REQUIRED_STATE: User [%s]", username); + /* next token code mode */ + + acm_ret = SD_Next(securid_session->sdiHandle, securid_pass); + if (acm_ret == ACM_OK) { + INFO("Next SecurID token accepted for [%s].", securid_session->identity); + rc = RC_SECURID_AUTH_SUCCESS; + + } else { + INFO("SecurID: Next token rejected for [%s].", securid_session->identity); + rc = RC_SECURID_AUTH_FAILURE; + } + + /* deallocate session */ + securid_session_free(inst, request, securid_session); + return rc; + + case NEW_PIN_REQUIRED_STATE: + RDEBUG2("SecurID NEW_PIN_REQUIRED_STATE for %s", + username); + + /* save the previous pin */ + if (securid_session->pin) { + free(securid_session->pin); + securid_session->pin = NULL; + } + securid_session->pin = strdup(passcode); + + strlcpy(replyMsgBuffer, "\r\n Please re-enter new PIN:", replyMsgBufferSize); + + /* set next state */ + securid_session->securidSessionState = NEW_PIN_USER_CONFIRM_STATE; + + /* insert the updated session in the session list */ + securid_sessionlist_add(inst, request, securid_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 (!securid_session->pin || strcmp(securid_session->pin, passcode)) { + RDEBUG2("Pin confirmation failed. Pins do not match [%s] and [%s]", + SAFE_STR(securid_session->pin), securid_pass); + /* pins do not match */ + + /* challenge the user again */ + AceGetPinParams(securid_session->sdiHandle, &pin_params); + if (pin_params.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:", + pin_params.Min, pin_params.Max, format); + + securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE; + + /* insert the updated session in the session list */ + securid_sessionlist_add(inst, request, securid_session); + rc = RC_SECURID_AUTH_CHALLENGE; + + } else { + /* pins match */ + RDEBUG2("Pin confirmation succeeded. Pins match"); + acm_ret = SD_Pin(securid_session->sdiHandle, securid_pass); + if (acm_ret == ACM_NEW_PIN_ACCEPTED) { + RDEBUG("New SecurID pin accepted for %s.", securid_session->identity); + + securid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE; + + /* insert the updated session in the session list */ + securid_sessionlist_add(inst, request, securid_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.", securid_session->identity); + SD_Pin(securid_session->sdiHandle, &empty_pin[0]); /* cancel PIN */ + + + rc = RC_SECURID_AUTH_FAILURE; + + /* deallocate session */ + securid_session_free(inst, request, securid_session); + } + } + return rc; + case NEW_PIN_AUTH_VALIDATE_STATE: + acm_ret = SD_Check(securid_session->sdiHandle, securid_pass, securid_user); + if (acm_ret == ACM_OK) { + RDEBUG("New SecurID passcode accepted for %s.", + securid_session->identity); + rc = RC_SECURID_AUTH_SUCCESS; + + } else { + INFO("SecurID: New passcode rejected for [%s].", securid_session->identity); + rc = RC_SECURID_AUTH_FAILURE; + } + + /* deallocate session */ + securid_session_free(inst, request, securid_session); + + return rc; + case NEW_PIN_SYSTEM_ACCEPT_STATE: + if (!strcmp(passcode, "y")) { + AceGetSystemPin(securid_session->sdiHandle, new_pin); + + /* Save the PIN for the next session + * continuation */ + if (securid_session->pin) { + free(securid_session->pin); + securid_session->pin = NULL; + } + securid_session->pin = strdup(new_pin); + + snprintf(replyMsgBuffer, replyMsgBufferSize, + "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?", + new_pin); + securid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE; + + /* insert the updated session in the + * session list */ + securid_sessionlist_add(inst, request, securid_session); + + rc = RC_SECURID_AUTH_CHALLENGE; + + } else { + SD_Pin(securid_session->sdiHandle, &empty_pin[0]); //Cancel new PIN + + /* deallocate session */ + securid_session_free(inst, request, + securid_session); + + rc = RC_SECURID_AUTH_FAILURE; + } + + return rc; + + case NEW_PIN_SYSTEM_CONFIRM_STATE: + acm_ret = SD_Pin(securid_session->sdiHandle, (SD_CHAR*)securid_session->pin); + if (acm_ret == 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); + securid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE; + /* insert the updated session in the session list */ + securid_sessionlist_add(inst, request, securid_session); + rc = RC_SECURID_AUTH_CHALLENGE; + + } else { + SD_Pin(securid_session->sdiHandle, &empty_pin[0]); //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, + securid_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(securid_session->sdiHandle, new_pin); + snprintf(replyMsgBuffer, replyMsgBufferSize, + "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?", + new_pin); + securid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE; + + /* insert the updated session in the session list */ + securid_sessionlist_add(inst, request, + securid_session); + rc = RC_SECURID_AUTH_CHALLENGE; + + } else { + /* User has opted for a user-defined PIN */ + AceGetPinParams(securid_session->sdiHandle, + &pin_params); + if (pin_params.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:", + pin_params.Min, pin_params.Max, format); + securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE; + + /* insert the updated session in the session list */ + securid_sessionlist_add(inst, request, + securid_session); + rc = RC_SECURID_AUTH_CHALLENGE; + } + + return rc; + + default: + ERROR("rlm_securid: Invalid session state %d for user [%s]", + securid_session->securidSessionState, + username); + break; + } + } + + return 0; + +} + +/******************************************/ +static int mod_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)); + + return 0; +} + + +static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance) +{ + rlm_securid_t *inst = instance; + + /* + * 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(NULL, securid_session_cmp, NULL, 0); + if (!inst->session_tree) { + ERROR("rlm_securid: Cannot initialize session tree"); + return -1; + } + + pthread_mutex_init(&(inst->session_mutex), NULL); + return 0; +} + + +/* + * Authenticate the user via one of any well-known password. + */ +static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request) +{ + int rcode; + rlm_securid_t *inst = instance; + char buffer[MAX_STRING_LEN]=""; + char const *username=NULL, *password=NULL; + VALUE_PAIR *vp; + + /* + * We can only authenticate user requests which HAVE + * a User-Name attribute. + */ + if (!request->username) { + AUTH("rlm_securid: Attribute \"User-Name\" is required for authentication"); + return RLM_MODULE_INVALID; + } + + if (!request->password) { + RAUTH("Attribute \"Password\" is required for authentication"); + return RLM_MODULE_INVALID; + } + + /* + * Clear-text passwords are the only ones we support. + */ + if (request->password->da->attr != PW_USER_PASSWORD) { + RAUTH("Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->da->name); + return RLM_MODULE_INVALID; + } + + /* + * The user MUST supply a non-zero-length password. + */ + if (request->password->vp_length == 0) { + REDEBUG("Password should not be empty"); + return RLM_MODULE_INVALID; + } + + /* + * shortcuts + */ + username = request->username->vp_strvalue; + password = request->password->vp_strvalue; + + if (RDEBUG_ENABLED3) { + RDEBUG3("Login attempt with password \"%s\"", password); + } else { + RDEBUG("Login attempt with 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 = fr_pair_afrom_num(request->reply, PW_PROMPT, 0); + + rad_assert(vp != NULL); + vp->vp_integer = 0; /* no echo */ + fr_pair_add(&request->reply->vps, vp); + + /* Mark the packet as a Acceess-Challenge Packet */ + request->reply->code = PW_CODE_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) pair_make_reply("Reply-Message", buffer, T_OP_EQ); + + 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. + */ +extern module_t rlm_securid; +module_t rlm_securid = { + .magic = RLM_MODULE_INIT, + .name = "securid", + .type = RLM_TYPE_HUP_SAFE, + .inst_size = sizeof(rlm_securid_t), + .config = module_config, + .instantiate = mod_instantiate, + .detach = mod_detach, + .methods = { + [MOD_AUTHENTICATE] = mod_authenticate + }, +}; |