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