81a39b536d68e48b9a5b40e72bd2acd0d1f14e04
[freeradius.git] / src / modules / rlm_securid / rlm_securid.c
1 /*
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.
5  *
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.
10  *
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
14  */
15
16 /**
17  * $Id$
18  * @file rlm_securid.c
19  * @brief Supports auth against SecurID servers using OTP h/w tokens.
20  *
21  * Supports "next-token code" and "new-pin" modes.
22  *
23  * @copyright 2012  The FreeRADIUS server project
24  * @copyright 2012  Alan DeKok <aland@networkradius.com>
25  */
26 #include <freeradius-devel/radiusd.h>
27 #include <freeradius-devel/modules.h>
28 #include <ctype.h>
29
30 #include "rlm_securid.h"
31
32 typedef enum {
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
38 } SECURID_AUTH_RC;
39
40
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 */
47 };
48
49
50 static SD_CHAR empty_pin[] = "";
51
52 /* comparison function to find session in the tree */
53 static int securid_session_cmp(void const *a, void const *b)
54 {
55         int rcode;
56         SECURID_SESSION const *one = a;
57         SECURID_SESSION const *two = b;
58
59         rad_assert(one != NULL);
60         rad_assert(two != NULL);
61
62         rcode = fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr);
63         if (rcode != 0) return rcode;
64
65         return memcmp(one->state, two->state, sizeof(one->state));
66 }
67
68
69 static SECURID_AUTH_RC securidAuth(void *instance, REQUEST *request,
70                                    char const *username,
71                                    char const *passcode,
72                                    char *replyMsgBuffer, size_t replyMsgBufferSize)
73 {
74         rlm_securid_t *inst = (rlm_securid_t *) instance;
75         int acm_ret;
76         SD_PIN pin_params;
77         char new_pin[10];
78         char format[30];
79         SECURID_SESSION *securid_session = NULL;
80         int rc = -1;
81
82         SD_CHAR *securid_user, *securid_pass;
83
84         if (!username) {
85                 ERROR("SecurID username is NULL");
86                 return RC_SECURID_AUTH_FAILURE;
87         }
88
89         if (!passcode) {
90                 ERROR("SecurID passcode is NULL for %s user", username);
91                 return RC_SECURID_AUTH_FAILURE;
92         }
93
94         memcpy(&securid_user, &username, sizeof(securid_user));
95         memcpy(&securid_pass, &passcode, sizeof(securid_pass));
96
97         *replyMsgBuffer = '\0';
98
99         securid_session = securid_sessionlist_find(inst, request);
100         if (!securid_session) {
101                 /* securid session not found */
102                 SDI_HANDLE sdiHandle = SDI_HANDLE_NONE;
103
104                 acm_ret = SD_Init(&sdiHandle);
105                 if (acm_ret != ACM_OK) {
106                         ERROR("Cannot communicate with the ACE/Server");
107                         return -1;
108                 }
109
110                 acm_ret = SD_Lock(sdiHandle, securid_user);
111                 if (acm_ret != ACM_OK) {
112                         ERROR("SecurID: Access denied. Name [%s] lock failed", username);
113                         return -2;
114                 }
115
116                 acm_ret = SD_Check(sdiHandle, securid_pass, securid_user);
117                 switch (acm_ret) {
118                 case ACM_OK:
119                         /* we are in now */
120                         RDEBUG("SecurID authentication successful for %s", username);
121                         SD_Close(sdiHandle);
122
123                         return RC_SECURID_AUTH_SUCCESS;
124
125                 case ACM_ACCESS_DENIED:
126                         /* not this time */
127                         RDEBUG("SecurID Access denied for %s", username);
128                         SD_Close(sdiHandle);
129                         return RC_SECURID_AUTH_ACCESS_DENIED_FAILURE;
130
131                 case ACM_INVALID_SERVER:
132                         ERROR("SecurID: Invalid ACE server");
133                         return RC_SECURID_AUTH_INVALID_SERVER_FAILURE;
134
135                 case ACM_NEW_PIN_REQUIRED:
136                         RDEBUG2("SecurID new pin required for %s", username);
137
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);
143
144                         /* Get PIN requirements */
145                         (void) AceGetPinParams(sdiHandle, &pin_params);
146
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;
153
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;
158
159                         } else {
160                                 if (pin_params.Alphanumeric) {
161                                         strcpy(format, "alphanumeric characters");
162                                 } else {
163                                         strcpy(format, "digits");
164                                 }
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);
168                         }
169
170                         /* insert new session in the session list */
171                         securid_sessionlist_add(inst, request, securid_session);
172
173                         return RC_SECURID_AUTH_CHALLENGE;
174
175                 case ACM_NEXT_CODE_REQUIRED:
176                         RDEBUG2("Next securid token code required for %s",
177                                 username);
178
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);
184
185                         /* insert new session in the session list */
186                         securid_sessionlist_add(inst, request, securid_session);
187
188                         strlcpy(replyMsgBuffer, "\r\nPlease Enter the Next Code from Your Token:", replyMsgBufferSize);
189                         return RC_SECURID_AUTH_CHALLENGE;
190
191                 default:
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;
195
196
197                 }
198         } else {
199                 /* existing session found */
200                 RDEBUG("Continuing previous session found for user [%s]", username);
201
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 */
207
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;
212
213                         } else {
214                                 INFO("SecurID: Next token rejected for [%s].", securid_session->identity);
215                                 rc = RC_SECURID_AUTH_FAILURE;
216                         }
217
218                         /* deallocate session */
219                         securid_session_free(inst, request, securid_session);
220                         return rc;
221
222                 case NEW_PIN_REQUIRED_STATE:
223                         RDEBUG2("SecurID NEW_PIN_REQUIRED_STATE for %s",
224                                 username);
225
226                         /* save the previous pin */
227                         if (securid_session->pin) {
228                                 free(securid_session->pin);
229                                 securid_session->pin = NULL;
230                         }
231                         securid_session->pin = strdup(passcode);
232
233                         strlcpy(replyMsgBuffer, "\r\n            Please re-enter new PIN:", replyMsgBufferSize);
234
235                         /* set next state */
236                         securid_session->securidSessionState = NEW_PIN_USER_CONFIRM_STATE;
237
238                         /* insert the updated session in the session list */
239                         securid_sessionlist_add(inst, request, securid_session);
240                         return RC_SECURID_AUTH_CHALLENGE;
241
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 */
249
250                                 /* challenge the user again */
251                                 AceGetPinParams(securid_session->sdiHandle, &pin_params);
252                                 if (pin_params.Alphanumeric) {
253                                         strcpy(format, "alphanumeric characters");
254                                 } else {
255                                         strcpy(format, "digits");
256                                 }
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);
260
261                                 securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
262
263                                 /* insert the updated session in the session list */
264                                 securid_sessionlist_add(inst, request, securid_session);
265                                 rc = RC_SECURID_AUTH_CHALLENGE;
266
267                         } else {
268                                 /* pins match */
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);
273
274                                         securid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
275
276                                         /* insert the updated session in the session list */
277                                         securid_sessionlist_add(inst, request, securid_session);
278
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);
281                                 } else {
282                                         RDEBUG("SecurID: New SecurID pin rejected for %s.", securid_session->identity);
283                                         SD_Pin(securid_session->sdiHandle, &empty_pin[0]);  /* cancel PIN */
284
285
286                                         rc = RC_SECURID_AUTH_FAILURE;
287
288                                         /* deallocate session */
289                                         securid_session_free(inst, request, securid_session);
290                                 }
291                         }
292                         return rc;
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;
299
300                         } else {
301                                 INFO("SecurID: New passcode rejected for [%s].", securid_session->identity);
302                                 rc = RC_SECURID_AUTH_FAILURE;
303                         }
304
305                         /* deallocate session */
306                         securid_session_free(inst, request, securid_session);
307
308                         return rc;
309                 case NEW_PIN_SYSTEM_ACCEPT_STATE:
310                         if (!strcmp(passcode, "y")) {
311                                 AceGetSystemPin(securid_session->sdiHandle, new_pin);
312
313                                 /* Save the PIN for the next session
314                                  * continuation */
315                                 if (securid_session->pin) {
316                                         free(securid_session->pin);
317                                         securid_session->pin = NULL;
318                                 }
319                                 securid_session->pin = strdup(new_pin);
320
321                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
322                                          "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
323                                          new_pin);
324                                 securid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
325
326                                 /* insert the updated session in the
327                                  * session list */
328                                 securid_sessionlist_add(inst, request, securid_session);
329
330                                 rc = RC_SECURID_AUTH_CHALLENGE;
331
332                         } else {
333                                 SD_Pin(securid_session->sdiHandle, &empty_pin[0]); //Cancel new PIN
334
335                                 /* deallocate session */
336                                 securid_session_free(inst, request,
337                                                      securid_session);
338
339                                 rc = RC_SECURID_AUTH_FAILURE;
340                         }
341
342                         return rc;
343
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;
352
353                         } else {
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,
358                                                      securid_session);
359                                 rc = RC_SECURID_AUTH_FAILURE;
360                         }
361
362                         return rc;
363
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]?",
371                                          new_pin);
372                                 securid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
373
374                                 /* insert the updated session in the session list */
375                                 securid_sessionlist_add(inst, request,
376                                                         securid_session);
377                                 rc = RC_SECURID_AUTH_CHALLENGE;
378
379                         } else {
380                                 /* User has opted for a user-defined PIN */
381                                 AceGetPinParams(securid_session->sdiHandle,
382                                                 &pin_params);
383                                 if (pin_params.Alphanumeric) {
384                                         strcpy(format, "alphanumeric characters");
385                                 } else {
386                                         strcpy(format, "digits");
387                                 }
388
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;
393
394                                 /* insert the updated session in the session list */
395                                 securid_sessionlist_add(inst, request,
396                                                         securid_session);
397                                 rc = RC_SECURID_AUTH_CHALLENGE;
398                         }
399
400                         return rc;
401
402                 default:
403                         ERROR("rlm_securid: Invalid session state %d for user [%s]",
404                                securid_session->securidSessionState,
405                                username);
406                         break;
407                 }
408         }
409
410         return 0;
411
412 }
413
414 /******************************************/
415 static int mod_detach(void *instance)
416 {
417         rlm_securid_t *inst = (rlm_securid_t *) instance;
418
419         /* delete session tree */
420         if (inst->session_tree) {
421                 rbtree_free(inst->session_tree);
422                 inst->session_tree = NULL;
423         }
424
425         pthread_mutex_destroy(&(inst->session_mutex));
426
427         return 0;
428 }
429
430
431 static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
432 {
433         rlm_securid_t *inst = instance;
434
435         /*
436          *      Lookup sessions in the tree.  We don't free them in
437          *      the tree, as that's taken care of elsewhere...
438          */
439         inst->session_tree = rbtree_create(inst, securid_session_cmp, NULL, 0);
440         if (!inst->session_tree) {
441                 ERROR("rlm_securid: Cannot initialize session tree");
442                 return -1;
443         }
444
445         pthread_mutex_init(&(inst->session_mutex), NULL);
446         return 0;
447 }
448
449
450 /*
451  *      Authenticate the user via one of any well-known password.
452  */
453 static rlm_rcode_t CC_HINT(nonnull) mod_authenticate(void *instance, REQUEST *request)
454 {
455         int rcode;
456         rlm_securid_t *inst = instance;
457         char  buffer[MAX_STRING_LEN]="";
458         char const *username=NULL, *password=NULL;
459         VALUE_PAIR *vp;
460
461         /*
462          *      We can only authenticate user requests which HAVE
463          *      a User-Name attribute.
464          */
465         if (!request->username) {
466                 AUTH("rlm_securid: Attribute \"User-Name\" is required for authentication");
467                 return RLM_MODULE_INVALID;
468         }
469
470         if (!request->password) {
471                 RAUTH("Attribute \"Password\" is required for authentication");
472                 return RLM_MODULE_INVALID;
473         }
474
475         /*
476          *      Clear-text passwords are the only ones we support.
477          */
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;
481         }
482
483         /*
484          *      The user MUST supply a non-zero-length password.
485          */
486         if (request->password->length == 0) {
487                 REDEBUG("Password should not be empty");
488                 return RLM_MODULE_INVALID;
489         }
490
491         /*
492          *      shortcuts
493          */
494         username = request->username->vp_strvalue;
495         password = request->password->vp_strvalue;
496
497         if (RDEBUG_ENABLED3) {
498                 RDEBUG3("Login attempt with password \"%s\"", password);
499         } else {
500                 RDEBUG("Login attempt with password");
501         }
502
503         rcode = securidAuth(inst, request, username, password,
504                             buffer, sizeof(buffer));
505
506         switch (rcode) {
507         case RC_SECURID_AUTH_SUCCESS:
508                 rcode = RLM_MODULE_OK;
509                 break;
510
511         case RC_SECURID_AUTH_CHALLENGE:
512                 /* reply with Access-challenge message code (11) */
513
514                 /* Generate Prompt attribute */
515                 vp = paircreate(request->reply, PW_PROMPT, 0);
516
517                 rad_assert(vp != NULL);
518                 vp->vp_integer = 0; /* no echo */
519                 pairadd(&request->reply->vps, vp);
520
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;
525                 break;
526
527         case RC_SECURID_AUTH_FAILURE:
528         case RC_SECURID_AUTH_ACCESS_DENIED_FAILURE:
529         case RC_SECURID_AUTH_INVALID_SERVER_FAILURE:
530         default:
531                 rcode = RLM_MODULE_REJECT;
532                 break;
533         }
534
535         if (*buffer) pairmake_reply("Reply-Message", buffer, T_OP_EQ);
536
537         return rcode;
538 }
539
540
541 /*
542  *      The module name should be the only globally exported symbol.
543  *      That is, everything else should be 'static'.
544  *
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.
549  */
550 module_t rlm_securid = {
551         RLM_MODULE_INIT,
552         "securid",
553         RLM_TYPE_HUP_SAFE,      /* type */
554         sizeof(rlm_securid_t),
555         module_config,
556         mod_instantiate,                /* instantiation */
557         mod_detach,                     /* detach */
558         {
559                 mod_authenticate,       /* authentication */
560                 NULL,                   /* authorization */
561                 NULL,                   /* preaccounting */
562                 NULL,                   /* accounting */
563                 NULL,                   /* checksimul */
564                 NULL,                   /* pre-proxy */
565                 NULL,                   /* post-proxy */
566                 NULL                    /* post-auth */
567         },
568 };