2 * This program is is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License, version 2 if the
4 * License as published by the Free Software Foundation.
6 * This program is distributed in the hope that it will be useful,
7 * but WITHOUT ANY WARRANTY; without even the implied warranty of
8 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 * GNU General Public License for more details.
11 * You should have received a copy of the GNU General Public License
12 * along with this program; if not, write to the Free Software
13 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 * @brief Supports auth against SecurID servers using OTP h/w tokens.
21 * Supports "next-token code" and "new-pin" modes.
23 * @copyright 2012 The FreeRADIUS server project
24 * @copyright 2012 Alan DeKok <aland@networkradius.com>
26 #include <freeradius-devel/ident.h>
27 #include <freeradius-devel/radiusd.h>
28 #include <freeradius-devel/modules.h>
31 #include "rlm_securid.h"
34 RC_SECURID_AUTH_SUCCESS = 0,
35 RC_SECURID_AUTH_FAILURE = -3,
36 RC_SECURID_AUTH_ACCESS_DENIED_FAILURE = -4,
37 RC_SECURID_AUTH_INVALID_SERVER_FAILURE = -5,
38 RC_SECURID_AUTH_CHALLENGE = -17
43 static const CONF_PARSER module_config[] = {
44 { "timer_expire", PW_TYPE_INTEGER,
45 offsetof(rlm_securid_t, timer_limit), NULL, "600"},
46 { "max_sessions", PW_TYPE_INTEGER,
47 offsetof(rlm_securid_t, max_sessions), NULL, "2048"},
48 { "max_trips_per_session", PW_TYPE_INTEGER,
49 offsetof(rlm_securid_t, max_trips_per_session), NULL, NULL},
50 { "max_round_trips", PW_TYPE_INTEGER,
51 offsetof(rlm_securid_t, max_trips_per_session), NULL, "6"},
52 { NULL, -1, 0, NULL, NULL } /* end the list */
56 /* comparison function to find session in the tree */
57 static int securid_session_cmp(const void *a, const void *b)
60 const SECURID_SESSION *one = a;
61 const SECURID_SESSION *two = b;
63 rad_assert(one != NULL);
64 rad_assert(two != NULL);
66 rcode = fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr);
67 if (rcode != 0) return rcode;
69 return memcmp(one->state, two->state, sizeof(one->state));
73 static SECURID_AUTH_RC securidAuth(void *instance, REQUEST *request,
76 char* replyMsgBuffer,int replyMsgBufferSize)
78 rlm_securid_t *inst = (rlm_securid_t *) instance;
83 SECURID_SESSION *pSecurid_session=NULL;
87 radlog(L_ERR, "SecurID username is NULL");
88 return RC_SECURID_AUTH_FAILURE;
92 radlog(L_ERR, "SecurID passcode is NULL for %s user",username);
93 return RC_SECURID_AUTH_FAILURE;
96 *replyMsgBuffer = '\0';
98 pSecurid_session = securid_sessionlist_find(inst,request);
99 if (pSecurid_session == NULL) {
100 /* securid session not found */
101 SDI_HANDLE sdiHandle = SDI_HANDLE_NONE;
103 acmRet = SD_Init(&sdiHandle);
104 if (acmRet != ACM_OK) {
105 radlog(L_ERR, "Cannot communicate with the ACE/Server");
109 acmRet = SD_Lock(sdiHandle, (SD_CHAR*)username);
110 if (acmRet != ACM_OK) {
111 radlog(L_ERR,"SecurID: Access denied. Name [%s] lock failed.",username);
115 acmRet = SD_Check(sdiHandle, (SD_CHAR*) passcode,
116 (SD_CHAR*) username);
120 RDEBUG("SecurID authentication successful for %s.",
124 return RC_SECURID_AUTH_SUCCESS;
126 case ACM_ACCESS_DENIED:
128 RDEBUG("SecurID Access denied for %s", username);
130 return RC_SECURID_AUTH_ACCESS_DENIED_FAILURE;
132 case ACM_INVALID_SERVER:
133 radlog(L_ERR,"SecurID: Invalid ACE server.");
134 return RC_SECURID_AUTH_INVALID_SERVER_FAILURE;
136 case ACM_NEW_PIN_REQUIRED:
137 RDEBUG2("SeecurID new pin required for %s",
140 /* create a new session */
141 pSecurid_session = securid_session_alloc();
142 pSecurid_session->sdiHandle = sdiHandle; /* save ACE handle for future use */
143 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
144 pSecurid_session->identity = strdup(username);
146 /* Get PIN requirements */
147 acmRet = AceGetPinParams(sdiHandle, &pinParams);
149 /* If a system-generated PIN is required */
150 if (pinParams.Selectable == CANNOT_CHOOSE_PIN) {
151 /* Prompt user to accept a system generated PIN */
152 snprintf(replyMsgBuffer, replyMsgBufferSize,
153 "\r\nAre you prepared to accept a new system-generated PIN [y/n]?");
154 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_ACCEPT_STATE;
156 } else if (pinParams.Selectable == USER_SELECTABLE) { //may be returned by AM 6.x servers.
157 snprintf(replyMsgBuffer, replyMsgBufferSize,
158 "\r\nPress 'y' to generate a new PIN\r\nOR\r\n'n'to enter a new PIN yourself [y/n]");
159 pSecurid_session->securidSessionState = NEW_PIN_USER_SELECT_STATE;
162 if (pinParams.Alphanumeric) {
163 strcpy(format, "alphanumeric characters");
165 strcpy(format, "digits");
167 snprintf(replyMsgBuffer, replyMsgBufferSize,
168 " \r\n Enter your new PIN of %d to %d %s,\r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",
169 pinParams.Min, pinParams.Max, format);
172 /* insert new session in the session list */
173 securid_sessionlist_add(inst,request,pSecurid_session);
175 return RC_SECURID_AUTH_CHALLENGE;
177 case ACM_NEXT_CODE_REQUIRED:
178 RDEBUG2("Next securid token code required for %s",
181 /* create a new session */
182 pSecurid_session = securid_session_alloc();
183 pSecurid_session->sdiHandle = sdiHandle;
184 pSecurid_session->securidSessionState = NEXT_CODE_REQUIRED_STATE;
185 pSecurid_session->identity = strdup(username);
187 /* insert new session in the session list */
188 securid_sessionlist_add(inst,request,pSecurid_session);
190 strlcpy(replyMsgBuffer, "\r\nPlease Enter the Next Code from Your Token:", replyMsgBufferSize);
191 return RC_SECURID_AUTH_CHALLENGE;
193 radlog(L_ERR,"SecurID: Unexpected error from ACE/Agent API acmRet=%d",acmRet);
194 return RC_SECURID_AUTH_FAILURE;
199 /* existing session found */
200 RDEBUG("Continuing previous session found for user [%s]",username);
202 /* continue previous session */
203 switch (pSecurid_session->securidSessionState) {
204 case NEXT_CODE_REQUIRED_STATE:
205 DEBUG2("Securid NEXT_CODE_REQUIRED_STATE: User [%s]",username);
206 /* next token code mode */
208 acmRet = SD_Next(pSecurid_session->sdiHandle, (SD_CHAR*)passcode);
209 if (acmRet == ACM_OK) {
210 radlog(L_INFO,"Next SecurID token accepted for [%s].",pSecurid_session->identity);
211 rc = RC_SECURID_AUTH_SUCCESS;
214 radlog(L_INFO,"SecurID: Next token rejected for [%s].",pSecurid_session->identity);
215 rc = RC_SECURID_AUTH_FAILURE;
218 /* deallocate session */
219 securid_session_free(inst,request,pSecurid_session);
222 case NEW_PIN_REQUIRED_STATE:
223 RDEBUG2("SecurID NEW_PIN_REQUIRED_STATE for %s",
226 /* save the previous pin */
227 if (pSecurid_session->pin) {
228 free(pSecurid_session->pin);
229 pSecurid_session->pin = NULL;
231 pSecurid_session->pin = strdup(passcode);
233 strlcpy(replyMsgBuffer,"\r\n Please re-enter new PIN:", replyMsgBufferSize);
236 pSecurid_session->securidSessionState = NEW_PIN_USER_CONFIRM_STATE;
238 /* insert the updated session in the session list */
239 securid_sessionlist_add(inst,request,pSecurid_session);
240 return RC_SECURID_AUTH_CHALLENGE;
242 case NEW_PIN_USER_CONFIRM_STATE:
243 RDEBUG2("SecurID NEW_PIN_USER_CONFIRM_STATE: User [%s]",username);
244 /* compare previous pin and current pin */
245 if (!pSecurid_session->pin || strcmp(pSecurid_session->pin,passcode)) {
246 RDEBUG2("Pin confirmation failed. Pins do not match [%s] and [%s]",
247 SAFE_STR(pSecurid_session->pin),
249 /* pins do not match */
251 /* challenge the user again */
252 AceGetPinParams(pSecurid_session->sdiHandle, &pinParams);
253 if (pinParams.Alphanumeric) {
254 strcpy(format, "alphanumeric characters");
256 strcpy(format, "digits");
258 snprintf(replyMsgBuffer, replyMsgBufferSize,
259 " \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:",
260 pinParams.Min, pinParams.Max, format);
262 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
264 /* insert the updated session in the session list */
265 securid_sessionlist_add(inst,request,pSecurid_session);
266 rc = RC_SECURID_AUTH_CHALLENGE;
270 RDEBUG2("Pin confirmation succeeded. Pins match");
271 acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)passcode);
272 if (acmRet == ACM_NEW_PIN_ACCEPTED) {
273 RDEBUG("New SecurID pin accepted for %s.",pSecurid_session->identity);
275 pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
277 /* insert the updated session in the session list */
278 securid_sessionlist_add(inst,request,pSecurid_session);
280 rc = RC_SECURID_AUTH_CHALLENGE;
281 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);
283 RDEBUG("SecurID: New SecurID pin rejected for %s.",pSecurid_session->identity);
284 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); /* cancel PIN */
287 rc = RC_SECURID_AUTH_FAILURE;
289 /* deallocate session */
290 securid_session_free(inst, request,
295 case NEW_PIN_AUTH_VALIDATE_STATE:
296 acmRet = SD_Check(pSecurid_session->sdiHandle, (SD_CHAR*)passcode, (SD_CHAR*)username);
297 if (acmRet == ACM_OK) {
298 RDEBUG("New SecurID passcode accepted for %s.",
299 pSecurid_session->identity);
300 rc = RC_SECURID_AUTH_SUCCESS;
303 radlog(L_INFO,"SecurID: New passcode rejected for [%s].",pSecurid_session->identity);
304 rc = RC_SECURID_AUTH_FAILURE;
307 /* deallocate session */
308 securid_session_free(inst,request,pSecurid_session);
311 case NEW_PIN_SYSTEM_ACCEPT_STATE:
312 if (!strcmp(passcode, "y")) {
313 AceGetSystemPin(pSecurid_session->sdiHandle, newPin);
315 /* Save the PIN for the next session
317 if (pSecurid_session->pin) {
318 free(pSecurid_session->pin);
319 pSecurid_session->pin = NULL;
321 pSecurid_session->pin = strdup(newPin);
323 snprintf(replyMsgBuffer, replyMsgBufferSize,
324 "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
326 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
328 /* insert the updated session in the
330 securid_sessionlist_add(inst, request,
333 rc = RC_SECURID_AUTH_CHALLENGE;
336 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); //Cancel new PIN
338 /* deallocate session */
339 securid_session_free(inst, request,
342 rc = RC_SECURID_AUTH_FAILURE;
347 case NEW_PIN_SYSTEM_CONFIRM_STATE:
348 acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)pSecurid_session->pin);
349 if (acmRet == ACM_NEW_PIN_ACCEPTED) {
350 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);
351 pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
352 /* insert the updated session in the session list */
353 securid_sessionlist_add(inst,request,pSecurid_session);
354 rc = RC_SECURID_AUTH_CHALLENGE;
357 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); //Cancel new PIN
358 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);
359 /* deallocate session */
360 securid_session_free(inst, request,
362 rc = RC_SECURID_AUTH_FAILURE;
367 /* USER_SELECTABLE state should be implemented to preserve compatibility with AM 6.x servers, which can return this state */
368 case NEW_PIN_USER_SELECT_STATE:
369 if (!strcmp(passcode, "y")) {
370 /* User has opted for a system-generated PIN */
371 AceGetSystemPin(pSecurid_session->sdiHandle, newPin);
372 snprintf(replyMsgBuffer, replyMsgBufferSize,
373 "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
375 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
377 /* insert the updated session in the session list */
378 securid_sessionlist_add(inst, request,
380 rc = RC_SECURID_AUTH_CHALLENGE;
383 /* User has opted for a user-defined PIN */
384 AceGetPinParams(pSecurid_session->sdiHandle,
386 if (pinParams.Alphanumeric) {
387 strcpy(format, "alphanumeric characters");
389 strcpy(format, "digits");
392 snprintf(replyMsgBuffer, replyMsgBufferSize,
393 " \r\n Enter your new PIN of %d to %d %s,\r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",
394 pinParams.Min, pinParams.Max, format);
395 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
397 /* insert the updated session in the session list */
398 securid_sessionlist_add(inst, request,
400 rc = RC_SECURID_AUTH_CHALLENGE;
406 radlog(L_ERR, "rlm_securid: Invalid session state %d for user [%s]",
407 pSecurid_session->securidSessionState,
417 /******************************************/
418 static int securid_detach(void *instance)
420 rlm_securid_t *inst = (rlm_securid_t *) instance;
422 /* delete session tree */
423 if (inst->session_tree) {
424 rbtree_free(inst->session_tree);
425 inst->session_tree = NULL;
428 pthread_mutex_destroy(&(inst->session_mutex));
434 static rlm_rcode_t securid_instantiate(CONF_SECTION *conf, void **instance)
438 /* Set up a storage area for instance data */
439 *instance = inst = talloc_zero(conf, rlm_securid_t);
440 if (!inst) return -1;
442 /* If the configuration parameters can't be parsed, then fail. */
443 if (cf_section_parse(conf, inst, module_config) < 0) {
444 radlog(L_ERR, "rlm_securid: Unable to parse configuration section.");
449 * Lookup sessions in the tree. We don't free them in
450 * the tree, as that's taken care of elsewhere...
452 inst->session_tree = rbtree_create(securid_session_cmp, NULL, 0);
453 if (!inst->session_tree) {
454 radlog(L_ERR, "rlm_securid: Cannot initialize session tree.");
458 pthread_mutex_init(&(inst->session_mutex), NULL);
464 * Authenticate the user via one of any well-known password.
466 static int securid_authenticate(void *instance, REQUEST *request)
469 rlm_securid_t *inst = instance;
470 VALUE_PAIR *module_fmsg_vp;
472 char buffer[MAX_STRING_LEN]="";
473 const char *username=NULL, *password=NULL;
474 char module_fmsg[MAX_STRING_LEN]="";
477 * We can only authenticate user requests which HAVE
478 * a User-Name attribute.
480 if (!request->username) {
481 radlog(L_AUTH, "rlm_securid: Attribute \"User-Name\" is required for authentication.");
482 return RLM_MODULE_INVALID;
485 if (!request->password) {
486 radlog_request(L_AUTH, 0, request, "Attribute \"Password\" is required for authentication.");
487 return RLM_MODULE_INVALID;
491 * Clear-text passwords are the only ones we support.
493 if (request->password->da->attr != PW_USER_PASSWORD) {
494 radlog_request(L_AUTH, 0, request, "Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->da->name);
495 return RLM_MODULE_INVALID;
499 * The user MUST supply a non-zero-length password.
501 if (request->password->length == 0) {
502 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_securid: empty password supplied");
503 module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
504 pairadd(&request->packet->vps, module_fmsg_vp);
505 return RLM_MODULE_INVALID;
511 username = request->username->vp_strvalue;
512 password = request->password->vp_strvalue;
514 RDEBUG("User [%s] login attempt with password [%s]",
517 rcode = securidAuth(inst, request, username, password,
518 buffer, sizeof(buffer));
521 case RC_SECURID_AUTH_SUCCESS:
522 rcode = RLM_MODULE_OK;
525 case RC_SECURID_AUTH_CHALLENGE:
526 /* reply with Access-challenge message code (11) */
528 /* Generate Prompt attribute */
529 vp = paircreate(PW_PROMPT, 0, PW_TYPE_INTEGER);
531 rad_assert(vp != NULL);
532 vp->vp_integer = 0; /* no echo */
533 pairadd(&request->reply->vps, vp);
535 /* Mark the packet as a Acceess-Challenge Packet */
536 request->reply->code = PW_ACCESS_CHALLENGE;
537 RDEBUG("Sending Access-Challenge.");
538 rcode = RLM_MODULE_HANDLED;
541 case RC_SECURID_AUTH_FAILURE:
542 case RC_SECURID_AUTH_ACCESS_DENIED_FAILURE:
543 case RC_SECURID_AUTH_INVALID_SERVER_FAILURE:
545 rcode = RLM_MODULE_REJECT;
550 /* Generate Reply-Message attribute with reply message data */
551 vp = pairmake("Reply-Message", buffer, T_OP_EQ);
553 /* make sure message ends with '\0' */
554 if (vp->length < (int) sizeof(vp->vp_strvalue)) {
555 vp->vp_strvalue[vp->length] = '\0';
558 pairadd(&request->reply->vps,vp);
565 * The module name should be the only globally exported symbol.
566 * That is, everything else should be 'static'.
568 * If the module needs to temporarily modify it's instantiation
569 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
570 * The server will then take care of ensuring that the module
571 * is single-threaded.
573 module_t rlm_securid = {
576 RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE, /* type */
577 securid_instantiate, /* instantiation */
578 securid_detach, /* detach */
580 securid_authenticate, /* authentication */
581 NULL, /* authorization */
582 NULL, /* preaccounting */
583 NULL, /* accounting */
584 NULL, /* checksimul */
585 NULL, /* pre-proxy */
586 NULL, /* post-proxy */