Fixup $Id header for rlm_securid.c
[freeradius.git] / src / modules / rlm_securid / rlm_securid.c
1 /*
2  * rlm_securid.c
3  *
4  * Version:  $Id$
5  *
6  * supports "next-token code" and "new-pin" modes
7  *
8  *
9  *   This program is free software; you can redistribute it and/or modify
10  *   it under the terms of the GNU General Public License as published by
11  *   the Free Software Foundation; either version 2 of the License, or
12  *   (at your option) any later version.
13  *
14  *   This program is distributed in the hope that it will be useful,
15  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
16  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  *   GNU General Public License for more details.
18  *
19  *   You should have received a copy of the GNU General Public License
20  *   along with this program; if not, write to the Free Software
21  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22  *
23  * Copyright 2012  The FreeRADIUS server project
24  * Copyright 2012  Alan DeKok <aland@networkradius.com>
25  */
26
27 #include <freeradius-devel/ident.h>
28 #include <freeradius-devel/radiusd.h>
29 #include <freeradius-devel/modules.h>
30 #include <ctype.h>
31
32 #include "rlm_securid.h"
33
34 typedef enum {
35         RC_SECURID_AUTH_SUCCESS = 0,
36         RC_SECURID_AUTH_FAILURE = -3,
37         RC_SECURID_AUTH_ACCESS_DENIED_FAILURE = -4,
38         RC_SECURID_AUTH_INVALID_SERVER_FAILURE = -5,
39         RC_SECURID_AUTH_CHALLENGE = -17
40 }
41         SECURID_AUTH_RC;
42
43
44 static const CONF_PARSER module_config[] = {
45         { "timer_expire", PW_TYPE_INTEGER,
46           offsetof(rlm_securid_t, timer_limit), NULL, "600"},
47         { "max_sessions", PW_TYPE_INTEGER,
48           offsetof(rlm_securid_t, max_sessions), NULL, "2048"},
49         { "max_trips_per_session", PW_TYPE_INTEGER,
50           offsetof(rlm_securid_t, max_trips_per_session), NULL, NULL},
51         { "max_round_trips", PW_TYPE_INTEGER,
52           offsetof(rlm_securid_t, max_trips_per_session), NULL, "6"},
53         { NULL, -1, 0, NULL, NULL }             /* end the list */
54 };
55
56
57 /* comparison function to find session in the tree */
58 static int securid_session_cmp(const void *a, const void *b)
59 {
60         int rcode;
61         const SECURID_SESSION *one = a;
62         const SECURID_SESSION *two = b;
63
64         rad_assert(one != NULL);
65         rad_assert(two != NULL);
66
67         rcode = fr_ipaddr_cmp(&one->src_ipaddr, &two->src_ipaddr);
68         if (rcode != 0) return rcode;
69
70         return memcmp(one->state, two->state, sizeof(one->state));
71 }
72
73
74 static SECURID_AUTH_RC securidAuth(void *instance, REQUEST *request,
75                                    const char* username, 
76                                    const char* passcode,
77                                    char* replyMsgBuffer,int replyMsgBufferSize)
78 {
79         rlm_securid_t *inst = (rlm_securid_t *) instance;
80         int         acmRet;
81         SD_PIN pinParams;
82         char newPin[10];
83         char format[30];
84         SECURID_SESSION *pSecurid_session=NULL;
85         int rc=-1;
86
87         if (!username) {
88                 radlog(L_ERR, "SecurID username is NULL");
89                 return RC_SECURID_AUTH_FAILURE;         
90         }
91
92         if (!passcode) {
93                 radlog(L_ERR, "SecurID passcode is NULL for %s user",username);
94                 return RC_SECURID_AUTH_FAILURE;         
95         }
96
97         *replyMsgBuffer = '\0';
98
99         pSecurid_session = securid_sessionlist_find(inst,request);
100         if (pSecurid_session == NULL) {
101                 /* securid session not found */
102                 SDI_HANDLE  sdiHandle = SDI_HANDLE_NONE;
103
104                 acmRet = SD_Init(&sdiHandle);
105                 if (acmRet != ACM_OK) {
106                         radlog(L_ERR, "Cannot communicate with the ACE/Server");
107                         return -1;
108                 }
109
110                 acmRet = SD_Lock(sdiHandle, (SD_CHAR*)username);
111                 if (acmRet != ACM_OK) {
112                         radlog(L_ERR,"SecurID: Access denied. Name [%s] lock failed.",username);
113                         return -2;
114                 }
115
116                 acmRet = SD_Check(sdiHandle, (SD_CHAR*) passcode,
117                                   (SD_CHAR*) username);
118                 switch (acmRet) {
119                 case ACM_OK:
120                         /* we are in now */
121                         RDEBUG("SecurID authentication successful for %s.",
122                                username);
123                         SD_Close(sdiHandle);
124
125                         return RC_SECURID_AUTH_SUCCESS;
126
127                 case ACM_ACCESS_DENIED:         
128                         /* not this time */
129                         RDEBUG("SecurID Access denied for %s", username);
130                         SD_Close(sdiHandle);
131                         return RC_SECURID_AUTH_ACCESS_DENIED_FAILURE;
132
133                 case ACM_INVALID_SERVER:
134                         radlog(L_ERR,"SecurID: Invalid ACE server.");
135                         return RC_SECURID_AUTH_INVALID_SERVER_FAILURE;
136
137                 case ACM_NEW_PIN_REQUIRED:
138                         RDEBUG2("SeecurID new pin required for %s",
139                                 username);
140
141                         /* create a new session */
142                         pSecurid_session = securid_session_alloc();
143                         pSecurid_session->sdiHandle = sdiHandle; /* save ACE handle for future use */
144                         pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
145                         pSecurid_session->identity = strdup(username);
146                          
147                         /* Get PIN requirements */
148                         acmRet = AceGetPinParams(sdiHandle, &pinParams);
149                          
150                         /* If a system-generated PIN is required */
151                         if (pinParams.Selectable == CANNOT_CHOOSE_PIN) {
152                                 /* Prompt user to accept a system generated PIN */
153                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
154                                          "\r\nAre you prepared to accept a new system-generated PIN [y/n]?");
155                                 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_ACCEPT_STATE;
156
157                         } else if (pinParams.Selectable == USER_SELECTABLE) { //may be returned by AM 6.x servers.
158                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
159                                          "\r\nPress 'y' to generate a new PIN\r\nOR\r\n'n'to enter a new PIN yourself [y/n]");
160                                 pSecurid_session->securidSessionState = NEW_PIN_USER_SELECT_STATE;
161
162                         } else {
163                                 if (pinParams.Alphanumeric) {
164                                         strcpy(format, "alphanumeric characters");
165                                 } else {
166                                         strcpy(format, "digits");
167                                 }
168                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
169                                          " \r\n   Enter your new PIN of %d to %d %s,\r\n                or\r\n   <Ctrl-D> to cancel the New PIN procedure:",
170                                          pinParams.Min, pinParams.Max, format);
171                         }
172
173                         /* insert new session in the session list */
174                         securid_sessionlist_add(inst,request,pSecurid_session);
175                          
176                         return RC_SECURID_AUTH_CHALLENGE;
177
178                 case ACM_NEXT_CODE_REQUIRED:
179                         RDEBUG2("Next securid token code required for %s",
180                                 username);
181
182                         /* create a new session */
183                         pSecurid_session = securid_session_alloc();
184                         pSecurid_session->sdiHandle = sdiHandle;
185                         pSecurid_session->securidSessionState = NEXT_CODE_REQUIRED_STATE;
186                         pSecurid_session->identity = strdup(username);
187
188                         /* insert new session in the session list */
189                         securid_sessionlist_add(inst,request,pSecurid_session);
190                      
191                         strlcpy(replyMsgBuffer, "\r\nPlease Enter the Next Code from Your Token:", replyMsgBufferSize);
192                         return RC_SECURID_AUTH_CHALLENGE;
193                 default:
194                         radlog(L_ERR,"SecurID: Unexpected error from ACE/Agent API acmRet=%d",acmRet);
195                         return RC_SECURID_AUTH_FAILURE;
196   
197                         
198                 }
199         } else {
200                 /* existing session found */
201                 RDEBUG("Continuing previous session found for user [%s]",username);
202
203                 /* continue previous session */
204                 switch (pSecurid_session->securidSessionState) {
205                 case NEXT_CODE_REQUIRED_STATE:
206                         DEBUG2("Securid NEXT_CODE_REQUIRED_STATE: User [%s]",username);
207                         /* next token code mode */
208
209                         acmRet = SD_Next(pSecurid_session->sdiHandle, (SD_CHAR*)passcode);
210                         if (acmRet == ACM_OK) {
211                                 radlog(L_INFO,"Next SecurID token accepted for [%s].",pSecurid_session->identity);
212                                 rc = RC_SECURID_AUTH_SUCCESS;
213
214                         } else {
215                                 radlog(L_INFO,"SecurID: Next token rejected for [%s].",pSecurid_session->identity);
216                                 rc = RC_SECURID_AUTH_FAILURE;
217                         }
218
219                         /* deallocate session */
220                         securid_session_free(inst,request,pSecurid_session);
221                         return rc;
222
223                 case NEW_PIN_REQUIRED_STATE:
224                         RDEBUG2("SecurID NEW_PIN_REQUIRED_STATE for %s",
225                                 username);
226
227                         /* save the previous pin */
228                         if (pSecurid_session->pin) {
229                                 free(pSecurid_session->pin);
230                                 pSecurid_session->pin = NULL;
231                         }
232                         pSecurid_session->pin = strdup(passcode);
233
234                         strlcpy(replyMsgBuffer,"\r\n                 Please re-enter new PIN:", replyMsgBufferSize);
235
236                         /* set next state */
237                         pSecurid_session->securidSessionState = NEW_PIN_USER_CONFIRM_STATE;
238
239                         /* insert the updated session in the session list */
240                         securid_sessionlist_add(inst,request,pSecurid_session);
241                         return RC_SECURID_AUTH_CHALLENGE;
242                           
243                 case NEW_PIN_USER_CONFIRM_STATE:
244                         RDEBUG2("SecurID NEW_PIN_USER_CONFIRM_STATE: User [%s]",username);
245                         /* compare previous pin and current pin */
246                         if (!pSecurid_session->pin || strcmp(pSecurid_session->pin,passcode)) {
247                                 RDEBUG2("Pin confirmation failed. Pins do not match [%s] and [%s]",
248                                        SAFE_STR(pSecurid_session->pin),
249                                        passcode);
250                                 /* pins do not match */
251
252                                 /* challenge the user again */
253                                 AceGetPinParams(pSecurid_session->sdiHandle, &pinParams);
254                                 if (pinParams.Alphanumeric) {
255                                         strcpy(format, "alphanumeric characters");
256                                 } else {
257                                         strcpy(format, "digits");
258                                 }
259                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
260                                          " \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:",
261                                          pinParams.Min, pinParams.Max, format);
262
263                                 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
264
265                                 /* insert the updated session in the session list */
266                                 securid_sessionlist_add(inst,request,pSecurid_session);
267                                 rc = RC_SECURID_AUTH_CHALLENGE;
268
269                         } else {
270                                 /* pins match */
271                                 RDEBUG2("Pin confirmation succeeded. Pins match");
272                                 acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)passcode);
273                                 if (acmRet == ACM_NEW_PIN_ACCEPTED) {
274                                         RDEBUG("New SecurID pin accepted for %s.",pSecurid_session->identity);
275
276                                         pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
277
278                                         /* insert the updated session in the session list */
279                                         securid_sessionlist_add(inst,request,pSecurid_session);
280
281                                         rc = RC_SECURID_AUTH_CHALLENGE;
282                                         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);
283                                 } else {
284                                         RDEBUG("SecurID: New SecurID pin rejected for %s.",pSecurid_session->identity);
285                                         SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)"");  /* cancel PIN */
286                                         
287
288                                         rc = RC_SECURID_AUTH_FAILURE;
289
290                                         /* deallocate session */
291                                         securid_session_free(inst, request,
292                                                              pSecurid_session);
293                                 }
294                         }
295                         return rc;                
296                 case NEW_PIN_AUTH_VALIDATE_STATE:
297                         acmRet = SD_Check(pSecurid_session->sdiHandle, (SD_CHAR*)passcode, (SD_CHAR*)username);
298                         if (acmRet == ACM_OK) {
299                                 RDEBUG("New SecurID passcode accepted for %s.",
300                                        pSecurid_session->identity);
301                                 rc = RC_SECURID_AUTH_SUCCESS;
302
303                         } else {
304                                 radlog(L_INFO,"SecurID: New passcode rejected for [%s].",pSecurid_session->identity);
305                                 rc = RC_SECURID_AUTH_FAILURE;
306                         }
307
308                         /* deallocate session */
309                         securid_session_free(inst,request,pSecurid_session);
310
311                         return rc;
312                 case NEW_PIN_SYSTEM_ACCEPT_STATE:
313                         if (!strcmp(passcode, "y")) {
314                                 AceGetSystemPin(pSecurid_session->sdiHandle, newPin);
315                                         
316                                 /* Save the PIN for the next session
317                                  * continuation */
318                                 if (pSecurid_session->pin) {
319                                         free(pSecurid_session->pin);
320                                         pSecurid_session->pin = NULL;
321                                 }
322                                 pSecurid_session->pin = strdup(newPin);
323                                         
324                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
325                                          "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
326                                          newPin);
327                                 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
328                                         
329                                 /* insert the updated session in the
330                                  * session list */
331                                 securid_sessionlist_add(inst, request,
332                                                         pSecurid_session);
333                                         
334                                 rc = RC_SECURID_AUTH_CHALLENGE;
335
336                         } else {
337                                 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); //Cancel new PIN
338                                         
339                                 /* deallocate session */
340                                 securid_session_free(inst, request,
341                                                      pSecurid_session);
342                                         
343                                 rc = RC_SECURID_AUTH_FAILURE;
344                         }
345                                 
346                         return rc;                              
347                          
348                 case NEW_PIN_SYSTEM_CONFIRM_STATE:
349                         acmRet = SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)pSecurid_session->pin);
350                         if (acmRet == 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                                 pSecurid_session->securidSessionState = NEW_PIN_AUTH_VALIDATE_STATE;
353                                 /* insert the updated session in the session list */
354                                 securid_sessionlist_add(inst,request,pSecurid_session);
355                                 rc = RC_SECURID_AUTH_CHALLENGE;
356
357                         } else {
358                                 SD_Pin(pSecurid_session->sdiHandle, (SD_CHAR*)""); //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                                                      pSecurid_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(pSecurid_session->sdiHandle, newPin);
373                                 snprintf(replyMsgBuffer, replyMsgBufferSize,
374                                          "\r\nYour new PIN is: %s\r\nDo you accept this [y/n]?",
375                                          newPin);
376                                 pSecurid_session->securidSessionState = NEW_PIN_SYSTEM_CONFIRM_STATE;
377                                         
378                                 /* insert the updated session in the session list */
379                                 securid_sessionlist_add(inst, request,
380                                                         pSecurid_session);
381                                 rc = RC_SECURID_AUTH_CHALLENGE;
382
383                         } else {
384                                 /* User has opted for a user-defined PIN */
385                                 AceGetPinParams(pSecurid_session->sdiHandle,
386                                                 &pinParams);
387                                 if (pinParams.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                                          pinParams.Min, pinParams.Max, format);
396                                 pSecurid_session->securidSessionState = NEW_PIN_REQUIRED_STATE;
397                                         
398                                 /* insert the updated session in the session list */
399                                 securid_sessionlist_add(inst, request,
400                                                         pSecurid_session);
401                                 rc = RC_SECURID_AUTH_CHALLENGE;
402                         }
403                                 
404                         return rc;
405                                 
406                 default:
407                         radlog(L_ERR|L_CONS, "rlm_securid: Invalid session state %d for user [%s]",
408                                pSecurid_session->securidSessionState,
409                                username);
410                         break;  
411                 }
412         }
413         
414         return 0;
415                 
416 }
417
418 /******************************************/
419 static int securid_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         free(inst);
432         return 0;
433 }
434
435
436 static int securid_instantiate(CONF_SECTION *conf, void **instance)
437 {
438         rlm_securid_t *inst;
439
440         /* Set up a storage area for instance data */
441         inst = rad_malloc(sizeof(*inst));
442         if (!inst)  return -1;
443         memset(inst, 0, sizeof(*inst));
444
445         /* If the configuration parameters can't be parsed, then fail. */
446         if (cf_section_parse(conf, inst, module_config) < 0) {
447                 radlog(L_ERR|L_CONS, "rlm_securid: Unable to parse configuration section.");
448                 securid_detach(inst);
449                 return -1;
450         }
451
452         /*
453          *      Lookup sessions in the tree.  We don't free them in
454          *      the tree, as that's taken care of elsewhere...
455          */
456         inst->session_tree = rbtree_create(securid_session_cmp, NULL, 0);
457         if (!inst->session_tree) {
458                 radlog(L_ERR|L_CONS, "rlm_securid: Cannot initialize session tree.");
459                 securid_detach(inst);
460                 return -1;
461         }
462
463         pthread_mutex_init(&(inst->session_mutex), NULL);
464
465         *instance = inst;
466         return 0;
467 }
468
469
470 /*
471  *      Authenticate the user via one of any well-known password.
472  */
473 static int securid_authenticate(void *instance, REQUEST *request)
474 {
475         int rcode;
476         rlm_securid_t *inst = instance;
477         VALUE_PAIR *module_fmsg_vp;
478         VALUE_PAIR *vp;
479         char  buffer[MAX_STRING_LEN]="";
480         const char *username=NULL, *password=NULL;
481         char module_fmsg[MAX_STRING_LEN]="";
482         
483         /*
484          *      We can only authenticate user requests which HAVE
485          *      a User-Name attribute.
486          */
487         if (!request->username) {
488                 radlog(L_AUTH, "rlm_securid: Attribute \"User-Name\" is required for authentication.");
489                 return RLM_MODULE_INVALID;
490         }
491
492         if (!request->password) {
493                 radlog_request(L_AUTH, 0, request, "Attribute \"Password\" is required for authentication.");
494                 return RLM_MODULE_INVALID;
495         }
496
497         /*
498          *      Clear-text passwords are the only ones we support.
499          */
500         if (request->password->attribute != PW_USER_PASSWORD) {
501                 radlog_request(L_AUTH, 0, request, "Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
502                 return RLM_MODULE_INVALID;
503         }
504
505         /*
506          *      The user MUST supply a non-zero-length password.
507          */
508         if (request->password->length == 0) {
509                 snprintf(module_fmsg,sizeof(module_fmsg),"rlm_securid: empty password supplied");
510                 module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
511                 pairadd(&request->packet->vps, module_fmsg_vp);
512                 return RLM_MODULE_INVALID;
513         }
514
515         /*
516          *      shortcuts
517          */
518         username = request->username->vp_strvalue;
519         password = request->password->vp_strvalue;
520         
521         RDEBUG("User [%s] login attempt with password [%s]",
522                username, password);
523         
524         rcode = securidAuth(inst, request, username, password,
525                             buffer, sizeof(buffer));
526         
527         switch (rcode) {
528         case RC_SECURID_AUTH_SUCCESS:
529                 rcode = RLM_MODULE_OK;
530                 break;
531
532         case RC_SECURID_AUTH_CHALLENGE:
533                 /* reply with Access-challenge message code (11) */
534
535                 /* Generate Prompt attribute */
536                 vp = paircreate(PW_PROMPT, 0, PW_TYPE_INTEGER);
537                                 
538                 rad_assert(vp != NULL);
539                 vp->vp_integer = 0; /* no echo */
540                 pairadd(&request->reply->vps, vp);
541
542                 /* Mark the packet as a Acceess-Challenge Packet */
543                 request->reply->code = PW_ACCESS_CHALLENGE;
544                 RDEBUG("Sending Access-Challenge.");
545                 rcode = RLM_MODULE_HANDLED;
546                 break;
547
548         case RC_SECURID_AUTH_FAILURE:
549         case RC_SECURID_AUTH_ACCESS_DENIED_FAILURE:
550         case RC_SECURID_AUTH_INVALID_SERVER_FAILURE:
551         default:
552                 rcode = RLM_MODULE_REJECT;
553                 break;
554         }
555
556         if (*buffer) {
557                 /* Generate Reply-Message attribute with reply message data */
558                 vp = pairmake("Reply-Message", buffer, T_OP_EQ);
559                 
560                 /* make sure message ends with '\0' */
561                 if (vp->length < (int) sizeof(vp->vp_strvalue)) {
562                         vp->vp_strvalue[vp->length] = '\0';
563                         vp->length++;
564                 }
565                 pairadd(&request->reply->vps,vp);
566         }
567         return rcode;
568 }
569
570
571 /*
572  *      The module name should be the only globally exported symbol.
573  *      That is, everything else should be 'static'.
574  *
575  *      If the module needs to temporarily modify it's instantiation
576  *      data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
577  *      The server will then take care of ensuring that the module
578  *      is single-threaded.
579  */
580 module_t rlm_securid = {
581         RLM_MODULE_INIT,
582         "securid",
583         RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,         /* type */
584         securid_instantiate,            /* instantiation */
585         securid_detach,                 /* detach */
586         {
587                 securid_authenticate,   /* authentication */
588                 NULL,                   /* authorization */
589                 NULL,                   /* preaccounting */
590                 NULL,                   /* accounting */
591                 NULL,                   /* checksimul */
592                 NULL,                   /* pre-proxy */
593                 NULL,                   /* post-proxy */
594                 NULL                    /* post-auth */
595         },
596 };