removed the old dynamic log code. It's no longer needed, as the
[freeradius.git] / src / modules / rlm_files / rlm_files.c
1 /*
2  * rlm_files.c  authorization: Find a user in the "users" file.
3  *              accounting:    Write the "detail" files.
4  *
5  * Version:     $Id$
6  *
7  */
8
9 static const char rcsid[] = "$Id$";
10
11 #include        "autoconf.h"
12
13 #include        "radiusd.h"
14
15 #include        <sys/socket.h>
16 #include        <sys/stat.h>
17 #include        <netinet/in.h>
18
19 #include        <stdlib.h>
20 #include        <string.h>
21 #include        <netdb.h>
22 #include        <ctype.h>
23 #include        <fcntl.h>
24 #include        <limits.h>
25
26 #if HAVE_MALLOC_H
27 #  include      <malloc.h>
28 #endif
29
30 #include        "modules.h"
31
32 #ifdef WITH_DBM
33 #  include      <dbm.h>
34 #endif
35 #ifdef WITH_NDBM
36 #  include      <ndbm.h>
37 #endif
38
39 struct file_instance {
40         char *compat_mode;
41
42         /* autz */
43         char *usersfile;
44         PAIR_LIST *users;
45
46         /* preacct */
47         char *acctusersfile;
48         PAIR_LIST *acctusers;
49 };
50
51 #if defined(WITH_DBM) || defined(WITH_NDBM)
52 /*
53  *      See if a potential DBM file is present.
54  */
55 static int checkdbm(char *users, char *ext)
56 {
57         char buffer[256];
58         struct stat st;
59
60         strcpy(buffer, users);
61         strcat(buffer, ext);
62
63         return stat(buffer, &st);
64 }
65
66 /*
67  *      Find the named user in the DBM user database.
68  *      Returns: -1 not found
69  *                0 found but doesn't match.
70  *                1 found and matches.
71  */
72 static int dbm_find(DBM *dbmfile, char *name, VALUE_PAIR *request_pairs,
73                 VALUE_PAIR **check_pairs, VALUE_PAIR **reply_pairs)
74 {
75         datum           named;
76         datum           contentd;
77         char            *ptr;
78         VALUE_PAIR      *check_tmp;
79         VALUE_PAIR      *reply_tmp;
80         int             ret = 0;
81
82         named.dptr = name;
83         named.dsize = strlen(name);
84 #ifdef WITH_DBM
85         contentd = fetch(named);
86 #endif
87 #ifdef WITH_NDBM
88         contentd = dbm_fetch(dbmfile, named);
89 #endif
90         if(contentd.dptr == NULL)
91                 return -1;
92
93         check_tmp = NULL;
94         reply_tmp = NULL;
95
96         /*
97          *      Parse the check values
98          */
99         ptr = contentd.dptr;
100         contentd.dptr[contentd.dsize] = '\0';
101
102         if (*ptr != '\n' && userparse(ptr, &check_tmp) != 0) {
103                 radlog(L_ERR|L_CONS, "Parse error (check) for user %s", name);
104                 pairfree(check_tmp);
105                 return -1;
106         }
107         while(*ptr != '\n' && *ptr != '\0') {
108                 ptr++;
109         }
110         if(*ptr != '\n') {
111                 radlog(L_ERR|L_CONS, "Parse error (no reply pairs) for user %s",
112                         name);
113                 pairfree(check_tmp);
114                 return -1;
115         }
116         ptr++;
117
118         /*
119          *      Parse the reply values
120          */
121         if (userparse(ptr, &reply_tmp) != 0) {
122                 radlog(L_ERR|L_CONS, "Parse error (reply) for user %s", name);
123                 pairfree(check_tmp);
124                 pairfree(reply_tmp);
125                 return -1;
126         }
127
128         /*
129          *      See if the check_pairs match.
130          */
131         if (paircmp(request_pairs, check_tmp, reply_pairs) == 0) {
132                 ret = 1;
133                 pairmove(reply_pairs, &reply_tmp);
134                 pairmove2(reply_pairs, &reply_tmp, PW_FALL_THROUGH);
135                 pairmove(check_pairs, &check_tmp);
136         }
137         pairfree(reply_tmp);
138         pairfree(check_tmp);
139
140         return ret;
141 }
142 #endif /* DBM */
143
144 /*
145  *     See if a VALUE_PAIR list contains Fall-Through = Yes
146  */
147 static int fallthrough(VALUE_PAIR *vp)
148 {
149         VALUE_PAIR *tmp;
150
151         tmp = pairfind(vp, PW_FALL_THROUGH);
152
153         return tmp ? tmp->lvalue : 0;
154 }
155
156
157
158 static int file_init(void)
159 {
160         return 0;
161 }
162
163 /*
164  *      A temporary holding area for config values to be extracted
165  *      into, before they are copied into the instance data
166  */
167 static struct file_instance config;
168
169 static CONF_PARSER module_config[] = {
170         { "usersfile",     PW_TYPE_STRING_PTR, &config.usersfile, RADIUS_USERS },
171         { "acctusersfile", PW_TYPE_STRING_PTR, &config.acctusersfile, RADIUS_ACCT_USERS },
172         { "compat",        PW_TYPE_STRING_PTR, &config.compat_mode, "cistron" },
173         { NULL, -1, NULL, NULL }
174 };
175
176 static int getusersfile(const char *filename, PAIR_LIST **pair_list)
177 {
178         int rcode;
179         PAIR_LIST *users = NULL;
180 #if defined(WITH_DBM) || defined(WITH_NDBM)
181         if (!use_dbm &&
182             (checkdbm(filename, ".dir") == 0 ||
183              checkdbm(filename, ".db") == 0)) {
184                 radlog(L_INFO|L_CONS, "DBM files found but no -b flag " "given - NOT using DBM");
185         }
186 #endif
187
188         if (!use_dbm) {
189                 rcode = pairlist_read(filename, &users, 1);
190                 if (rcode < 0) {
191                         return -1;
192                 }
193         }
194
195         /*
196          *      Walk through the 'users' file list, if we're debugging,
197          *      or if we're in compat_mode.
198          */
199         if ((debug_flag) ||
200             (strcmp(config.compat_mode, "cistron") == 0)) {
201                 PAIR_LIST *entry;
202                 VALUE_PAIR *vp;
203                 int compat_mode = FALSE;
204
205                 if (strcmp(config.compat_mode, "cistron") == 0) {
206                         compat_mode = TRUE;
207                 }
208         
209                 entry = users;
210                 while (entry) {
211                         if (compat_mode) {
212                                 DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
213                                       filename, entry->lineno,
214                                       entry->name);
215                         }
216
217                         /*
218                          *      Look for improper use of '=' in the
219                          *      check items.  They should be using
220                          *      '==' for on-the-wire RADIUS attributes,
221                          *      and probably ':=' for server
222                          *      configuration items.
223                          */
224                         for (vp = entry->check; vp != NULL; vp = vp->next) {
225                                 /*
226                                  *      Ignore attributes which are set
227                                  *      properly.
228                                  */
229                                 if (vp->operator != T_OP_EQ) {
230                                         continue;
231                                 }
232
233                                 /*
234                                  *      If it's a vendor attribute,
235                                  *      or it's a wire protocol, 
236                                  *      ensure it has '=='.
237                                  */
238                                 if (((vp->attribute & ~0xffff) != 0) ||
239                                     (vp->attribute < 0x100)) {
240                                         if (!compat_mode) {
241                                                 DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
242                                                       filename, entry->lineno,
243                                                       vp->name, vp->name,
244                                                       entry->name);
245                                         } else {
246                                                 DEBUG("\tChanging '%s =' to '%s =='",
247                                                       vp->name, vp->name);
248                                         }
249                                         vp->operator = T_OP_CMP_EQ;
250                                         continue;
251                                 }
252                                 
253                                 /*
254                                  *      Cistron Compatibility mode.
255                                  *
256                                  *      Re-write selected attributes
257                                  *      to be '+=', instead of '='.
258                                  *
259                                  *      All others get set to '=='
260                                  */
261                                 if (compat_mode) {
262                                         /*
263                                          *      Non-wire attributes become +=
264                                          *
265                                          *      On the write attributes
266                                          *      become ==
267                                          */
268                                         if ((vp->attribute >= 0x100) &&
269                                             (vp->attribute <= 0xffff) &&
270                                             (vp->attribute != PW_HINT) &&
271                                             (vp->attribute != PW_HUNTGROUP_NAME)) {
272                                                 DEBUG("\tChanging '%s =' to '%s +='",
273                                                       vp->name, vp->name);
274                                                 vp->operator = T_OP_ADD;
275                                         } else {
276                                                 DEBUG("\tChanging '%s =' to '%s =='",
277                                                       vp->name, vp->name);
278                                                 vp->operator = T_OP_CMP_EQ;
279                                         }
280                                 }
281                                 
282                         } /* end of loop over check items */
283                 
284                 
285                         /*
286                          *      Look for server configuration items
287                          *      in the reply list.
288                          *
289                          *      It's a common enough mistake, that it's
290                          *      worth doing.
291                          */
292                         for (vp = entry->reply; vp != NULL; vp = vp->next) {
293                                 /*
294                                  *      If it's NOT a vendor attribute,
295                                  *      and it's NOT a wire protocol
296                                  *      and we ignore Fall-Through,
297                                  *      then bitch about it, giving a
298                                  *      good warning message.
299                                  */
300                                 if (!(vp->attribute & ~0xffff) &&
301                                     (vp->attribute > 0xff) &&
302                                     (vp->attribute > 1000)) {
303                                         log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
304                                                   "\tfound in reply item list for user \"%s\".\n"
305                                                   "\tThis attribute MUST go on the first line"
306                                                   " with the other check items", 
307                                                   filename, entry->lineno, vp->name,
308                                                   entry->name);
309                                 }
310                         }
311                 
312                         entry = entry->next;
313                 }
314         
315         }
316
317         *pair_list = users;
318         return 0;
319 }
320
321 /*
322  *      (Re-)read the "users" file into memory.
323  */
324 static int file_instantiate(CONF_SECTION *conf, void **instance)
325 {
326         struct file_instance *inst;
327         int rcode;
328
329         inst = malloc(sizeof *inst);
330         if (!inst) {
331                 radlog(L_ERR|L_CONS, "Out of memory\n");
332                 return -1;
333         }
334
335         if (cf_section_parse(conf, module_config) < 0) {
336                 free(inst);
337                 return -1;
338         }
339
340         inst->usersfile = config.usersfile;
341         inst->acctusersfile = config.acctusersfile;
342         config.usersfile = NULL;
343         config.acctusersfile = NULL;
344
345         rcode = getusersfile(inst->usersfile, &inst->users);
346         if (rcode != 0) {
347                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->usersfile);
348                 free(inst->usersfile);
349                 free(inst->acctusersfile);
350                 free(inst);
351                 return -1;
352         }
353
354         rcode = getusersfile(inst->acctusersfile, &inst->acctusers);
355         if (rcode != 0) {
356                 radlog(L_ERR|L_CONS, "Errors reading %s", inst->acctusersfile);
357                 pairlist_free(&inst->users);
358                 free(inst->usersfile);
359                 free(inst->acctusersfile);
360                 free(inst);
361                 return -1;
362         }
363
364         *instance = inst;
365         return 0;
366 }
367
368 /*
369  *      Find the named user in the database.  Create the
370  *      set of attribute-value pairs to check and reply with
371  *      for this user from the database. The main code only
372  *      needs to check the password, the rest is done here.
373  */
374 static int file_authorize(void *instance, REQUEST *request)
375 {
376         int             nas_port = 0;
377         VALUE_PAIR      *namepair;
378         VALUE_PAIR      *request_pairs;
379         VALUE_PAIR      *check_tmp;
380         VALUE_PAIR      *reply_tmp;
381         VALUE_PAIR      *tmp;
382         PAIR_LIST       *pl;
383         int             found = 0;
384 #if defined(WITH_DBM) || defined(WITH_NDBM)
385         int             i, r;
386         char            buffer[256];
387 #endif
388         const char      *name;
389         struct file_instance *inst = instance;
390 #ifdef WITH_USERCOLLIDE
391         VALUE_PAIR  *auth_type_pair;
392         VALUE_PAIR  *password_pair;
393         VALUE_PAIR  *auth_item;
394         int   auth_type = -1;
395         int   result = 1;
396 #endif
397         VALUE_PAIR **check_pairs, **reply_pairs;
398
399
400         request_pairs = request->packet->vps;
401         check_pairs = &request->config_items;
402         reply_pairs = &request->reply->vps;
403
404         /*
405          *      Grab the canonical user name.
406          */
407         namepair = request->username;
408         name = namepair ? (char *) namepair->strvalue : "NONE";
409
410         /*
411          *      Find the NAS port ID.
412          */
413         if ((tmp = pairfind(request_pairs, PW_NAS_PORT_ID)) != NULL)
414                 nas_port = tmp->lvalue;
415
416         /*
417          *      Find the entry for the user.
418          */
419 #if defined(WITH_DBM) || defined(WITH_NDBM)
420         /*
421          *      FIXME: move to rlm_dbm.c
422          */
423         if (use_dbm) {
424                 /*
425                  *      FIXME: No Prefix / Suffix support for DBM.
426                  */
427 #ifdef WITH_DBM
428                 if (dbminit(inst->usersfile) != 0)
429 #endif
430 #ifdef WITH_NDBM
431                 if ((dbmfile = dbm_open(inst->usersfile, O_RDONLY, 0)) == NULL)
432 #endif
433                 {
434                         radlog(L_ERR|L_CONS, "cannot open dbm file %s",
435                                 buffer);
436                         return RLM_MODULE_FAIL;
437                 }
438
439                 r = dbm_find(dbmfile, name, request_pairs, check_pairs,
440                              reply_pairs);
441                 if (r > 0) found = 1;
442                 if (r <= 0 || fallthrough(*reply_pairs)) {
443
444                         pairdelete(reply_pairs, PW_FALL_THROUGH);
445
446                         sprintf(buffer, "DEFAULT");
447                         i = 0;
448                         while ((r = dbm_find(dbmfile, buffer, request_pairs,
449                                check_pairs, reply_pairs)) >= 0 || i < 2) {
450                                 if (r > 0) {
451                                         found = 1;
452                                         if (!fallthrough(*reply_pairs))
453                                                 break;
454                                         pairdelete(reply_pairs,PW_FALL_THROUGH);
455                                 }
456                                 sprintf(buffer, "DEFAULT%d", i++);
457                         }
458                 }
459 #ifdef WITH_DBM
460                 dbmclose();
461 #endif
462 #ifdef WITH_NDBM
463                 dbm_close(dbmfile);
464 #endif
465         } else
466         /*
467          *      Note the fallthrough through the #endif.
468          */
469 #endif
470
471         for(pl = inst->users; pl; pl = pl->next) {
472 #ifdef WITH_USERCOLLIDE
473                 result = 1;
474 #endif
475                 /*
476                  *      If the current entry is NOT a default,
477                  *      AND the name does NOT match the current entry,
478                  *      then skip to the next entry.
479                  */
480                 if ((strcmp(pl->name, "DEFAULT") != 0) &&
481                     (strcmp(name, pl->name) != 0))  {
482                         continue;
483                 }
484
485                 /*
486                  *      If the current request matches against the
487                  *      check pairs, then add the reply pairs from the
488                  *      entry to the current list of reply pairs.
489                  */
490                 if ((paircmp(request_pairs, pl->check, reply_pairs) == 0)) {
491 #ifdef WITH_USERCOLLIDE
492                         /* 
493                          *      We don't compare pass on default users
494                          *      or they never match.  Oops.
495                          */
496                         if(strcmp(pl->name, "DEFAULT")) {
497                                 /* 
498                                  *      We check the pass as a config
499                                  *      item with user collisions Most
500                                  *      of this is stolen out of
501                                  *      rad_check_password()
502                                  */
503                                 if ((auth_type_pair = pairfind(pl->check, PW_AUTHTYPE)) != NULL) {
504                                         auth_type = auth_type_pair->lvalue;
505                                         DEBUG2("  file_auth (Usercollide):  auth_type %d", auth_type);
506                                 }
507         
508                                 /* Find pass in the REQ */
509                                 auth_item = request->password;
510                                 if (auth_item == NULL) {
511                                         DEBUG2("  file_auth (Usercollide): No password in the request");
512                                         return RLM_MODULE_OK;
513                                 }
514                 
515                                 /* Find the password from the users file. */
516                                 if ((password_pair = pairfind(pl->check, PW_CRYPT_PASSWORD)) != NULL)
517                                         auth_type = PW_AUTHTYPE_CRYPT;
518                                 else
519                                         password_pair = pairfind(pl->check, PW_PASSWORD);
520                                 
521                                 switch(auth_type) {
522                                 case PW_AUTHTYPE_CRYPT:
523                                         DEBUG2("  file_auth (Usercollide): Checking Crypt");
524                                         if (password_pair == NULL) {
525                                                 result = auth_item->strvalue ? 0 : 1;
526                                                 break;
527                                         }
528                                         if (strcmp(password_pair->strvalue,
529                                                    crypt(auth_item->strvalue,
530                                                          password_pair->strvalue)) != 0)
531                                                 result = 0;
532                                         break;
533                                 case PW_AUTHTYPE_LOCAL:
534                                         DEBUG2("  file_auth (Usercollide): Checking Local");
535                                         if (auth_item->attribute != PW_CHAP_PASSWORD) {
536                                                 if (password_pair == NULL ||
537                                                     strcmp(password_pair->strvalue,
538                                                            auth_item->strvalue)!=0)
539                                                         result = 0;
540                                                 break;
541                                         }
542                                 case PW_AUTHTYPE_ACCEPT:
543                                         break;  
544                                 default:
545                                         continue;
546                                 } /* switch(auth_type) */
547                         } /* if(!default) */
548
549                         if(result) { 
550 #endif
551                                 DEBUG2("  users: Matched %s at %d", pl->name, pl->lineno);
552                                 found = 1;
553                                 check_tmp = paircopy(pl->check);
554                                 reply_tmp = paircopy(pl->reply);
555                                 pairmove(reply_pairs, &reply_tmp);
556                                 pairmove(check_pairs, &check_tmp);
557                                 pairfree(reply_tmp);
558                                 pairfree(check_tmp); /* should be NULL */
559                                 /*
560                                  *      Fallthrough?
561                                  */
562                                 if (!fallthrough(pl->reply))
563                                         break;
564 #ifdef WITH_USERCOLLIDE
565                         }
566 #endif
567                 }
568         }
569         
570         /*
571          *      See if we succeeded.  If we didn't find the user,
572          *      then exit from the module.
573          */
574         if (!found)
575                 return RLM_MODULE_NOTFOUND;
576
577         /*
578          *      Add the port number to the Framed-IP-Address if
579          *      vp->addport is set, or if the Add-Port-To-IP-Address
580          *      pair is present.
581          *
582          *      FIXME: this should not happen here, but
583          *      after module_authorize in the main code!
584          */
585         if ((tmp = pairfind(*reply_pairs, PW_FRAMED_IP_ADDRESS)) != NULL) {
586                 VALUE_PAIR *tmp2;
587
588                 tmp2 = pairfind(*reply_pairs, PW_ADD_PORT_TO_IP_ADDRESS);
589                 if (tmp->addport || (tmp2 && tmp2->lvalue)) {
590                         tmp->lvalue = htonl(ntohl(tmp->lvalue) + nas_port);
591                         tmp->addport = 0;
592                 }
593                 pairdelete(reply_pairs, PW_ADD_PORT_TO_IP_ADDRESS);
594         }
595
596         /*
597          *      Remove server internal parameters.
598          */
599         pairdelete(reply_pairs, PW_FALL_THROUGH);
600
601         return RLM_MODULE_OK;
602 }
603
604 /*
605  *      Authentication - unused.
606  */
607 static int file_authenticate(void *instance, REQUEST *request)
608 {
609         instance = instance;
610         request = request;
611         return RLM_MODULE_OK;
612 }
613
614 /*
615  *      Pre-Accounting - read the acct_users file for check_items and
616  *      config_items. Reply items are Not Recommended(TM) in acct_users,
617  *      except for Fallthrough, which should work
618  *
619  *      This function is mostly a copy of file_authorize
620  */
621 static int file_preacct(void *instance, REQUEST *request)
622 {
623         VALUE_PAIR      *namepair;
624         const char      *name;
625         VALUE_PAIR      *request_pairs;
626         VALUE_PAIR      **config_pairs;
627         VALUE_PAIR      *reply_pairs = NULL;
628         VALUE_PAIR      *check_tmp;
629         VALUE_PAIR      *reply_tmp;
630         PAIR_LIST       *pl;
631         int             found = 0;
632 #if defined(WITH_DBM) || defined(WITH_NDBM)
633         int             i, r;
634         char            buffer[256];
635 #endif
636         struct file_instance *inst = instance;
637
638         namepair = request->username;
639         name = namepair ? (char *) namepair->strvalue : "NONE";
640         request_pairs = request->packet->vps;
641         config_pairs = &request->config_items;
642         
643         /*
644          *      Find the entry for the user.
645          */
646 #if defined(WITH_DBM) || defined(WITH_NDBM)
647         /*
648          *      FIXME: move to rlm_dbm.c
649          */
650         if (use_dbm) {
651                 /*
652                  *      FIXME: No Prefix / Suffix support for DBM.
653                  */
654 #ifdef WITH_DBM
655                 if (dbminit(inst->acctusersfile) != 0)
656 #endif
657 #ifdef WITH_NDBM
658                 if ((dbmfile = dbm_open(inst->acctusersfile, O_RDONLY, 0)) == NULL)
659 #endif
660                 {
661                         radlog(L_ERR|L_CONS, "cannot open dbm file %s",
662                                 buffer);
663                         return RLM_MODULE_FAIL;
664                 }
665
666                 r = dbm_find(dbmfile, name, request_pairs, config_pairs,
667                              &reply_pairs);
668                 if (r > 0) found = 1;
669                 if (r <= 0 || fallthrough(*reply_pairs)) {
670
671                   pairdelete(reply_pairs, PW_FALL_THROUGH);
672
673                         sprintf(buffer, "DEFAULT");
674                         i = 0;
675                         while ((r = dbm_find(dbmfile, buffer, request_pairs,
676                                config_pairs, &reply_pairs)) >= 0 || i < 2) {
677                                 if (r > 0) {
678                                         found = 1;
679                                         if (!fallthrough(*reply_pairs))
680                                                 break;
681                                         pairdelete(reply_pairs,PW_FALL_THROUGH);
682                                 }
683                                 sprintf(buffer, "DEFAULT%d", i++);
684                         }
685                 }
686 #ifdef WITH_DBM
687                 dbmclose();
688 #endif
689 #ifdef WITH_NDBM
690                 dbm_close(dbmfile);
691 #endif
692         } else
693         /*
694          *      Note the fallthrough through the #endif.
695          */
696 #endif
697
698         for(pl = inst->acctusers; pl; pl = pl->next) {
699
700                 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
701                         continue;
702
703                 if (paircmp(request_pairs, pl->check, &reply_pairs) == 0) {
704                         DEBUG2("  acct_users: Matched %s at %d",
705                                pl->name, pl->lineno);
706                         found = 1;
707                         check_tmp = paircopy(pl->check);
708                         reply_tmp = paircopy(pl->reply);
709                         pairmove(&reply_pairs, &reply_tmp);
710                         pairmove(config_pairs, &check_tmp);
711                         pairfree(reply_tmp);
712                         pairfree(check_tmp); /* should be NULL */
713                         /*
714                          *      Fallthrough?
715                          */
716                         if (!fallthrough(pl->reply))
717                                 break;
718                 }
719         }
720
721         /*
722          *      See if we succeeded.
723          */
724         if (!found)
725                 return RLM_MODULE_NOOP; /* on to the next module */
726
727         /*
728          *      FIXME: log a warning if there are any reply items other than
729          *      Fallthrough
730          */
731         pairfree(reply_pairs); /* Don't need these */
732
733         return RLM_MODULE_OK;
734 }
735
736 /*
737  *      Clean up.
738  */
739 static int file_detach(void *instance)
740 {
741         struct file_instance *inst = instance;
742         pairlist_free(&inst->users);
743         pairlist_free(&inst->acctusers);
744         free(inst->usersfile);
745         free(inst->acctusersfile);
746         free(inst);
747         return 0;
748 }
749
750
751 /* globally exported name */
752 module_t rlm_files = {
753         "files",
754         0,                              /* type: reserved */
755         file_init,                      /* initialization */
756         file_instantiate,               /* instantiation */
757         file_authorize,                 /* authorization */
758         file_authenticate,              /* authentication */
759         file_preacct,                   /* preaccounting */
760         NULL,                           /* accounting */
761         file_detach,                    /* detach */
762         NULL                            /* destroy */
763 };
764