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
42 static const CONF_PARSER module_config[] = {
43 { "timer_expire", PW_TYPE_INTEGER,
44 offsetof(rlm_securid_t, timer_limit), NULL, "600"},
45 { "max_sessions", PW_TYPE_INTEGER,
46 offsetof(rlm_securid_t, max_sessions), NULL, "2048"},
47 { "max_trips_per_session", PW_TYPE_INTEGER,
48 offsetof(rlm_securid_t, max_trips_per_session), NULL, NULL},
49 { "max_round_trips", PW_TYPE_INTEGER,
50 offsetof(rlm_securid_t, max_trips_per_session), NULL, "6"},
51 { NULL, -1, 0, NULL, NULL } /* end the list */
55 /* comparison function to find session in the tree */
56 static int securid_session_cmp(const void *a, const void *b)
59 const SECURID_SESSION *one = a;
60 const SECURID_SESSION *two = b;
62 rad_assert(one != NULL);
63 rad_assert(two != NULL);
65 rcode = fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr);
66 if (rcode != 0) return rcode;
68 return memcmp(one->state, two->state, sizeof(one->state));
72 static SECURID_AUTH_RC securidAuth(void *instance, REQUEST *request,
75 char* replyMsgBuffer,int replyMsgBufferSize)
77 rlm_securid_t *inst = (rlm_securid_t *) instance;
82 SECURID_SESSION *pSecurid_session=NULL;
86 radlog(L_ERR, "SecurID username is NULL");
87 return RC_SECURID_AUTH_FAILURE;
91 radlog(L_ERR, "SecurID passcode is NULL for %s user",username);
92 return RC_SECURID_AUTH_FAILURE;
95 *replyMsgBuffer = '\0';
97 pSecurid_session = securid_sessionlist_find(inst,request);
98 if (pSecurid_session == NULL) {
99 /* securid session not found */
100 SDI_HANDLE sdiHandle = SDI_HANDLE_NONE;
102 acmRet = SD_Init(&sdiHandle);
103 if (acmRet != ACM_OK) {
104 radlog(L_ERR, "Cannot communicate with the ACE/Server");
108 acmRet = SD_Lock(sdiHandle, (SD_CHAR*)username);
109 if (acmRet != ACM_OK) {
110 radlog(L_ERR,"SecurID: Access denied. Name [%s] lock failed.",username);
114 acmRet = SD_Check(sdiHandle, (SD_CHAR*) passcode,
115 (SD_CHAR*) username);
119 RDEBUG("SecurID authentication successful for %s.",
123 return RC_SECURID_AUTH_SUCCESS;
125 case ACM_ACCESS_DENIED:
127 RDEBUG("SecurID Access denied for %s", username);
129 return RC_SECURID_AUTH_ACCESS_DENIED_FAILURE;
131 case ACM_INVALID_SERVER:
132 radlog(L_ERR,"SecurID: Invalid ACE server.");
133 return RC_SECURID_AUTH_INVALID_SERVER_FAILURE;
135 case ACM_NEW_PIN_REQUIRED:
136 RDEBUG2("SeecurID new pin required for %s",
139 /* create a new session */
140 pSecurid_session = securid_session_alloc();
141 pSecurid_session->sdiHandle = sdiHandle; /* save ACE handle for future use */
142 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
143 pSecurid_session->identity = strdup(username);
145 /* Get PIN requirements */
146 acmRet = AceGetPinParams(sdiHandle, &pinParams);
148 /* If a system-generated PIN is required */
149 if (pinParams.Selectable == CANNOT_CHOOSE_PIN) {
150 /* Prompt user to accept a system generated PIN */
151 snprintf(replyMsgBuffer, replyMsgBufferSize,
152 "\r\nAre you prepared to accept a new system-generated PIN [y/n]?");
153 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_ACCEPT_STATE;
155 } else if (pinParams.Selectable == USER_SELECTABLE) { //may be returned by AM 6.x servers.
156 snprintf(replyMsgBuffer, replyMsgBufferSize,
157 "\r\nPress 'y' to generate a new PIN\r\nOR\r\n'n'to enter a new PIN yourself [y/n]");
158 pSecurid_session->securidSessionState = NEW_PIN_USER_SELECT_STATE;
161 if (pinParams.Alphanumeric) {
162 strcpy(format, "alphanumeric characters");
164 strcpy(format, "digits");
166 snprintf(replyMsgBuffer, replyMsgBufferSize,
167 " \r\n Enter your new PIN of %d to %d %s,\r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",
168 pinParams.Min, pinParams.Max, format);
171 /* insert new session in the session list */
172 securid_sessionlist_add(inst,request,pSecurid_session);
174 return RC_SECURID_AUTH_CHALLENGE;
176 case ACM_NEXT_CODE_REQUIRED:
177 RDEBUG2("Next securid token code required for %s",
180 /* create a new session */
181 pSecurid_session = securid_session_alloc();
182 pSecurid_session->sdiHandle = sdiHandle;
183 pSecurid_session->securidSessionState = NEXT_CODE_REQUIRED_STATE;
184 pSecurid_session->identity = strdup(username);
186 /* insert new session in the session list */
187 securid_sessionlist_add(inst,request,pSecurid_session);
189 strlcpy(replyMsgBuffer, "\r\nPlease Enter the Next Code from Your Token:", replyMsgBufferSize);
190 return RC_SECURID_AUTH_CHALLENGE;
192 radlog(L_ERR,"SecurID: Unexpected error from ACE/Agent API acmRet=%d",acmRet);
193 return RC_SECURID_AUTH_FAILURE;
198 /* existing session found */
199 RDEBUG("Continuing previous session found for user [%s]",username);
201 /* continue previous session */
202 switch (pSecurid_session->securidSessionState) {
203 case NEXT_CODE_REQUIRED_STATE:
204 DEBUG2("Securid NEXT_CODE_REQUIRED_STATE: User [%s]",username);
205 /* next token code mode */
207 acmRet = SD_Next(pSecurid_session->sdiHandle, (SD_CHAR*)passcode);
208 if (acmRet == ACM_OK) {
209 radlog(L_INFO,"Next SecurID token accepted for [%s].",pSecurid_session->identity);
210 rc = RC_SECURID_AUTH_SUCCESS;
213 radlog(L_INFO,"SecurID: Next token rejected for [%s].",pSecurid_session->identity);
214 rc = RC_SECURID_AUTH_FAILURE;
217 /* deallocate session */
218 securid_session_free(inst,request,pSecurid_session);
221 case NEW_PIN_REQUIRED_STATE:
222 RDEBUG2("SecurID NEW_PIN_REQUIRED_STATE for %s",
225 /* save the previous pin */
226 if (pSecurid_session->pin) {
227 free(pSecurid_session->pin);
228 pSecurid_session->pin = NULL;
230 pSecurid_session->pin = strdup(passcode);
232 strlcpy(replyMsgBuffer,"\r\n Please re-enter new PIN:", replyMsgBufferSize);
235 pSecurid_session->securidSessionState = NEW_PIN_USER_CONFIRM_STATE;
237 /* insert the updated session in the session list */
238 securid_sessionlist_add(inst,request,pSecurid_session);
239 return RC_SECURID_AUTH_CHALLENGE;
241 case NEW_PIN_USER_CONFIRM_STATE:
242 RDEBUG2("SecurID NEW_PIN_USER_CONFIRM_STATE: User [%s]",username);
243 /* compare previous pin and current pin */
244 if (!pSecurid_session->pin || strcmp(pSecurid_session->pin,passcode)) {
245 RDEBUG2("Pin confirmation failed. Pins do not match [%s] and [%s]",
246 SAFE_STR(pSecurid_session->pin),
248 /* pins do not match */
250 /* challenge the user again */
251 AceGetPinParams(pSecurid_session->sdiHandle, &pinParams);
252 if (pinParams.Alphanumeric) {
253 strcpy(format, "alphanumeric characters");
255 strcpy(format, "digits");
257 snprintf(replyMsgBuffer, replyMsgBufferSize,
258 " \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:",
259 pinParams.Min, pinParams.Max, format);
261 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
263 /* insert the updated session in the session list */
264 securid_sessionlist_add(inst,request,pSecurid_session);
265 rc = RC_SECURID_AUTH_CHALLENGE;
269 RDEBUG2("Pin confirmation succeeded. Pins match");
270 acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)passcode);
271 if (acmRet == ACM_NEW_PIN_ACCEPTED) {
272 RDEBUG("New SecurID pin accepted for %s.",pSecurid_session->identity);
274 pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
276 /* insert the updated session in the session list */
277 securid_sessionlist_add(inst,request,pSecurid_session);
279 rc = RC_SECURID_AUTH_CHALLENGE;
280 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);
282 RDEBUG("SecurID: New SecurID pin rejected for %s.",pSecurid_session->identity);
283 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); /* cancel PIN */
286 rc = RC_SECURID_AUTH_FAILURE;
288 /* deallocate session */
289 securid_session_free(inst, request,
294 case NEW_PIN_AUTH_VALIDATE_STATE:
295 acmRet = SD_Check(pSecurid_session->sdiHandle, (SD_CHAR*)passcode, (SD_CHAR*)username);
296 if (acmRet == ACM_OK) {
297 RDEBUG("New SecurID passcode accepted for %s.",
298 pSecurid_session->identity);
299 rc = RC_SECURID_AUTH_SUCCESS;
302 radlog(L_INFO,"SecurID: New passcode rejected for [%s].",pSecurid_session->identity);
303 rc = RC_SECURID_AUTH_FAILURE;
306 /* deallocate session */
307 securid_session_free(inst,request,pSecurid_session);
310 case NEW_PIN_SYSTEM_ACCEPT_STATE:
311 if (!strcmp(passcode, "y")) {
312 AceGetSystemPin(pSecurid_session->sdiHandle, newPin);
314 /* Save the PIN for the next session
316 if (pSecurid_session->pin) {
317 free(pSecurid_session->pin);
318 pSecurid_session->pin = NULL;
320 pSecurid_session->pin = strdup(newPin);
322 snprintf(replyMsgBuffer, replyMsgBufferSize,
323 "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
325 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
327 /* insert the updated session in the
329 securid_sessionlist_add(inst, request,
332 rc = RC_SECURID_AUTH_CHALLENGE;
335 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); //Cancel new PIN
337 /* deallocate session */
338 securid_session_free(inst, request,
341 rc = RC_SECURID_AUTH_FAILURE;
346 case NEW_PIN_SYSTEM_CONFIRM_STATE:
347 acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)pSecurid_session->pin);
348 if (acmRet == ACM_NEW_PIN_ACCEPTED) {
349 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);
350 pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
351 /* insert the updated session in the session list */
352 securid_sessionlist_add(inst,request,pSecurid_session);
353 rc = RC_SECURID_AUTH_CHALLENGE;
356 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); //Cancel new PIN
357 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);
358 /* deallocate session */
359 securid_session_free(inst, request,
361 rc = RC_SECURID_AUTH_FAILURE;
366 /* USER_SELECTABLE state should be implemented to preserve compatibility with AM 6.x servers, which can return this state */
367 case NEW_PIN_USER_SELECT_STATE:
368 if (!strcmp(passcode, "y")) {
369 /* User has opted for a system-generated PIN */
370 AceGetSystemPin(pSecurid_session->sdiHandle, newPin);
371 snprintf(replyMsgBuffer, replyMsgBufferSize,
372 "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
374 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
376 /* insert the updated session in the session list */
377 securid_sessionlist_add(inst, request,
379 rc = RC_SECURID_AUTH_CHALLENGE;
382 /* User has opted for a user-defined PIN */
383 AceGetPinParams(pSecurid_session->sdiHandle,
385 if (pinParams.Alphanumeric) {
386 strcpy(format, "alphanumeric characters");
388 strcpy(format, "digits");
391 snprintf(replyMsgBuffer, replyMsgBufferSize,
392 " \r\n Enter your new PIN of %d to %d %s,\r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",
393 pinParams.Min, pinParams.Max, format);
394 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
396 /* insert the updated session in the session list */
397 securid_sessionlist_add(inst, request,
399 rc = RC_SECURID_AUTH_CHALLENGE;
405 radlog(L_ERR, "rlm_securid: Invalid session state %d for user [%s]",
406 pSecurid_session->securidSessionState,
416 /******************************************/
417 static int securid_detach(void *instance)
419 rlm_securid_t *inst = (rlm_securid_t *) instance;
421 /* delete session tree */
422 if (inst->session_tree) {
423 rbtree_free(inst->session_tree);
424 inst->session_tree = NULL;
427 pthread_mutex_destroy(&(inst->session_mutex));
433 static rlm_rcode_t securid_instantiate(CONF_SECTION *conf, void **instance)
437 /* Set up a storage area for instance data */
438 *instance = inst = talloc_zero(conf, rlm_securid_t);
439 if (!inst) return -1;
441 /* If the configuration parameters can't be parsed, then fail. */
442 if (cf_section_parse(conf, inst, module_config) < 0) {
443 radlog(L_ERR, "rlm_securid: Unable to parse configuration section.");
448 * Lookup sessions in the tree. We don't free them in
449 * the tree, as that's taken care of elsewhere...
451 inst->session_tree = rbtree_create(securid_session_cmp, NULL, 0);
452 if (!inst->session_tree) {
453 radlog(L_ERR, "rlm_securid: Cannot initialize session tree.");
457 pthread_mutex_init(&(inst->session_mutex), NULL);
463 * Authenticate the user via one of any well-known password.
465 static int securid_authenticate(void *instance, REQUEST *request)
468 rlm_securid_t *inst = instance;
469 VALUE_PAIR *module_fmsg_vp;
471 char buffer[MAX_STRING_LEN]="";
472 const char *username=NULL, *password=NULL;
473 char module_fmsg[MAX_STRING_LEN]="";
476 * We can only authenticate user requests which HAVE
477 * a User-Name attribute.
479 if (!request->username) {
480 radlog(L_AUTH, "rlm_securid: Attribute \"User-Name\" is required for authentication.");
481 return RLM_MODULE_INVALID;
484 if (!request->password) {
485 radlog_request(L_AUTH, 0, request, "Attribute \"Password\" is required for authentication.");
486 return RLM_MODULE_INVALID;
490 * Clear-text passwords are the only ones we support.
492 if (request->password->da->attr != PW_USER_PASSWORD) {
493 radlog_request(L_AUTH, 0, request, "Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->da->name);
494 return RLM_MODULE_INVALID;
498 * The user MUST supply a non-zero-length password.
500 if (request->password->length == 0) {
501 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_securid: empty password supplied");
502 module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
503 pairadd(&request->packet->vps, module_fmsg_vp);
504 return RLM_MODULE_INVALID;
510 username = request->username->vp_strvalue;
511 password = request->password->vp_strvalue;
513 RDEBUG("User [%s] login attempt with password [%s]",
516 rcode = securidAuth(inst, request, username, password,
517 buffer, sizeof(buffer));
520 case RC_SECURID_AUTH_SUCCESS:
521 rcode = RLM_MODULE_OK;
524 case RC_SECURID_AUTH_CHALLENGE:
525 /* reply with Access-challenge message code (11) */
527 /* Generate Prompt attribute */
528 vp = paircreate(PW_PROMPT, 0);
530 rad_assert(vp != NULL);
531 vp->vp_integer = 0; /* no echo */
532 pairadd(&request->reply->vps, vp);
534 /* Mark the packet as a Acceess-Challenge Packet */
535 request->reply->code = PW_ACCESS_CHALLENGE;
536 RDEBUG("Sending Access-Challenge.");
537 rcode = RLM_MODULE_HANDLED;
540 case RC_SECURID_AUTH_FAILURE:
541 case RC_SECURID_AUTH_ACCESS_DENIED_FAILURE:
542 case RC_SECURID_AUTH_INVALID_SERVER_FAILURE:
544 rcode = RLM_MODULE_REJECT;
549 /* Generate Reply-Message attribute with reply message data */
550 vp = pairmake("Reply-Message", buffer, T_OP_EQ);
552 /* make sure message ends with '\0' */
553 if (vp->length < (int) sizeof(vp->vp_strvalue)) {
554 vp->vp_strvalue[vp->length] = '\0';
557 pairadd(&request->reply->vps,vp);
564 * The module name should be the only globally exported symbol.
565 * That is, everything else should be 'static'.
567 * If the module needs to temporarily modify it's instantiation
568 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
569 * The server will then take care of ensuring that the module
570 * is single-threaded.
572 module_t rlm_securid = {
575 RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE, /* type */
576 securid_instantiate, /* instantiation */
577 securid_detach, /* detach */
579 securid_authenticate, /* authentication */
580 NULL, /* authorization */
581 NULL, /* preaccounting */
582 NULL, /* accounting */
583 NULL, /* checksimul */
584 NULL, /* pre-proxy */
585 NULL, /* post-proxy */