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/radiusd.h>
27 #include <freeradius-devel/modules.h>
30 #include "rlm_securid.h"
33 RC_SECURID_AUTH_SUCCESS = 0,
34 RC_SECURID_AUTH_FAILURE = -3,
35 RC_SECURID_AUTH_ACCESS_DENIED_FAILURE = -4,
36 RC_SECURID_AUTH_INVALID_SERVER_FAILURE = -5,
37 RC_SECURID_AUTH_CHALLENGE = -17
41 static const CONF_PARSER module_config[] = {
42 { "timer_expire", PW_TYPE_INTEGER,
43 offsetof(rlm_securid_t, timer_limit), NULL, "600"},
44 { "max_sessions", PW_TYPE_INTEGER,
45 offsetof(rlm_securid_t, max_sessions), NULL, "2048"},
46 { "max_trips_per_session", PW_TYPE_INTEGER,
47 offsetof(rlm_securid_t, max_trips_per_session), NULL, NULL},
48 { "max_round_trips", PW_TYPE_INTEGER,
49 offsetof(rlm_securid_t, max_trips_per_session), NULL, "6"},
50 { NULL, -1, 0, NULL, NULL } /* end the list */
54 /* comparison function to find session in the tree */
55 static int securid_session_cmp(const void *a, const void *b)
58 const SECURID_SESSION *one = a;
59 const SECURID_SESSION *two = b;
61 rad_assert(one != NULL);
62 rad_assert(two != NULL);
64 rcode = fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr);
65 if (rcode != 0) return rcode;
67 return memcmp(one->state, two->state, sizeof(one->state));
71 static SECURID_AUTH_RC securidAuth(void *instance, REQUEST *request,
74 char* replyMsgBuffer,int replyMsgBufferSize)
76 rlm_securid_t *inst = (rlm_securid_t *) instance;
81 SECURID_SESSION *pSecurid_session=NULL;
85 DEBUGE("SecurID username is NULL");
86 return RC_SECURID_AUTH_FAILURE;
90 DEBUGE("SecurID passcode is NULL for %s user",username);
91 return RC_SECURID_AUTH_FAILURE;
94 *replyMsgBuffer = '\0';
96 pSecurid_session = securid_sessionlist_find(inst,request);
97 if (!pSecurid_session) {
98 /* securid session not found */
99 SDI_HANDLE sdiHandle = SDI_HANDLE_NONE;
101 acmRet = SD_Init(&sdiHandle);
102 if (acmRet != ACM_OK) {
103 DEBUGE("Cannot communicate with the ACE/Server");
107 acmRet = SD_Lock(sdiHandle, (SD_CHAR*)username);
108 if (acmRet != ACM_OK) {
109 DEBUGE("SecurID: Access denied. Name [%s] lock failed.",username);
113 acmRet = SD_Check(sdiHandle, (SD_CHAR*) passcode,
114 (SD_CHAR*) username);
118 RDEBUG("SecurID authentication successful for %s.",
122 return RC_SECURID_AUTH_SUCCESS;
124 case ACM_ACCESS_DENIED:
126 RDEBUG("SecurID Access denied for %s", username);
128 return RC_SECURID_AUTH_ACCESS_DENIED_FAILURE;
130 case ACM_INVALID_SERVER:
131 DEBUGE("SecurID: Invalid ACE server.");
132 return RC_SECURID_AUTH_INVALID_SERVER_FAILURE;
134 case ACM_NEW_PIN_REQUIRED:
135 RDEBUG2("SeecurID new pin required for %s",
138 /* create a new session */
139 pSecurid_session = securid_session_alloc();
140 pSecurid_session->sdiHandle = sdiHandle; /* save ACE handle for future use */
141 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
142 pSecurid_session->identity = strdup(username);
144 /* Get PIN requirements */
145 acmRet = AceGetPinParams(sdiHandle, &pinParams);
147 /* If a system-generated PIN is required */
148 if (pinParams.Selectable == CANNOT_CHOOSE_PIN) {
149 /* Prompt user to accept a system generated PIN */
150 snprintf(replyMsgBuffer, replyMsgBufferSize,
151 "\r\nAre you prepared to accept a new system-generated PIN [y/n]?");
152 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_ACCEPT_STATE;
154 } else if (pinParams.Selectable == USER_SELECTABLE) { //may be returned by AM 6.x servers.
155 snprintf(replyMsgBuffer, replyMsgBufferSize,
156 "\r\nPress 'y' to generate a new PIN\r\nOR\r\n'n'to enter a new PIN yourself [y/n]");
157 pSecurid_session->securidSessionState = NEW_PIN_USER_SELECT_STATE;
160 if (pinParams.Alphanumeric) {
161 strcpy(format, "alphanumeric characters");
163 strcpy(format, "digits");
165 snprintf(replyMsgBuffer, replyMsgBufferSize,
166 " \r\n Enter your new PIN of %d to %d %s,\r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",
167 pinParams.Min, pinParams.Max, format);
170 /* insert new session in the session list */
171 securid_sessionlist_add(inst,request,pSecurid_session);
173 return RC_SECURID_AUTH_CHALLENGE;
175 case ACM_NEXT_CODE_REQUIRED:
176 RDEBUG2("Next securid token code required for %s",
179 /* create a new session */
180 pSecurid_session = securid_session_alloc();
181 pSecurid_session->sdiHandle = sdiHandle;
182 pSecurid_session->securidSessionState = NEXT_CODE_REQUIRED_STATE;
183 pSecurid_session->identity = strdup(username);
185 /* insert new session in the session list */
186 securid_sessionlist_add(inst,request,pSecurid_session);
188 strlcpy(replyMsgBuffer, "\r\nPlease Enter the Next Code from Your Token:", replyMsgBufferSize);
189 return RC_SECURID_AUTH_CHALLENGE;
191 DEBUGE("SecurID: Unexpected error from ACE/Agent API acmRet=%d",acmRet);
192 return RC_SECURID_AUTH_FAILURE;
197 /* existing session found */
198 RDEBUG("Continuing previous session found for user [%s]",username);
200 /* continue previous session */
201 switch (pSecurid_session->securidSessionState) {
202 case NEXT_CODE_REQUIRED_STATE:
203 DEBUG2("Securid NEXT_CODE_REQUIRED_STATE: User [%s]",username);
204 /* next token code mode */
206 acmRet = SD_Next(pSecurid_session->sdiHandle, (SD_CHAR*)passcode);
207 if (acmRet == ACM_OK) {
208 radlog(L_INFO,"Next SecurID token accepted for [%s].",pSecurid_session->identity);
209 rc = RC_SECURID_AUTH_SUCCESS;
212 radlog(L_INFO,"SecurID: Next token rejected for [%s].",pSecurid_session->identity);
213 rc = RC_SECURID_AUTH_FAILURE;
216 /* deallocate session */
217 securid_session_free(inst,request,pSecurid_session);
220 case NEW_PIN_REQUIRED_STATE:
221 RDEBUG2("SecurID NEW_PIN_REQUIRED_STATE for %s",
224 /* save the previous pin */
225 if (pSecurid_session->pin) {
226 free(pSecurid_session->pin);
227 pSecurid_session->pin = NULL;
229 pSecurid_session->pin = strdup(passcode);
231 strlcpy(replyMsgBuffer,"\r\n Please re-enter new PIN:", replyMsgBufferSize);
234 pSecurid_session->securidSessionState = NEW_PIN_USER_CONFIRM_STATE;
236 /* insert the updated session in the session list */
237 securid_sessionlist_add(inst,request,pSecurid_session);
238 return RC_SECURID_AUTH_CHALLENGE;
240 case NEW_PIN_USER_CONFIRM_STATE:
241 RDEBUG2("SecurID NEW_PIN_USER_CONFIRM_STATE: User [%s]",username);
242 /* compare previous pin and current pin */
243 if (!pSecurid_session->pin || strcmp(pSecurid_session->pin,passcode)) {
244 RDEBUG2("Pin confirmation failed. Pins do not match [%s] and [%s]",
245 SAFE_STR(pSecurid_session->pin),
247 /* pins do not match */
249 /* challenge the user again */
250 AceGetPinParams(pSecurid_session->sdiHandle, &pinParams);
251 if (pinParams.Alphanumeric) {
252 strcpy(format, "alphanumeric characters");
254 strcpy(format, "digits");
256 snprintf(replyMsgBuffer, replyMsgBufferSize,
257 " \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:",
258 pinParams.Min, pinParams.Max, format);
260 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
262 /* insert the updated session in the session list */
263 securid_sessionlist_add(inst,request,pSecurid_session);
264 rc = RC_SECURID_AUTH_CHALLENGE;
268 RDEBUG2("Pin confirmation succeeded. Pins match");
269 acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)passcode);
270 if (acmRet == ACM_NEW_PIN_ACCEPTED) {
271 RDEBUG("New SecurID pin accepted for %s.",pSecurid_session->identity);
273 pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
275 /* insert the updated session in the session list */
276 securid_sessionlist_add(inst,request,pSecurid_session);
278 rc = RC_SECURID_AUTH_CHALLENGE;
279 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);
281 RDEBUG("SecurID: New SecurID pin rejected for %s.",pSecurid_session->identity);
282 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); /* cancel PIN */
285 rc = RC_SECURID_AUTH_FAILURE;
287 /* deallocate session */
288 securid_session_free(inst, request,
293 case NEW_PIN_AUTH_VALIDATE_STATE:
294 acmRet = SD_Check(pSecurid_session->sdiHandle, (SD_CHAR*)passcode, (SD_CHAR*)username);
295 if (acmRet == ACM_OK) {
296 RDEBUG("New SecurID passcode accepted for %s.",
297 pSecurid_session->identity);
298 rc = RC_SECURID_AUTH_SUCCESS;
301 radlog(L_INFO,"SecurID: New passcode rejected for [%s].",pSecurid_session->identity);
302 rc = RC_SECURID_AUTH_FAILURE;
305 /* deallocate session */
306 securid_session_free(inst,request,pSecurid_session);
309 case NEW_PIN_SYSTEM_ACCEPT_STATE:
310 if (!strcmp(passcode, "y")) {
311 AceGetSystemPin(pSecurid_session->sdiHandle, newPin);
313 /* Save the PIN for the next session
315 if (pSecurid_session->pin) {
316 free(pSecurid_session->pin);
317 pSecurid_session->pin = NULL;
319 pSecurid_session->pin = strdup(newPin);
321 snprintf(replyMsgBuffer, replyMsgBufferSize,
322 "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
324 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
326 /* insert the updated session in the
328 securid_sessionlist_add(inst, request,
331 rc = RC_SECURID_AUTH_CHALLENGE;
334 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); //Cancel new PIN
336 /* deallocate session */
337 securid_session_free(inst, request,
340 rc = RC_SECURID_AUTH_FAILURE;
345 case NEW_PIN_SYSTEM_CONFIRM_STATE:
346 acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)pSecurid_session->pin);
347 if (acmRet == ACM_NEW_PIN_ACCEPTED) {
348 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);
349 pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
350 /* insert the updated session in the session list */
351 securid_sessionlist_add(inst,request,pSecurid_session);
352 rc = RC_SECURID_AUTH_CHALLENGE;
355 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); //Cancel new PIN
356 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);
357 /* deallocate session */
358 securid_session_free(inst, request,
360 rc = RC_SECURID_AUTH_FAILURE;
365 /* USER_SELECTABLE state should be implemented to preserve compatibility with AM 6.x servers, which can return this state */
366 case NEW_PIN_USER_SELECT_STATE:
367 if (!strcmp(passcode, "y")) {
368 /* User has opted for a system-generated PIN */
369 AceGetSystemPin(pSecurid_session->sdiHandle, newPin);
370 snprintf(replyMsgBuffer, replyMsgBufferSize,
371 "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
373 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
375 /* insert the updated session in the session list */
376 securid_sessionlist_add(inst, request,
378 rc = RC_SECURID_AUTH_CHALLENGE;
381 /* User has opted for a user-defined PIN */
382 AceGetPinParams(pSecurid_session->sdiHandle,
384 if (pinParams.Alphanumeric) {
385 strcpy(format, "alphanumeric characters");
387 strcpy(format, "digits");
390 snprintf(replyMsgBuffer, replyMsgBufferSize,
391 " \r\n Enter your new PIN of %d to %d %s,\r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",
392 pinParams.Min, pinParams.Max, format);
393 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
395 /* insert the updated session in the session list */
396 securid_sessionlist_add(inst, request,
398 rc = RC_SECURID_AUTH_CHALLENGE;
404 DEBUGE("rlm_securid: Invalid session state %d for user [%s]",
405 pSecurid_session->securidSessionState,
415 /******************************************/
416 static int mod_detach(void *instance)
418 rlm_securid_t *inst = (rlm_securid_t *) instance;
420 /* delete session tree */
421 if (inst->session_tree) {
422 rbtree_free(inst->session_tree);
423 inst->session_tree = NULL;
426 pthread_mutex_destroy(&(inst->session_mutex));
432 static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
434 rlm_securid_t *inst = instance;
437 * Lookup sessions in the tree. We don't free them in
438 * the tree, as that's taken care of elsewhere...
440 inst->session_tree = rbtree_create(securid_session_cmp, NULL, 0);
441 if (!inst->session_tree) {
442 DEBUGE("rlm_securid: Cannot initialize session tree.");
446 pthread_mutex_init(&(inst->session_mutex), NULL);
452 * Authenticate the user via one of any well-known password.
454 static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
457 rlm_securid_t *inst = instance;
458 char buffer[MAX_STRING_LEN]="";
459 const char *username=NULL, *password=NULL;
463 * We can only authenticate user requests which HAVE
464 * a User-Name attribute.
466 if (!request->username) {
467 radlog(L_AUTH, "rlm_securid: Attribute \"User-Name\" is required for authentication.");
468 return RLM_MODULE_INVALID;
471 if (!request->password) {
472 radlog_request(L_AUTH, 0, request, "Attribute \"Password\" is required for authentication.");
473 return RLM_MODULE_INVALID;
477 * Clear-text passwords are the only ones we support.
479 if (request->password->da->attr != PW_USER_PASSWORD) {
480 radlog_request(L_AUTH, 0, request, "Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->da->name);
481 return RLM_MODULE_INVALID;
485 * The user MUST supply a non-zero-length password.
487 if (request->password->length == 0) {
488 RDEBUGE("Password should not be empty");
489 return RLM_MODULE_INVALID;
495 username = request->username->vp_strvalue;
496 password = request->password->vp_strvalue;
498 RDEBUG("User [%s] login attempt with password [%s]",
501 rcode = securidAuth(inst, request, username, password,
502 buffer, sizeof(buffer));
505 case RC_SECURID_AUTH_SUCCESS:
506 rcode = RLM_MODULE_OK;
509 case RC_SECURID_AUTH_CHALLENGE:
510 /* reply with Access-challenge message code (11) */
512 /* Generate Prompt attribute */
513 vp = paircreate(request->reply, PW_PROMPT, 0);
515 rad_assert(vp != NULL);
516 vp->vp_integer = 0; /* no echo */
517 pairadd(&request->reply->vps, vp);
519 /* Mark the packet as a Acceess-Challenge Packet */
520 request->reply->code = PW_ACCESS_CHALLENGE;
521 RDEBUG("Sending Access-Challenge.");
522 rcode = RLM_MODULE_HANDLED;
525 case RC_SECURID_AUTH_FAILURE:
526 case RC_SECURID_AUTH_ACCESS_DENIED_FAILURE:
527 case RC_SECURID_AUTH_INVALID_SERVER_FAILURE:
529 rcode = RLM_MODULE_REJECT;
533 if (*buffer) pairmake_reply("Reply-Message", buffer, T_OP_EQ);
540 * The module name should be the only globally exported symbol.
541 * That is, everything else should be 'static'.
543 * If the module needs to temporarily modify it's instantiation
544 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
545 * The server will then take care of ensuring that the module
546 * is single-threaded.
548 module_t rlm_securid = {
551 RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE, /* type */
552 sizeof(rlm_securid_t),
554 mod_instantiate, /* instantiation */
555 mod_detach, /* detach */
557 mod_authenticate, /* authentication */
558 NULL, /* authorization */
559 NULL, /* preaccounting */
560 NULL, /* accounting */
561 NULL, /* checksimul */
562 NULL, /* pre-proxy */
563 NULL, /* post-proxy */