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", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_securid_t, timer_limit), "600" },
43 { "max_sessions", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_securid_t, max_sessions), "2048" },
44 { "max_trips_per_session", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_securid_t, max_trips_per_session), NULL },
45 { "max_round_trips", FR_CONF_OFFSET(PW_TYPE_INTEGER, rlm_securid_t, max_trips_per_session), "6" },
46 { NULL, -1, 0, NULL, NULL } /* end the list */
50 static SD_CHAR empty_pin[] = "";
52 /* comparison function to find session in the tree */
53 static int securid_session_cmp(void const *a, void const *b)
56 SECURID_SESSION const *one = a;
57 SECURID_SESSION const *two = b;
59 rad_assert(one != NULL);
60 rad_assert(two != NULL);
62 rcode = fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr);
63 if (rcode != 0) return rcode;
65 return memcmp(one->state, two->state, sizeof(one->state));
69 static SECURID_AUTH_RC securidAuth(void *instance, REQUEST *request,
72 char *replyMsgBuffer, size_t replyMsgBufferSize)
74 rlm_securid_t *inst = (rlm_securid_t *) instance;
79 SECURID_SESSION *securid_session = NULL;
82 SD_CHAR *securid_user, *securid_pass;
85 ERROR("SecurID username is NULL");
86 return RC_SECURID_AUTH_FAILURE;
90 ERROR("SecurID passcode is NULL for %s user", username);
91 return RC_SECURID_AUTH_FAILURE;
94 memcpy(&securid_user, &username, sizeof(securid_user));
95 memcpy(&securid_pass, &passcode, sizeof(securid_pass));
97 *replyMsgBuffer = '\0';
99 securid_session = securid_sessionlist_find(inst, request);
100 if (!securid_session) {
101 /* securid session not found */
102 SDI_HANDLE sdiHandle = SDI_HANDLE_NONE;
104 acm_ret = SD_Init(&sdiHandle);
105 if (acm_ret != ACM_OK) {
106 ERROR("Cannot communicate with the ACE/Server");
110 acm_ret = SD_Lock(sdiHandle, securid_user);
111 if (acm_ret != ACM_OK) {
112 ERROR("SecurID: Access denied. Name [%s] lock failed", username);
116 acm_ret = SD_Check(sdiHandle, securid_pass, securid_user);
120 RDEBUG("SecurID authentication successful for %s", username);
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 ERROR("SecurID: Invalid ACE server");
133 return RC_SECURID_AUTH_INVALID_SERVER_FAILURE;
135 case ACM_NEW_PIN_REQUIRED:
136 RDEBUG2("SecurID new pin required for %s", username);
138 /* create a new session */
139 securid_session = securid_session_alloc();
140 securid_session->sdiHandle = sdiHandle; /* save ACE handle for future use */
141 securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
142 securid_session->identity = strdup(username);
144 /* Get PIN requirements */
145 (void) AceGetPinParams(sdiHandle, &pin_params);
147 /* If a system-generated PIN is required */
148 if (pin_params.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 securid_session->securidSessionState = NEW_PIN_SYSTEM_ACCEPT_STATE;
154 } else if (pin_params.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 securid_session->securidSessionState = NEW_PIN_USER_SELECT_STATE;
160 if (pin_params.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 pin_params.Min, pin_params.Max, format);
170 /* insert new session in the session list */
171 securid_sessionlist_add(inst, request, securid_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 securid_session = securid_session_alloc();
181 securid_session->sdiHandle = sdiHandle;
182 securid_session->securidSessionState = NEXT_CODE_REQUIRED_STATE;
183 securid_session->identity = strdup(username);
185 /* insert new session in the session list */
186 securid_sessionlist_add(inst, request, securid_session);
188 strlcpy(replyMsgBuffer, "\r\nPlease Enter the Next Code from Your Token:", replyMsgBufferSize);
189 return RC_SECURID_AUTH_CHALLENGE;
192 ERROR("SecurID: Unexpected error from ACE/Agent API acm_ret=%d", acm_ret);
193 securid_session_free(inst, request, securid_session);
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 (securid_session->securidSessionState) {
204 case NEXT_CODE_REQUIRED_STATE:
205 DEBUG2("Securid NEXT_CODE_REQUIRED_STATE: User [%s]", username);
206 /* next token code mode */
208 acm_ret = SD_Next(securid_session->sdiHandle, securid_pass);
209 if (acm_ret == ACM_OK) {
210 INFO("Next SecurID token accepted for [%s].", securid_session->identity);
211 rc = RC_SECURID_AUTH_SUCCESS;
214 INFO("SecurID: Next token rejected for [%s].", securid_session->identity);
215 rc = RC_SECURID_AUTH_FAILURE;
218 /* deallocate session */
219 securid_session_free(inst, request, securid_session);
222 case NEW_PIN_REQUIRED_STATE:
223 RDEBUG2("SecurID NEW_PIN_REQUIRED_STATE for %s",
226 /* save the previous pin */
227 if (securid_session->pin) {
228 free(securid_session->pin);
229 securid_session->pin = NULL;
231 securid_session->pin = strdup(passcode);
233 strlcpy(replyMsgBuffer, "\r\n Please re-enter new PIN:", replyMsgBufferSize);
236 securid_session->securidSessionState = NEW_PIN_USER_CONFIRM_STATE;
238 /* insert the updated session in the session list */
239 securid_sessionlist_add(inst, request, securid_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 (!securid_session->pin || strcmp(securid_session->pin, passcode)) {
246 RDEBUG2("Pin confirmation failed. Pins do not match [%s] and [%s]",
247 SAFE_STR(securid_session->pin), securid_pass);
248 /* pins do not match */
250 /* challenge the user again */
251 AceGetPinParams(securid_session->sdiHandle, &pin_params);
252 if (pin_params.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 pin_params.Min, pin_params.Max, format);
261 securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
263 /* insert the updated session in the session list */
264 securid_sessionlist_add(inst, request, securid_session);
265 rc = RC_SECURID_AUTH_CHALLENGE;
269 RDEBUG2("Pin confirmation succeeded. Pins match");
270 acm_ret = SD_Pin(securid_session->sdiHandle, securid_pass);
271 if (acm_ret == ACM_NEW_PIN_ACCEPTED) {
272 RDEBUG("New SecurID pin accepted for %s.", securid_session->identity);
274 securid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
276 /* insert the updated session in the session list */
277 securid_sessionlist_add(inst, request, securid_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.", securid_session->identity);
283 SD_Pin(securid_session->sdiHandle, &empty_pin[0]); /* cancel PIN */
286 rc = RC_SECURID_AUTH_FAILURE;
288 /* deallocate session */
289 securid_session_free(inst, request, securid_session);
293 case NEW_PIN_AUTH_VALIDATE_STATE:
294 acm_ret = SD_Check(securid_session->sdiHandle, securid_pass, securid_user);
295 if (acm_ret == ACM_OK) {
296 RDEBUG("New SecurID passcode accepted for %s.",
297 securid_session->identity);
298 rc = RC_SECURID_AUTH_SUCCESS;
301 INFO("SecurID: New passcode rejected for [%s].", securid_session->identity);
302 rc = RC_SECURID_AUTH_FAILURE;
305 /* deallocate session */
306 securid_session_free(inst, request, securid_session);
309 case NEW_PIN_SYSTEM_ACCEPT_STATE:
310 if (!strcmp(passcode, "y")) {
311 AceGetSystemPin(securid_session->sdiHandle, new_pin);
313 /* Save the PIN for the next session
315 if (securid_session->pin) {
316 free(securid_session->pin);
317 securid_session->pin = NULL;
319 securid_session->pin = strdup(new_pin);
321 snprintf(replyMsgBuffer, replyMsgBufferSize,
322 "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
324 securid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
326 /* insert the updated session in the
328 securid_sessionlist_add(inst, request, securid_session);
330 rc = RC_SECURID_AUTH_CHALLENGE;
333 SD_Pin(securid_session->sdiHandle, &empty_pin[0]); //Cancel new PIN
335 /* deallocate session */
336 securid_session_free(inst, request,
339 rc = RC_SECURID_AUTH_FAILURE;
344 case NEW_PIN_SYSTEM_CONFIRM_STATE:
345 acm_ret = SD_Pin(securid_session->sdiHandle, (SD_CHAR*)securid_session->pin);
346 if (acm_ret == ACM_NEW_PIN_ACCEPTED) {
347 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);
348 securid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
349 /* insert the updated session in the session list */
350 securid_sessionlist_add(inst, request, securid_session);
351 rc = RC_SECURID_AUTH_CHALLENGE;
354 SD_Pin(securid_session->sdiHandle, &empty_pin[0]); //Cancel new PIN
355 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);
356 /* deallocate session */
357 securid_session_free(inst, request,
359 rc = RC_SECURID_AUTH_FAILURE;
364 /* USER_SELECTABLE state should be implemented to preserve compatibility with AM 6.x servers, which can return this state */
365 case NEW_PIN_USER_SELECT_STATE:
366 if (!strcmp(passcode, "y")) {
367 /* User has opted for a system-generated PIN */
368 AceGetSystemPin(securid_session->sdiHandle, new_pin);
369 snprintf(replyMsgBuffer, replyMsgBufferSize,
370 "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
372 securid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
374 /* insert the updated session in the session list */
375 securid_sessionlist_add(inst, request,
377 rc = RC_SECURID_AUTH_CHALLENGE;
380 /* User has opted for a user-defined PIN */
381 AceGetPinParams(securid_session->sdiHandle,
383 if (pin_params.Alphanumeric) {
384 strcpy(format, "alphanumeric characters");
386 strcpy(format, "digits");
389 snprintf(replyMsgBuffer, replyMsgBufferSize,
390 " \r\n Enter your new PIN of %d to %d %s, \r\n or\r\n <Ctrl-D> to cancel the New PIN procedure:",
391 pin_params.Min, pin_params.Max, format);
392 securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
394 /* insert the updated session in the session list */
395 securid_sessionlist_add(inst, request,
397 rc = RC_SECURID_AUTH_CHALLENGE;
403 ERROR("rlm_securid: Invalid session state %d for user [%s]",
404 securid_session->securidSessionState,
414 /******************************************/
415 static int mod_detach(void *instance)
417 rlm_securid_t *inst = (rlm_securid_t *) instance;
419 /* delete session tree */
420 if (inst->session_tree) {
421 rbtree_free(inst->session_tree);
422 inst->session_tree = NULL;
425 pthread_mutex_destroy(&(inst->session_mutex));
431 static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
433 rlm_securid_t *inst = instance;
436 * Lookup sessions in the tree. We don't free them in
437 * the tree, as that's taken care of elsewhere...
439 inst->session_tree = rbtree_create(NULL, securid_session_cmp, NULL, 0);
440 if (!inst->session_tree) {
441 ERROR("rlm_securid: Cannot initialize session tree");
445 pthread_mutex_init(&(inst->session_mutex), NULL);
451 * Authenticate the user via one of any well-known password.
453 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
456 rlm_securid_t *inst = instance;
457 char buffer[MAX_STRING_LEN]="";
458 char const *username=NULL, *password=NULL;
462 * We can only authenticate user requests which HAVE
463 * a User-Name attribute.
465 if (!request->username) {
466 AUTH("rlm_securid: Attribute \"User-Name\" is required for authentication");
467 return RLM_MODULE_INVALID;
470 if (!request->password) {
471 RAUTH("Attribute \"Password\" is required for authentication");
472 return RLM_MODULE_INVALID;
476 * Clear-text passwords are the only ones we support.
478 if (request->password->da->attr != PW_USER_PASSWORD) {
479 RAUTH("Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->da->name);
480 return RLM_MODULE_INVALID;
484 * The user MUST supply a non-zero-length password.
486 if (request->password->length == 0) {
487 REDEBUG("Password should not be empty");
488 return RLM_MODULE_INVALID;
494 username = request->username->vp_strvalue;
495 password = request->password->vp_strvalue;
497 if (RDEBUG_ENABLED3) {
498 RDEBUG3("Login attempt with password \"%s\"", password);
500 RDEBUG("Login attempt with password");
503 rcode = securidAuth(inst, request, username, password,
504 buffer, sizeof(buffer));
507 case RC_SECURID_AUTH_SUCCESS:
508 rcode = RLM_MODULE_OK;
511 case RC_SECURID_AUTH_CHALLENGE:
512 /* reply with Access-challenge message code (11) */
514 /* Generate Prompt attribute */
515 vp = paircreate(request->reply, PW_PROMPT, 0);
517 rad_assert(vp != NULL);
518 vp->vp_integer = 0; /* no echo */
519 pairadd(&request->reply->vps, vp);
521 /* Mark the packet as a Acceess-Challenge Packet */
522 request->reply->code = PW_CODE_ACCESS_CHALLENGE;
523 RDEBUG("Sending Access-Challenge");
524 rcode = RLM_MODULE_HANDLED;
527 case RC_SECURID_AUTH_FAILURE:
528 case RC_SECURID_AUTH_ACCESS_DENIED_FAILURE:
529 case RC_SECURID_AUTH_INVALID_SERVER_FAILURE:
531 rcode = RLM_MODULE_REJECT;
535 if (*buffer) pairmake_reply("Reply-Message", buffer, T_OP_EQ);
542 * The module name should be the only globally exported symbol.
543 * That is, everything else should be 'static'.
545 * If the module needs to temporarily modify it's instantiation
546 * data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
547 * The server will then take care of ensuring that the module
548 * is single-threaded.
550 module_t rlm_securid = {
553 RLM_TYPE_HUP_SAFE, /* type */
554 sizeof(rlm_securid_t),
556 mod_instantiate, /* instantiation */
557 mod_detach, /* detach */
559 mod_authenticate, /* authentication */
560 NULL, /* authorization */
561 NULL, /* preaccounting */
562 NULL, /* accounting */
563 NULL, /* checksimul */
564 NULL, /* pre-proxy */
565 NULL, /* post-proxy */