Fix more places where passwords are revealed in debugging messages
[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", 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 */
51 };
52
53
54 static SD_CHAR empty_pin[] = "";
55
56 /* comparison function to find session in the tree */
57 static int securid_session_cmp(void const *a, void const *b)
58 {
59         int rcode;
60         SECURID_SESSION const *one = a;
61         SECURID_SESSION const *two = b;
62
63         rad_assert(one != NULL);
64         rad_assert(two != NULL);
65
66         rcode = fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr);
67         if (rcode != 0) return rcode;
68
69         return memcmp(one->state, two->state, sizeof(one->state));
70 }
71
72
73 static SECURID_AUTH_RC securidAuth(void *instance, REQUEST *request,
74                                    char const *username,
75                                    char const *passcode,
76                                    char *replyMsgBuffer, size_t replyMsgBufferSize)
77 {
78         rlm_securid_t *inst = (rlm_securid_t *) instance;
79         int acm_ret;
80         SD_PIN pin_params;
81         char new_pin[10];
82         char format[30];
83         SECURID_SESSION *securid_session = NULL;
84         int rc = -1;
85
86         SD_CHAR *securid_user, *securid_pass;
87
88         if (!username) {
89                 ERROR("SecurID username is NULL");
90                 return RC_SECURID_AUTH_FAILURE;
91         }
92
93         if (!passcode) {
94                 ERROR("SecurID passcode is NULL for %s user", username);
95                 return RC_SECURID_AUTH_FAILURE;
96         }
97
98         memcpy(&securid_user, &username, sizeof(securid_user));
99         memcpy(&securid_pass, &passcode, sizeof(securid_pass));
100
101         *replyMsgBuffer = '\0';
102
103         securid_session = securid_sessionlist_find(inst, request);
104         if (!securid_session) {
105                 /* securid session not found */
106                 SDI_HANDLE sdiHandle = SDI_HANDLE_NONE;
107
108                 acm_ret = SD_Init(&sdiHandle);
109                 if (acm_ret != ACM_OK) {
110                         ERROR("Cannot communicate with the ACE/Server");
111                         return -1;
112                 }
113
114                 acm_ret = SD_Lock(sdiHandle, securid_user);
115                 if (acm_ret != ACM_OK) {
116                         ERROR("SecurID: Access denied. Name [%s] lock failed", username);
117                         return -2;
118                 }
119
120                 acm_ret = SD_Check(sdiHandle, securid_pass, securid_user);
121                 switch (acm_ret) {
122                 case ACM_OK:
123                         /* we are in now */
124                         RDEBUG("SecurID authentication successful for %s", username);
125                         SD_Close(sdiHandle);
126
127                         return RC_SECURID_AUTH_SUCCESS;
128
129                 case ACM_ACCESS_DENIED:
130                         /* not this time */
131                         RDEBUG("SecurID Access denied for %s", username);
132                         SD_Close(sdiHandle);
133                         return RC_SECURID_AUTH_ACCESS_DENIED_FAILURE;
134
135                 case ACM_INVALID_SERVER:
136                         ERROR("SecurID: Invalid ACE server");
137                         return RC_SECURID_AUTH_INVALID_SERVER_FAILURE;
138
139                 case ACM_NEW_PIN_REQUIRED:
140                         RDEBUG2("SecurID new pin required for %s", username);
141
142                         /* create a new session */
143                         securid_session = securid_session_alloc();
144                         securid_session->sdiHandle = sdiHandle; /* save ACE handle for future use */
145                         securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
146                         securid_session->identity = strdup(username);
147
148                         /* Get PIN requirements */
149                         acm_ret = AceGetPinParams(sdiHandle, &pin_params);
150
151                         /* If a system-generated PIN is required */
152                         if (pin_params.Selectable == CANNOT_CHOOSE_PIN) {
153                                 /* Prompt user to accept a system generated PIN */
154                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
155                                          "\r\nAre you prepared to accept a new system-generated PIN [y/n]?");
156                                 securid_session->securidSessionState = NEW_PIN_SYSTEM_ACCEPT_STATE;
157
158                         } else if (pin_params.Selectable == USER_SELECTABLE) { //may be returned by AM 6.x servers.
159                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
160                                          "\r\nPress 'y' to generate a new PIN\r\nOR\r\n'n'to enter a new PIN yourself [y/n]");
161                                 securid_session->securidSessionState = NEW_PIN_USER_SELECT_STATE;
162
163                         } else {
164                                 if (pin_params.Alphanumeric) {
165                                         strcpy(format, "alphanumeric characters");
166                                 } else {
167                                         strcpy(format, "digits");
168                                 }
169                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
170                                          " \r\n   Enter your new PIN of %d to %d %s, \r\n               or\r\n   <Ctrl-D> to cancel the New PIN procedure:",
171                                          pin_params.Min, pin_params.Max, format);
172                         }
173
174                         /* insert new session in the session list */
175                         securid_sessionlist_add(inst, request, securid_session);
176
177                         return RC_SECURID_AUTH_CHALLENGE;
178
179                 case ACM_NEXT_CODE_REQUIRED:
180                         RDEBUG2("Next securid token code required for %s",
181                                 username);
182
183                         /* create a new session */
184                         securid_session = securid_session_alloc();
185                         securid_session->sdiHandle = sdiHandle;
186                         securid_session->securidSessionState = NEXT_CODE_REQUIRED_STATE;
187                         securid_session->identity = strdup(username);
188
189                         /* insert new session in the session list */
190                         securid_sessionlist_add(inst, request, securid_session);
191
192                         strlcpy(replyMsgBuffer, "\r\nPlease Enter the Next Code from Your Token:", replyMsgBufferSize);
193                         return RC_SECURID_AUTH_CHALLENGE;
194
195                 default:
196                         ERROR("SecurID: Unexpected error from ACE/Agent API acm_ret=%d", acm_ret);
197                         securid_session_free(inst, request, securid_session);
198                         return RC_SECURID_AUTH_FAILURE;
199
200
201                 }
202         } else {
203                 /* existing session found */
204                 RDEBUG("Continuing previous session found for user [%s]", username);
205
206                 /* continue previous session */
207                 switch (securid_session->securidSessionState) {
208                 case NEXT_CODE_REQUIRED_STATE:
209                         DEBUG2("Securid NEXT_CODE_REQUIRED_STATE: User [%s]", username);
210                         /* next token code mode */
211
212                         acm_ret = SD_Next(securid_session->sdiHandle, securid_pass);
213                         if (acm_ret == ACM_OK) {
214                                 INFO("Next SecurID token accepted for [%s].", securid_session->identity);
215                                 rc = RC_SECURID_AUTH_SUCCESS;
216
217                         } else {
218                                 INFO("SecurID: Next token rejected for [%s].", securid_session->identity);
219                                 rc = RC_SECURID_AUTH_FAILURE;
220                         }
221
222                         /* deallocate session */
223                         securid_session_free(inst, request, securid_session);
224                         return rc;
225
226                 case NEW_PIN_REQUIRED_STATE:
227                         RDEBUG2("SecurID NEW_PIN_REQUIRED_STATE for %s",
228                                 username);
229
230                         /* save the previous pin */
231                         if (securid_session->pin) {
232                                 free(securid_session->pin);
233                                 securid_session->pin = NULL;
234                         }
235                         securid_session->pin = strdup(passcode);
236
237                         strlcpy(replyMsgBuffer, "\r\n            Please re-enter new PIN:", replyMsgBufferSize);
238
239                         /* set next state */
240                         securid_session->securidSessionState = NEW_PIN_USER_CONFIRM_STATE;
241
242                         /* insert the updated session in the session list */
243                         securid_sessionlist_add(inst, request, securid_session);
244                         return RC_SECURID_AUTH_CHALLENGE;
245
246                 case NEW_PIN_USER_CONFIRM_STATE:
247                         RDEBUG2("SecurID NEW_PIN_USER_CONFIRM_STATE: User [%s]", username);
248                         /* compare previous pin and current pin */
249                         if (!securid_session->pin || strcmp(securid_session->pin, passcode)) {
250                                 RDEBUG2("Pin confirmation failed. Pins do not match [%s] and [%s]",
251                                        SAFE_STR(securid_session->pin), securid_pass);
252                                 /* pins do not match */
253
254                                 /* challenge the user again */
255                                 AceGetPinParams(securid_session->sdiHandle, &pin_params);
256                                 if (pin_params.Alphanumeric) {
257                                         strcpy(format, "alphanumeric characters");
258                                 } else {
259                                         strcpy(format, "digits");
260                                 }
261                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
262                                          " \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:",
263                                          pin_params.Min, pin_params.Max, format);
264
265                                 securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
266
267                                 /* insert the updated session in the session list */
268                                 securid_sessionlist_add(inst, request, securid_session);
269                                 rc = RC_SECURID_AUTH_CHALLENGE;
270
271                         } else {
272                                 /* pins match */
273                                 RDEBUG2("Pin confirmation succeeded. Pins match");
274                                 acm_ret = SD_Pin(securid_session->sdiHandle, securid_pass);
275                                 if (acm_ret == ACM_NEW_PIN_ACCEPTED) {
276                                         RDEBUG("New SecurID pin accepted for %s.", securid_session->identity);
277
278                                         securid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
279
280                                         /* insert the updated session in the session list */
281                                         securid_sessionlist_add(inst, request, securid_session);
282
283                                         rc = RC_SECURID_AUTH_CHALLENGE;
284                                         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);
285                                 } else {
286                                         RDEBUG("SecurID: New SecurID pin rejected for %s.", securid_session->identity);
287                                         SD_Pin(securid_session->sdiHandle, &empty_pin[0]);  /* cancel PIN */
288
289
290                                         rc = RC_SECURID_AUTH_FAILURE;
291
292                                         /* deallocate session */
293                                         securid_session_free(inst, request, securid_session);
294                                 }
295                         }
296                         return rc;
297                 case NEW_PIN_AUTH_VALIDATE_STATE:
298                         acm_ret = SD_Check(securid_session->sdiHandle, securid_pass, securid_user);
299                         if (acm_ret == ACM_OK) {
300                                 RDEBUG("New SecurID passcode accepted for %s.",
301                                        securid_session->identity);
302                                 rc = RC_SECURID_AUTH_SUCCESS;
303
304                         } else {
305                                 INFO("SecurID: New passcode rejected for [%s].", securid_session->identity);
306                                 rc = RC_SECURID_AUTH_FAILURE;
307                         }
308
309                         /* deallocate session */
310                         securid_session_free(inst, request, securid_session);
311
312                         return rc;
313                 case NEW_PIN_SYSTEM_ACCEPT_STATE:
314                         if (!strcmp(passcode, "y")) {
315                                 AceGetSystemPin(securid_session->sdiHandle, new_pin);
316
317                                 /* Save the PIN for the next session
318                                  * continuation */
319                                 if (securid_session->pin) {
320                                         free(securid_session->pin);
321                                         securid_session->pin = NULL;
322                                 }
323                                 securid_session->pin = strdup(new_pin);
324
325                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
326                                          "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
327                                          new_pin);
328                                 securid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
329
330                                 /* insert the updated session in the
331                                  * session list */
332                                 securid_sessionlist_add(inst, request, securid_session);
333
334                                 rc = RC_SECURID_AUTH_CHALLENGE;
335
336                         } else {
337                                 SD_Pin(securid_session->sdiHandle, &empty_pin[0]); //Cancel new PIN
338
339                                 /* deallocate session */
340                                 securid_session_free(inst, request,
341                                                      securid_session);
342
343                                 rc = RC_SECURID_AUTH_FAILURE;
344                         }
345
346                         return rc;
347
348                 case NEW_PIN_SYSTEM_CONFIRM_STATE:
349                         acm_ret = SD_Pin(securid_session->sdiHandle, (SD_CHAR*)securid_session->pin);
350                         if (acm_ret == 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                                 securid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
353                                 /* insert the updated session in the session list */
354                                 securid_sessionlist_add(inst, request, securid_session);
355                                 rc = RC_SECURID_AUTH_CHALLENGE;
356
357                         } else {
358                                 SD_Pin(securid_session->sdiHandle, &empty_pin[0]); //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,
362                                                      securid_session);
363                                 rc = RC_SECURID_AUTH_FAILURE;
364                         }
365
366                         return rc;
367
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(securid_session->sdiHandle, new_pin);
373                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
374                                          "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
375                                          new_pin);
376                                 securid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
377
378                                 /* insert the updated session in the session list */
379                                 securid_sessionlist_add(inst, request,
380                                                         securid_session);
381                                 rc = RC_SECURID_AUTH_CHALLENGE;
382
383                         } else {
384                                 /* User has opted for a user-defined PIN */
385                                 AceGetPinParams(securid_session->sdiHandle,
386                                                 &pin_params);
387                                 if (pin_params.Alphanumeric) {
388                                         strcpy(format, "alphanumeric characters");
389                                 } else {
390                                         strcpy(format, "digits");
391                                 }
392
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                                          pin_params.Min, pin_params.Max, format);
396                                 securid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
397
398                                 /* insert the updated session in the session list */
399                                 securid_sessionlist_add(inst, request,
400                                                         securid_session);
401                                 rc = RC_SECURID_AUTH_CHALLENGE;
402                         }
403
404                         return rc;
405
406                 default:
407                         ERROR("rlm_securid: Invalid session state %d for user [%s]",
408                                securid_session->securidSessionState,
409                                username);
410                         break;
411                 }
412         }
413
414         return 0;
415
416 }
417
418 /******************************************/
419 static int mod_detach(void *instance)
420 {
421         rlm_securid_t *inst = (rlm_securid_t *) instance;
422
423         /* delete session tree */
424         if (inst->session_tree) {
425                 rbtree_free(inst->session_tree);
426                 inst->session_tree = NULL;
427         }
428
429         pthread_mutex_destroy(&(inst->session_mutex));
430
431         return 0;
432 }
433
434
435 static int mod_instantiate(UNUSED CONF_SECTION *conf, void *instance)
436 {
437         rlm_securid_t *inst = instance;
438
439         /*
440          *      Lookup sessions in the tree.  We don't free them in
441          *      the tree, as that's taken care of elsewhere...
442          */
443         inst->session_tree = rbtree_create(securid_session_cmp, NULL, 0);
444         if (!inst->session_tree) {
445                 ERROR("rlm_securid: Cannot initialize session tree.");
446                 return -1;
447         }
448
449         pthread_mutex_init(&(inst->session_mutex), NULL);
450         return 0;
451 }
452
453
454 /*
455  *      Authenticate the user via one of any well-known password.
456  */
457 static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
458 {
459         int rcode;
460         rlm_securid_t *inst = instance;
461         char  buffer[MAX_STRING_LEN]="";
462         char const *username=NULL, *password=NULL;
463         VALUE_PAIR *vp;
464
465         /*
466          *      We can only authenticate user requests which HAVE
467          *      a User-Name attribute.
468          */
469         if (!request->username) {
470                 AUTH("rlm_securid: Attribute \"User-Name\" is required for authentication.");
471                 return RLM_MODULE_INVALID;
472         }
473
474         if (!request->password) {
475                 RAUTH("Attribute \"Password\" is required for authentication.");
476                 return RLM_MODULE_INVALID;
477         }
478
479         /*
480          *      Clear-text passwords are the only ones we support.
481          */
482         if (request->password->da->attr != PW_USER_PASSWORD) {
483                 RAUTH("Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->da->name);
484                 return RLM_MODULE_INVALID;
485         }
486
487         /*
488          *      The user MUST supply a non-zero-length password.
489          */
490         if (request->password->length == 0) {
491                 REDEBUG("Password should not be empty");
492                 return RLM_MODULE_INVALID;
493         }
494
495         /*
496          *      shortcuts
497          */
498         username = request->username->vp_strvalue;
499         password = request->password->vp_strvalue;
500
501         if (RDEBUG_ENABLED3) {
502                 RDEBUG3("Login attempt with password \"%s\"", password);
503         } else {
504                 RDEBUG("Login attempt with password");
505         }
506
507         rcode = securidAuth(inst, request, username, password,
508                             buffer, sizeof(buffer));
509
510         switch (rcode) {
511         case RC_SECURID_AUTH_SUCCESS:
512                 rcode = RLM_MODULE_OK;
513                 break;
514
515         case RC_SECURID_AUTH_CHALLENGE:
516                 /* reply with Access-challenge message code (11) */
517
518                 /* Generate Prompt attribute */
519                 vp = paircreate(request->reply, PW_PROMPT, 0);
520
521                 rad_assert(vp != NULL);
522                 vp->vp_integer = 0; /* no echo */
523                 pairadd(&request->reply->vps, vp);
524
525                 /* Mark the packet as a Acceess-Challenge Packet */
526                 request->reply->code = PW_ACCESS_CHALLENGE;
527                 RDEBUG("Sending Access-Challenge.");
528                 rcode = RLM_MODULE_HANDLED;
529                 break;
530
531         case RC_SECURID_AUTH_FAILURE:
532         case RC_SECURID_AUTH_ACCESS_DENIED_FAILURE:
533         case RC_SECURID_AUTH_INVALID_SERVER_FAILURE:
534         default:
535                 rcode = RLM_MODULE_REJECT;
536                 break;
537         }
538
539         if (*buffer) pairmake_reply("Reply-Message", buffer, T_OP_EQ);
540
541         return rcode;
542 }
543
544
545 /*
546  *      The module name should be the only globally exported symbol.
547  *      That is, everything else should be 'static'.
548  *
549  *      If the module needs to temporarily modify it's instantiation
550  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
551  *      The server will then take care of ensuring that the module
552  *      is single-threaded.
553  */
554 module_t rlm_securid = {
555         RLM_MODULE_INIT,
556         "securid",
557         RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,         /* type */
558         sizeof(rlm_securid_t),
559         module_config,
560         mod_instantiate,                /* instantiation */
561         mod_detach,                     /* detach */
562         {
563                 mod_authenticate,       /* authentication */
564                 NULL,                   /* authorization */
565                 NULL,                   /* preaccounting */
566                 NULL,                   /* accounting */
567                 NULL,                   /* checksimul */
568                 NULL,                   /* pre-proxy */
569                 NULL,                   /* post-proxy */
570                 NULL                    /* post-auth */
571         },
572 };