6 * supports "next-token code" and "new-pin" modes
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
23 * Copyright 2012 The FreeRADIUS server project
24 * Copyright 2012 Alan DeKok <aland@networkradius.com>
27 #include <freeradius-devel/ident.h>
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/modules.h>
32 #include "rlm_securid.h"
35 RC_SECURID_AUTH_SUCCESS = 0,
36 RC_SECURID_AUTH_FAILURE = -3,
37 RC_SECURID_AUTH_ACCESS_DENIED_FAILURE = -4,
38 RC_SECURID_AUTH_INVALID_SERVER_FAILURE = -5,
39 RC_SECURID_AUTH_CHALLENGE = -17
44 static const CONF_PARSER module_config[] = {
45 { "timer_expire", PW_TYPE_INTEGER,
46 offsetof(rlm_securid_t, timer_limit), NULL, "600"},
47 { "max_sessions", PW_TYPE_INTEGER,
48 offsetof(rlm_securid_t, max_sessions), NULL, "2048"},
49 { "max_trips_per_session", PW_TYPE_INTEGER,
50 offsetof(rlm_securid_t, max_trips_per_session), NULL, NULL},
51 { "max_round_trips", PW_TYPE_INTEGER,
52 offsetof(rlm_securid_t, max_trips_per_session), NULL, "6"},
53 { NULL, -1, 0, NULL, NULL } /* end the list */
57 /* comparison function to find session in the tree */
58 static int securid_session_cmp(const void *a, const void *b)
61 const SECURID_SESSION *one = a;
62 const SECURID_SESSION *two = b;
64 rad_assert(one != NULL);
65 rad_assert(two != NULL);
67 rcode = fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr);
68 if (rcode != 0) return rcode;
70 return memcmp(one->state, two->state, sizeof(one->state));
74 static SECURID_AUTH_RC securidAuth(void *instance, REQUEST *request,
77 char* replyMsgBuffer,int replyMsgBufferSize)
79 rlm_securid_t *inst = (rlm_securid_t *) instance;
84 SECURID_SESSION *pSecurid_session=NULL;
88 radlog(L_ERR, "SecurID username is NULL");
89 return RC_SECURID_AUTH_FAILURE;
93 radlog(L_ERR, "SecurID passcode is NULL for %s user",username);
94 return RC_SECURID_AUTH_FAILURE;
97 *replyMsgBuffer = '\0';
99 pSecurid_session = securid_sessionlist_find(inst,request);
100 if (pSecurid_session == NULL) {
101 /* securid session not found */
102 SDI_HANDLE sdiHandle = SDI_HANDLE_NONE;
104 acmRet = SD_Init(&sdiHandle);
105 if (acmRet != ACM_OK) {
106 radlog(L_ERR, "Cannot communicate with the ACE/Server");
110 acmRet = SD_Lock(sdiHandle, (SD_CHAR*)username);
111 if (acmRet != ACM_OK) {
112 radlog(L_ERR,"SecurID: Access denied. Name [%s] lock failed.",username);
116 acmRet = SD_Check(sdiHandle, (SD_CHAR*) passcode,
117 (SD_CHAR*) username);
121 RDEBUG("SecurID authentication successful for %s.",
125 return RC_SECURID_AUTH_SUCCESS;
127 case ACM_ACCESS_DENIED:
129 RDEBUG("SecurID Access denied for %s", username);
131 return RC_SECURID_AUTH_ACCESS_DENIED_FAILURE;
133 case ACM_INVALID_SERVER:
134 radlog(L_ERR,"SecurID: Invalid ACE server.");
135 return RC_SECURID_AUTH_INVALID_SERVER_FAILURE;
137 case ACM_NEW_PIN_REQUIRED:
138 RDEBUG2("SeecurID new pin required for %s",
141 /* create a new session */
142 pSecurid_session = securid_session_alloc();
143 pSecurid_session->sdiHandle = sdiHandle; /* save ACE handle for future use */
144 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
145 pSecurid_session->identity = strdup(username);
147 /* Get PIN requirements */
148 acmRet = AceGetPinParams(sdiHandle, &pinParams);
150 /* If a system-generated PIN is required */
151 if (pinParams.Selectable == CANNOT_CHOOSE_PIN) {
152 /* Prompt user to accept a system generated PIN */
153 snprintf(replyMsgBuffer, replyMsgBufferSize,
154 "\r\nAre you prepared to accept a new system-generated PIN [y/n]?");
155 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_ACCEPT_STATE;
157 } else if (pinParams.Selectable == USER_SELECTABLE) { //may be returned by AM 6.x servers.
158 snprintf(replyMsgBuffer, replyMsgBufferSize,
159 "\r\nPress 'y' to generate a new PIN\r\nOR\r\n'n'to enter a new PIN yourself [y/n]");
160 pSecurid_session->securidSessionState = NEW_PIN_USER_SELECT_STATE;
163 if (pinParams.Alphanumeric) {
164 strcpy(format, "alphanumeric characters");
166 strcpy(format, "digits");
168 snprintf(replyMsgBuffer, replyMsgBufferSize,
169 " \r\n Enter your new PIN of %d to %d %s,\r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",
170 pinParams.Min, pinParams.Max, format);
173 /* insert new session in the session list */
174 securid_sessionlist_add(inst,request,pSecurid_session);
176 return RC_SECURID_AUTH_CHALLENGE;
178 case ACM_NEXT_CODE_REQUIRED:
179 RDEBUG2("Next securid token code required for %s",
182 /* create a new session */
183 pSecurid_session = securid_session_alloc();
184 pSecurid_session->sdiHandle = sdiHandle;
185 pSecurid_session->securidSessionState = NEXT_CODE_REQUIRED_STATE;
186 pSecurid_session->identity = strdup(username);
188 /* insert new session in the session list */
189 securid_sessionlist_add(inst,request,pSecurid_session);
191 strlcpy(replyMsgBuffer, "\r\nPlease Enter the Next Code from Your Token:", replyMsgBufferSize);
192 return RC_SECURID_AUTH_CHALLENGE;
194 radlog(L_ERR,"SecurID: Unexpected error from ACE/Agent API acmRet=%d",acmRet);
195 return RC_SECURID_AUTH_FAILURE;
200 /* existing session found */
201 RDEBUG("Continuing previous session found for user [%s]",username);
203 /* continue previous session */
204 switch (pSecurid_session->securidSessionState) {
205 case NEXT_CODE_REQUIRED_STATE:
206 DEBUG2("Securid NEXT_CODE_REQUIRED_STATE: User [%s]",username);
207 /* next token code mode */
209 acmRet = SD_Next(pSecurid_session->sdiHandle, (SD_CHAR*)passcode);
210 if (acmRet == ACM_OK) {
211 radlog(L_INFO,"Next SecurID token accepted for [%s].",pSecurid_session->identity);
212 rc = RC_SECURID_AUTH_SUCCESS;
215 radlog(L_INFO,"SecurID: Next token rejected for [%s].",pSecurid_session->identity);
216 rc = RC_SECURID_AUTH_FAILURE;
219 /* deallocate session */
220 securid_session_free(inst,request,pSecurid_session);
223 case NEW_PIN_REQUIRED_STATE:
224 RDEBUG2("SecurID NEW_PIN_REQUIRED_STATE for %s",
227 /* save the previous pin */
228 if (pSecurid_session->pin) {
229 free(pSecurid_session->pin);
230 pSecurid_session->pin = NULL;
232 pSecurid_session->pin = strdup(passcode);
234 strlcpy(replyMsgBuffer,"\r\n Please re-enter new PIN:", replyMsgBufferSize);
237 pSecurid_session->securidSessionState = NEW_PIN_USER_CONFIRM_STATE;
239 /* insert the updated session in the session list */
240 securid_sessionlist_add(inst,request,pSecurid_session);
241 return RC_SECURID_AUTH_CHALLENGE;
243 case NEW_PIN_USER_CONFIRM_STATE:
244 RDEBUG2("SecurID NEW_PIN_USER_CONFIRM_STATE: User [%s]",username);
245 /* compare previous pin and current pin */
246 if (!pSecurid_session->pin || strcmp(pSecurid_session->pin,passcode)) {
247 RDEBUG2("Pin confirmation failed. Pins do not match [%s] and [%s]",
248 SAFE_STR(pSecurid_session->pin),
250 /* pins do not match */
252 /* challenge the user again */
253 AceGetPinParams(pSecurid_session->sdiHandle, &pinParams);
254 if (pinParams.Alphanumeric) {
255 strcpy(format, "alphanumeric characters");
257 strcpy(format, "digits");
259 snprintf(replyMsgBuffer, replyMsgBufferSize,
260 " \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:",
261 pinParams.Min, pinParams.Max, format);
263 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
265 /* insert the updated session in the session list */
266 securid_sessionlist_add(inst,request,pSecurid_session);
267 rc = RC_SECURID_AUTH_CHALLENGE;
271 RDEBUG2("Pin confirmation succeeded. Pins match");
272 acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)passcode);
273 if (acmRet == ACM_NEW_PIN_ACCEPTED) {
274 RDEBUG("New SecurID pin accepted for %s.",pSecurid_session->identity);
276 pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
278 /* insert the updated session in the session list */
279 securid_sessionlist_add(inst,request,pSecurid_session);
281 rc = RC_SECURID_AUTH_CHALLENGE;
282 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);
284 RDEBUG("SecurID: New SecurID pin rejected for %s.",pSecurid_session->identity);
285 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); /* cancel PIN */
288 rc = RC_SECURID_AUTH_FAILURE;
290 /* deallocate session */
291 securid_session_free(inst, request,
296 case NEW_PIN_AUTH_VALIDATE_STATE:
297 acmRet = SD_Check(pSecurid_session->sdiHandle, (SD_CHAR*)passcode, (SD_CHAR*)username);
298 if (acmRet == ACM_OK) {
299 RDEBUG("New SecurID passcode accepted for %s.",
300 pSecurid_session->identity);
301 rc = RC_SECURID_AUTH_SUCCESS;
304 radlog(L_INFO,"SecurID: New passcode rejected for [%s].",pSecurid_session->identity);
305 rc = RC_SECURID_AUTH_FAILURE;
308 /* deallocate session */
309 securid_session_free(inst,request,pSecurid_session);
312 case NEW_PIN_SYSTEM_ACCEPT_STATE:
313 if (!strcmp(passcode, "y")) {
314 AceGetSystemPin(pSecurid_session->sdiHandle, newPin);
316 /* Save the PIN for the next session
318 if (pSecurid_session->pin) {
319 free(pSecurid_session->pin);
320 pSecurid_session->pin = NULL;
322 pSecurid_session->pin = strdup(newPin);
324 snprintf(replyMsgBuffer, replyMsgBufferSize,
325 "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
327 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
329 /* insert the updated session in the
331 securid_sessionlist_add(inst, request,
334 rc = RC_SECURID_AUTH_CHALLENGE;
337 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); //Cancel new PIN
339 /* deallocate session */
340 securid_session_free(inst, request,
343 rc = RC_SECURID_AUTH_FAILURE;
348 case NEW_PIN_SYSTEM_CONFIRM_STATE:
349 acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)pSecurid_session->pin);
350 if (acmRet == ACM_NEW_PIN_ACCEPTED) {
351 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);
352 pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
353 /* insert the updated session in the session list */
354 securid_sessionlist_add(inst,request,pSecurid_session);
355 rc = RC_SECURID_AUTH_CHALLENGE;
358 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); //Cancel new PIN
359 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);
360 /* deallocate session */
361 securid_session_free(inst, request,
363 rc = RC_SECURID_AUTH_FAILURE;
368 /* USER_SELECTABLE state should be implemented to preserve compatibility with AM 6.x servers, which can return this state */
369 case NEW_PIN_USER_SELECT_STATE:
370 if (!strcmp(passcode, "y")) {
371 /* User has opted for a system-generated PIN */
372 AceGetSystemPin(pSecurid_session->sdiHandle, newPin);
373 snprintf(replyMsgBuffer, replyMsgBufferSize,
374 "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
376 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
378 /* insert the updated session in the session list */
379 securid_sessionlist_add(inst, request,
381 rc = RC_SECURID_AUTH_CHALLENGE;
384 /* User has opted for a user-defined PIN */
385 AceGetPinParams(pSecurid_session->sdiHandle,
387 if (pinParams.Alphanumeric) {
388 strcpy(format, "alphanumeric characters");
390 strcpy(format, "digits");
393 snprintf(replyMsgBuffer, replyMsgBufferSize,
394 " \r\n Enter your new PIN of %d to %d %s,\r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",
395 pinParams.Min, pinParams.Max, format);
396 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
398 /* insert the updated session in the session list */
399 securid_sessionlist_add(inst, request,
401 rc = RC_SECURID_AUTH_CHALLENGE;
407 radlog(L_ERR|L_CONS, "rlm_securid: Invalid session state %d for user [%s]",
408 pSecurid_session->securidSessionState,
418 /******************************************/
419 static int securid_detach(void *instance)
421 rlm_securid_t *inst = (rlm_securid_t *) instance;
423 /* delete session tree */
424 if (inst->session_tree) {
425 rbtree_free(inst->session_tree);
426 inst->session_tree = NULL;
429 pthread_mutex_destroy(&(inst->session_mutex));
436 static int securid_instantiate(CONF_SECTION *conf, void **instance)
440 /* Set up a storage area for instance data */
441 inst = rad_malloc(sizeof(*inst));
442 if (!inst) return -1;
443 memset(inst, 0, sizeof(*inst));
445 /* If the configuration parameters can't be parsed, then fail. */
446 if (cf_section_parse(conf, inst, module_config) < 0) {
447 radlog(L_ERR|L_CONS, "rlm_securid: Unable to parse configuration section.");
448 securid_detach(inst);
453 * Lookup sessions in the tree. We don't free them in
454 * the tree, as that's taken care of elsewhere...
456 inst->session_tree = rbtree_create(securid_session_cmp, NULL, 0);
457 if (!inst->session_tree) {
458 radlog(L_ERR|L_CONS, "rlm_securid: Cannot initialize session tree.");
459 securid_detach(inst);
463 pthread_mutex_init(&(inst->session_mutex), NULL);
471 * Authenticate the user via one of any well-known password.
473 static int securid_authenticate(void *instance, REQUEST *request)
476 rlm_securid_t *inst = instance;
477 VALUE_PAIR *module_fmsg_vp;
479 char buffer[MAX_STRING_LEN]="";
480 const char *username=NULL, *password=NULL;
481 char module_fmsg[MAX_STRING_LEN]="";
484 * We can only authenticate user requests which HAVE
485 * a User-Name attribute.
487 if (!request->username) {
488 radlog(L_AUTH, "rlm_securid: Attribute \"User-Name\" is required for authentication.");
489 return RLM_MODULE_INVALID;
492 if (!request->password) {
493 radlog_request(L_AUTH, 0, request, "Attribute \"Password\" is required for authentication.");
494 return RLM_MODULE_INVALID;
498 * Clear-text passwords are the only ones we support.
500 if (request->password->attribute != PW_USER_PASSWORD) {
501 radlog_request(L_AUTH, 0, request, "Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
502 return RLM_MODULE_INVALID;
506 * The user MUST supply a non-zero-length password.
508 if (request->password->length == 0) {
509 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_securid: empty password supplied");
510 module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
511 pairadd(&request->packet->vps, module_fmsg_vp);
512 return RLM_MODULE_INVALID;
518 username = request->username->vp_strvalue;
519 password = request->password->vp_strvalue;
521 RDEBUG("User [%s] login attempt with password [%s]",
524 rcode = securidAuth(inst, request, username, password,
525 buffer, sizeof(buffer));
528 case RC_SECURID_AUTH_SUCCESS:
529 rcode = RLM_MODULE_OK;
532 case RC_SECURID_AUTH_CHALLENGE:
533 /* reply with Access-challenge message code (11) */
535 /* Generate Prompt attribute */
536 vp = paircreate(PW_PROMPT, 0, PW_TYPE_INTEGER);
538 rad_assert(vp != NULL);
539 vp->vp_integer = 0; /* no echo */
540 pairadd(&request->reply->vps, vp);
542 /* Mark the packet as a Acceess-Challenge Packet */
543 request->reply->code = PW_ACCESS_CHALLENGE;
544 RDEBUG("Sending Access-Challenge.");
545 rcode = RLM_MODULE_HANDLED;
548 case RC_SECURID_AUTH_FAILURE:
549 case RC_SECURID_AUTH_ACCESS_DENIED_FAILURE:
550 case RC_SECURID_AUTH_INVALID_SERVER_FAILURE:
552 rcode = RLM_MODULE_REJECT;
557 /* Generate Reply-Message attribute with reply message data */
558 vp = pairmake("Reply-Message", buffer, T_OP_EQ);
560 /* make sure message ends with '\0' */
561 if (vp->length < (int) sizeof(vp->vp_strvalue)) {
562 vp->vp_strvalue[vp->length] = '\0';
565 pairadd(&request->reply->vps,vp);
572 * The module name should be the only globally exported symbol.
573 * That is, everything else should be 'static'.
575 * If the module needs to temporarily modify it's instantiation
576 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
577 * The server will then take care of ensuring that the module
578 * is single-threaded.
580 module_t rlm_securid = {
583 RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE, /* type */
584 securid_instantiate, /* instantiation */
585 securid_detach, /* detach */
587 securid_authenticate, /* authentication */
588 NULL, /* authorization */
589 NULL, /* preaccounting */
590 NULL, /* accounting */
591 NULL, /* checksimul */
592 NULL, /* pre-proxy */
593 NULL, /* post-proxy */