pull the patch from the head
[freeradius.git] / src / modules / rlm_fastusers / rlm_fastusers.c
1 /*
2  * rlm_fastusers.c      authorization: Find a user in the hashed "users" file.
3  *                      accounting:    Do nothing.  Auth module only.
4  *
5  * Version:     $Id$
6  *
7  *   This program is free software; you can redistribute it and/or modify
8  *   it under the terms of the GNU General Public License as published by
9  *   the Free Software Foundation; either version 2 of the License, or
10  *   (at your option) any later version.
11  *
12  *   This program is distributed in the hope that it will be useful,
13  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *   GNU General Public License for more details.
16  *
17  *   You should have received a copy of the GNU General Public License
18  *   along with this program; if not, write to the Free Software
19  *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * Copyright 2000  The FreeRADIUS server project
22  * Copyright 2000  Jeff Carneal <jeff@apex.net>
23  */
24
25 #include        "autoconf.h"
26 #include        "libradius.h"
27
28 #include        <sys/socket.h>
29 #include        <sys/time.h>
30 #include        <sys/stat.h>
31
32 #include        <stdio.h>
33 #include        <stdlib.h>
34 #include        <string.h>
35 #include        <pwd.h>
36 #include        <grp.h>
37 #include        <ctype.h>
38 #include        <fcntl.h>
39 #include        <limits.h>
40
41 #include        "radiusd.h"
42 #include        "modules.h"
43
44 struct fastuser_instance {
45         char *compat_mode;
46         int hash_reload;
47
48         /* hash table */
49         long hashsize;
50         PAIR_LIST **hashtable;
51         PAIR_LIST *defaults;
52         PAIR_LIST *acctusers;
53         int stats;
54
55         char *usersfile;
56         char *acctusersfile;
57         time_t next_reload;
58         time_t lastusersload;
59         time_t lastacctusersload;
60 };
61
62 /* Function declarations */
63 static int fallthrough(VALUE_PAIR *vp);
64 static int fastuser_buildhash(struct fastuser_instance *inst);
65 static int fastuser_getfile(struct fastuser_instance *inst, const char *filename, 
66                                                                                                                 PAIR_LIST **default_list, PAIR_LIST **pair_list, 
67                                                                                                                 int isacctfile);
68 static int fastuser_hash(const char *s, long hashtablesize);
69 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *entry, int idx);
70 static PAIR_LIST *fastuser_find(REQUEST *request, PAIR_LIST *user,
71                                                                                                                                 const char *username);
72 static void fastuser_tablestats(PAIR_LIST **hashtable, long size);
73 static int fastuser_passcheck(REQUEST *request, PAIR_LIST *user, const char *name);
74
75 static CONF_PARSER module_config[] = {
76         { "usersfile",     PW_TYPE_STRING_PTR,
77           offsetof(struct fastuser_instance,usersfile), NULL, "${raddbdir}/users_fast" },
78         { "acctusersfile",     PW_TYPE_STRING_PTR,
79           offsetof(struct fastuser_instance,acctusersfile), NULL, "${raddbdir}/acct_users" },
80         { "hashsize",     PW_TYPE_INTEGER,
81           offsetof(struct fastuser_instance,hashsize), NULL, "100000" },
82         { "stats",     PW_TYPE_BOOLEAN,
83           offsetof(struct fastuser_instance,stats), NULL, "no" },
84         { "compat",        PW_TYPE_STRING_PTR,
85           offsetof(struct fastuser_instance,compat_mode), NULL, "cistron" },
86         { "hash_reload",     PW_TYPE_INTEGER,
87           offsetof(struct fastuser_instance,hash_reload), NULL, "600" },
88         { NULL, -1, 0, NULL, NULL }
89 };
90
91 /*
92  * See if a VALUE_PAIR list contains Fall-Through = Yes
93  */
94 static int fallthrough(VALUE_PAIR *vp)
95 {
96         VALUE_PAIR *tmp;
97         tmp = pairfind(vp, PW_FALL_THROUGH);
98         return tmp ? tmp->lvalue : 0;
99 }
100
101 /*
102  *       returncheck - Check for Auth-Type = Reject and return appropriate
103  *                     module return code if it is found.
104  */
105 static int rad_check_return(VALUE_PAIR *list)
106 {
107       VALUE_PAIR      *authtype;
108
109       /* 
110        * We check for Auth-Type = Reject here
111        */
112
113       authtype = pairfind(list, PW_AUTHTYPE);
114       if((authtype) && authtype->lvalue == PW_AUTHTYPE_REJECT)  {
115               DEBUG2("rad_check_return:  Auth-Type is Reject");
116               return RLM_MODULE_REJECT;
117       }
118
119       return RLM_MODULE_UPDATED;
120 }
121
122 static int fastuser_buildhash(struct fastuser_instance *inst) {
123         long memsize=0;
124         int rcode, hashindex;
125         PAIR_LIST **newhash=NULL, **oldhash=NULL;
126         PAIR_LIST *newdefaults=NULL, *newacctusers, *cur=NULL;
127         PAIR_LIST *olddefaults=NULL, *oldacctusers=NULL;
128         struct stat statbuf;
129         int reloadusers = 1;
130         int reloadacctusers = 1;
131
132         /* 
133          * Allocate space for hash table here
134          */
135         memsize = sizeof(PAIR_LIST *) * inst->hashsize;
136
137         newhash = (PAIR_LIST **) rad_malloc(memsize);
138
139         memset((PAIR_LIST *)newhash, 0, memsize);
140
141         /* Check acct_users last modification time */
142         if ((stat(inst->acctusersfile, &statbuf) != -1)
143          && (statbuf.st_mtime <= inst->lastacctusersload)) {
144                 DEBUG2("rlm_fastusers:  File %s was unchanged. Not reloading.",
145                         inst->acctusersfile);
146                 reloadacctusers = 0;
147                 rcode = 0;
148         }
149         else
150         /* Read acct_users */
151         rcode = fastuser_getfile(inst, inst->acctusersfile, NULL, &newacctusers, 1);
152
153         if (rcode != 0) {
154                 radlog(L_ERR|L_CONS, "rlm_fastusers:  Errors reading %s", inst->usersfile);
155                 return -1;
156         }
157
158         /* Check users last modification time */
159         if ((stat(inst->usersfile, &statbuf) != -1)
160          && (statbuf.st_mtime <= inst->lastusersload)) {
161                 DEBUG2("rlm_fastusers:  File %s was unchanged. Not reloading.",
162                         inst->usersfile);
163                 reloadusers = 0;
164                 rcode = 0;
165                 /* This was allocated earlier but will remain unused */
166                 free(newhash);
167         }
168         else
169         /* Read users */
170         rcode = fastuser_getfile(inst, inst->usersfile, &newdefaults, newhash, 0);
171
172         if (rcode != 0) {
173                 radlog(L_ERR|L_CONS, "rlm_fastusers:  Errors reading %s", inst->usersfile);
174                 return -1;
175         }
176
177         if (reloadusers) {
178                 /*
179                  * We need to do this now so that users auths
180                  * aren't blocked while we free the old table
181                  * below
182                  */
183                 inst->lastusersload = time(NULL);
184                 oldhash = inst->hashtable;
185                 inst->hashtable = newhash;
186                 olddefaults = inst->defaults;
187                 inst->defaults = newdefaults;
188
189                 /*
190                  * When we get here, we assume the hash built properly.
191                  * So we begin to tear down the old one
192                  */
193                 if (oldhash) {
194                         for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
195                                 if(oldhash[hashindex]) {
196                                         cur = oldhash[hashindex];
197                                         pairlist_free(&cur);
198                                 }
199                         } 
200                         free(oldhash);
201                 }
202                 pairlist_free(&olddefaults);
203         }
204         if (reloadacctusers) {
205                 inst->lastacctusersload = time(NULL);
206                 oldacctusers = inst->acctusers;
207                 inst->acctusers = newacctusers;
208                 pairlist_free(&oldacctusers);
209         }
210
211         if(inst->stats) 
212                 fastuser_tablestats(inst->hashtable, inst->hashsize);
213
214         return 0;       
215 }
216
217 static int fastuser_getfile(struct fastuser_instance *inst, const char *filename, 
218                                                                                                                 PAIR_LIST **default_list, PAIR_LIST **pair_list, 
219                                                                                                                 int isacctfile) {
220         int rcode;
221         PAIR_LIST *users = NULL;
222         PAIR_LIST *entry=NULL, *next=NULL, *cur=NULL, *defaults=NULL, *lastdefault=NULL;
223         int compat_mode = FALSE;
224         VALUE_PAIR *vp=NULL;
225         int hashindex = 0;
226         long numdefaults = 0, numusers=0;
227
228         radlog(L_INFO, " fastusers:  Reading %s", filename);
229         rcode = pairlist_read(filename, &users, 1);
230         if (rcode < 0) {
231                 return -1;
232         }
233
234         if (strcmp(inst->compat_mode, "cistron") == 0) {
235                 compat_mode = TRUE;
236         }
237         
238         entry = users;
239         while (entry) {
240                 if (compat_mode) {
241                         DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
242                                 filename, entry->lineno, entry->name);
243                 }
244
245                 /*
246                  *      Look for improper use of '=' in the
247                  *      check items.  They should be using
248                  *      '==' for on-the-wire RADIUS attributes,
249                  *      and probably ':=' for server
250                  *      configuration items.
251                  */
252                 for (vp = entry->check; vp != NULL; vp = vp->next) {
253                         /*
254                          *      Ignore attributes which are set
255                          *      properly.
256                          */
257                         if (vp->operator != T_OP_EQ) 
258                                 continue;
259                                 
260
261                         /*
262                          *      If it's a vendor attribute,
263                          *      or it's a wire protocol, 
264                          *      ensure it has '=='.
265                          */
266                         if (((vp->attribute & ~0xffff) != 0) ||
267                                 (vp->attribute < 0x100)) {
268                                 if (!compat_mode) {
269                                         DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
270                                         filename, entry->lineno, vp->name, vp->name, entry->name);
271                                 } else {
272                                         DEBUG("\tChanging '%s =' to '%s =='",
273                                                 vp->name, vp->name);
274                                 }
275                                 vp->operator = T_OP_CMP_EQ;
276                                 continue;
277                         }
278                                 
279                         /*
280                          *      Cistron Compatibility mode.
281                          *
282                          *      Re-write selected attributes
283                          *      to be '+=', instead of '='.
284                          *
285                          *      All others get set to '=='
286                          */
287                         if (compat_mode) {
288                                 /*
289                                  *      Non-wire attributes become +=
290                                  *
291                                  *      On the write attributes
292                                  *      become ==
293                                  */
294                                 if ((vp->attribute >= 0x100) &&
295                                         (vp->attribute <= 0xffff) &&
296                                         (vp->attribute != PW_HINT) &&
297                                         (vp->attribute != PW_HUNTGROUP_NAME)) {
298                                         DEBUG("\tChanging '%s =' to '%s +='",
299                                                 vp->name, vp->name);
300                                                 vp->operator = T_OP_ADD;
301                                 } else {
302                                         DEBUG("\tChanging '%s =' to '%s =='",
303                                                 vp->name, vp->name);
304                                         vp->operator = T_OP_CMP_EQ;
305                                 }
306                         }
307                                 
308                 } /* end of loop over check items */
309                 
310                 
311                 /*
312                  *      Look for server configuration items
313                  *      in the reply list.
314                  *
315                  *      It's a common enough mistake, that it's
316                  *      worth doing.
317                  */
318                 for (vp = entry->reply; vp != NULL; vp = vp->next) {
319                         /*
320                          *      If it's NOT a vendor attribute,
321                          *      and it's NOT a wire protocol
322                          *      and we ignore Fall-Through,
323                          *      then bitch about it, giving a
324                          *      good warning message.
325                          */
326                         if (!(vp->attribute & ~0xffff) &&
327                                 (vp->attribute > 0xff) &&
328                                 (vp->attribute > 1000)) {
329                                 log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
330                                         "\tfound in reply item list for user \"%s\".\n"
331                                         "\tThis attribute MUST go on the first line"
332                                         " with the other check items", 
333                                         filename, entry->lineno, vp->name,
334                                         entry->name);
335                         }
336                 }
337
338                 /*
339                  * Ok, we've done all the same BS as
340                  * rlm_users, so here we tear apart the
341                  * linked list, and store our users in
342                  * the hashtable we've built instead
343                  */
344
345                 /* Save what was next */
346                 next = entry->next;
347
348                 if(!isacctfile) {
349                         /* Save the DEFAULT entry specially */
350                         if(strcmp(entry->name, "DEFAULT")==0) {
351                                 
352                                 /* Save this as the last default we've seen */
353                                 lastdefault = entry;
354                                 numdefaults++;
355         
356                                 /* put it at the end of the list */
357                                 if(defaults) {
358                                         for(cur=defaults; cur->next; cur=cur->next);
359                                         cur->next = entry;
360                                         entry->next = NULL;
361                                 } else {
362                                         defaults = entry;
363                                         defaults->next = NULL; 
364                                 }
365         
366                         } else {
367                                 numusers++;
368         
369                                 /* Hash the username */
370                                 hashindex = fastuser_hash(entry->name, inst->hashsize);
371         
372                                 /* Store the last default before this entry */
373                                 entry->lastdefault = lastdefault;
374         
375                                 /* Store user in the hash */
376                                 fastuser_store(pair_list, entry, hashindex);
377                         }
378                 }
379                 /* Restore entry to next pair_list */
380                 entry = next;
381
382         } /* while(entry) loop */
383
384         if(!isacctfile && (default_list)) {
385                 *default_list = defaults;
386                 radlog(L_INFO, "rlm_fastusers:  Loaded %ld users and %ld defaults",
387                                         numusers, numdefaults);
388         } else {
389                 *pair_list = users;
390         }
391
392         return 0;
393 }
394
395 /* Hashes the username sent to it and returns index into hashtable */
396 int fastuser_hash(const char *s, long hashtablesize) {
397         unsigned long hash = 0;
398
399         while (*s != '\0') {
400                 hash = hash * 7907 + (unsigned char)*s++;
401         }
402
403         return (hash % hashtablesize);
404 }
405
406 /* Stores the username sent into the hashtable */
407 static int fastuser_store(PAIR_LIST **hashtable, PAIR_LIST *new, int idx) {
408         PAIR_LIST *cur;
409
410         cur = hashtable[idx];
411         /* store new record at end of list */
412         if(cur) {
413                 while (cur->next != NULL) 
414                         cur=cur->next;
415                 cur->next = new;
416                 new->next = NULL;
417         } else {
418                 new->next = hashtable[idx];
419                 hashtable[idx] = new;
420         }
421         return 1;
422 }
423
424 /*
425  * Looks up user in hashtable.  If user can't be found, returns 0.
426  * Otherwise returns a pointer to the structure for the user
427  */
428 static PAIR_LIST *fastuser_find(REQUEST *request, PAIR_LIST *user, 
429                                             const char *username)
430 {
431         PAIR_LIST *cur=user;
432         int userfound = 0;
433
434         /*
435          * Now we have to make sure it's the right user by
436          * comparing the check pairs
437          */
438         while((cur) && (!userfound)) {
439                 if((strcmp(cur->name, username)==0) &&
440                                 paircmp(request, request->packet->vps, cur->check, &request->reply->vps) == 0) {
441                         /*
442                          * Usercollide means we have to compare check pairs
443                          * AND the password
444                          */
445                         if(mainconfig.do_usercollide) {
446                                 if((userfound = fastuser_passcheck(request, cur, username))==0) {
447                                         cur = cur->next;
448                                 } 
449
450                         } else {
451                                 userfound = 1;
452                                 DEBUG2("  fastusers: Matched %s at %d", cur->name, cur->lineno);
453                         }
454                 } else {
455                         cur = cur->next;
456                 }
457         }
458
459         if(cur) {
460                 return cur;
461         }
462
463         return (PAIR_LIST *)0;
464 }
465
466 /*
467  * Generate and log statistics about our hash table
468  */
469 static void fastuser_tablestats(PAIR_LIST **hashtable, long size) {
470         int i, count;
471         int countarray[256];
472         int toomany=0;
473         PAIR_LIST *cur;
474
475         memset(countarray, 0, sizeof(countarray));
476
477         for(i=0; i<size; i++) {
478                 count = 0;
479                 for(cur=hashtable[i]; cur; cur=cur->next) {
480                         count++;
481                 }
482                 if(count<256) {
483                         countarray[count]++;
484                 } else {
485                         toomany++;
486                 }
487         }
488
489         for(i=0; i<256; i++) 
490                 if(countarray[i]) {
491                         radlog(L_INFO, "rlm_fastusers:  Hash buckets with %d users:  %d",
492                                                 i, countarray[i]);
493                 }
494
495         if(toomany) {
496                 radlog(L_INFO, "rlm_fastusers:  Hash buckets with more than 256:  %d", 
497                                         toomany);
498         }
499 }
500
501 static int fastuser_passcheck(REQUEST *request, PAIR_LIST *user, const char *name)
502 {
503         int found=0;
504         VALUE_PAIR      *check_save;
505
506         /* 
507          * We check for REJECT specially here or a REJECT
508          * user will never match
509          */
510         check_save = pairfind(user->check, PW_AUTHTYPE);
511         if((check_save) && check_save->lvalue == PW_AUTHTYPE_REJECT)  {
512                 DEBUG2("  fastusers(uc):  User '%s' line %d is Auth-Type Reject, but usercollide match", 
513                                         user->name, user->lineno);
514                 return 1;
515         }
516
517         /* Save the orginal config items */
518         check_save = request->config_items;
519         request->config_items = NULL;
520         
521         DEBUG2("  fastusers(uc): Checking %s at %d", user->name, user->lineno);
522
523         /* Copy this users check pairs to the request */
524         request->config_items = paircopy(user->check);
525
526         /* Check the req to see if we matched */
527         if(rad_check_password(request)==0) {
528                 DEBUG2("  fastusers(uc): Matched %s at %d", user->name, user->lineno);
529                 found = 1;
530         }
531
532         /* Restore check items */
533         pairfree(&request->config_items); 
534         request->config_items = check_save;
535
536         return found;
537 }
538
539 /*
540  *      (Re-)read the "users" file into memory.
541  */
542 static int fastuser_instantiate(CONF_SECTION *conf, void **instance)
543 {
544         struct fastuser_instance *inst=0;
545
546         inst = rad_malloc(sizeof *inst);
547         if (!inst)
548                 return -1;
549         memset(inst, 0, sizeof(*inst));
550
551         if (cf_section_parse(conf, inst, module_config) < 0) {
552                 free(inst);
553                 return -1;
554         }
555
556         inst->next_reload = time(NULL) + inst->hash_reload;
557         inst->hashtable = NULL;
558         inst->lastusersload = 0;
559         inst->lastacctusersload = 0;
560         if(fastuser_buildhash(inst) < 0) {
561                 radlog(L_ERR, "rlm_fastusers:  error building user hash.  aborting");
562                 return -1;
563         }
564
565         /*
566          * Need code here to read acct_users file
567          */
568
569         *instance = inst;
570         return 0;
571 }
572
573 /*
574  *      Find the named user in the database.  Create the
575  *      set of attribute-value pairs to check and reply with
576  *      for this user from the database. The main code only
577  *      needs to check the password, the rest is done here.
578  */
579 static int fastuser_authorize(void *instance, REQUEST *request)
580 {
581
582         VALUE_PAIR      *namepair;
583         VALUE_PAIR      *check_tmp;
584         VALUE_PAIR      *reply_tmp;
585         PAIR_LIST               *user;
586         PAIR_LIST               *curdefault;
587         const char      *name;
588         int                     userfound=0;
589         int                     defaultfound=0;
590         int                     hashidx=0;
591         struct fastuser_instance *inst = instance;
592
593         /*
594          * Do we need to reload the cache?
595          * Really we should spawn a thread to do this
596          */
597         if((inst->hash_reload) && (request->timestamp > inst->next_reload)) {
598                 inst->next_reload = request->timestamp + inst->hash_reload;
599                 radlog(L_INFO, "rlm_fastusers:  Reloading fastusers hash");
600                 if(fastuser_buildhash(inst) < 0) {
601                         radlog(L_ERR, "rlm_fastusers:  error building user hash.  aborting");
602                         exit(1);
603                 }
604         }
605
606         /*
607          *      Grab the canonical user name.
608          */
609         namepair = request->username;
610         name = namepair ? (char *) namepair->strvalue : "NONE";
611
612         /*
613          *      Find the entry for the user.
614          */
615         hashidx = fastuser_hash(name, inst->hashsize);
616         user = inst->hashtable[hashidx];
617         if((user=fastuser_find(request, user, name))!=NULL) {
618                 userfound = 1;          
619         }
620
621         /* 
622          * If there's no lastdefault and we
623          * don't fallthrough, just copy the
624          * pairs for this user and return
625          */
626         if((user) && (userfound) && (user->lastdefault == NULL)) {
627                 DEBUG2("rlm_fastusers:  user found before DEFAULT");
628
629                 check_tmp = paircopy(user->check);
630                 pairmove(&request->config_items, &check_tmp);
631                 pairfree(&check_tmp); 
632
633                 reply_tmp = paircopy(user->reply);
634                 pairmove(&request->reply->vps, &reply_tmp);
635                 pairfree(&reply_tmp);
636
637                 if(!fallthrough(user->reply)) {
638                         pairdelete(&request->reply->vps, PW_FALL_THROUGH);
639                         return(rad_check_return(user->check));
640                 } else {
641                         user=user->next;
642                         user=fastuser_find(request, user, name);
643                 }
644         }
645
646         /* 
647          * When we get here, we've either found 
648          * the user or not, but to preserve order
649          * we start at the top of the default
650          * list and work our way thru
651          * When we get to the user's 'lastdefault'
652          * we check to see if we should stop
653          * and return
654          */
655         DEBUG2("rlm_fastusers:  checking defaults");
656                         
657         curdefault = inst->defaults;
658         while(curdefault) {
659                 if(paircmp(request, request->packet->vps, curdefault->check, 
660                                                         &request->reply->vps) == 0) {
661                         DEBUG2("  fastusers: Matched %s at %d", 
662                                                         curdefault->name, curdefault->lineno);
663                         defaultfound = 1;
664
665                         check_tmp = paircopy(curdefault->check);
666                         pairmove(&request->config_items, &check_tmp);
667                         pairfree(&check_tmp); 
668
669                         reply_tmp = paircopy(curdefault->reply);
670                         pairmove(&request->reply->vps, &reply_tmp);
671                         pairfree(&reply_tmp);
672
673                         /*                         
674                          * There's no fallthru on this default which    
675                          * is *before* we find the user in the file,         
676                          * so we know it's safe to quit here                        
677                          */
678                         if (!fallthrough(curdefault->reply))
679                           break;
680
681                 }
682
683                 /*
684                  * If we found the user, we want to stop
685                  * processing once we get to 'lastdefault'
686                  * This way we can process this user's entry
687                  * in the order it was found in the file
688                  */
689                 while((userfound && (user) && (curdefault == user->lastdefault))) {
690                                 DEBUG2("  fastusers:  found lastdefault at line %d",
691                                                    curdefault->lineno);
692
693                         check_tmp = paircopy(user->check);
694                         pairmove(&request->config_items, &check_tmp);
695                         pairfree(&check_tmp); 
696
697                         reply_tmp = paircopy(user->reply);
698                         pairmove(&request->reply->vps, &reply_tmp);
699                         pairfree(&reply_tmp);
700
701                         if(!fallthrough(user->reply)) {
702                                 pairdelete(&request->reply->vps, PW_FALL_THROUGH);
703                                 return(rad_check_return(user->check));
704                         }
705
706                         /* 
707                          * Find next occurence of THIS user in
708                          * the users file
709                          */
710                         user=user->next;
711                         user=fastuser_find(request, user, name);
712                 } 
713
714                 curdefault = curdefault->next;
715         }
716
717         if(userfound || defaultfound) {
718                 pairdelete(&request->reply->vps, PW_FALL_THROUGH);
719                 return(rad_check_return(request->config_items));
720         } else {
721                 DEBUG2("rlm_fastusers:  user not found");
722                 return RLM_MODULE_NOTFOUND;
723         }
724 }
725
726 /*
727  *      Authentication - unused.
728  */
729 static int fastuser_authenticate(void *instance, REQUEST *request)
730 {
731         instance = instance;
732         request = request;
733         return RLM_MODULE_OK;
734 }
735
736 /*
737  *      Pre-Accounting - read the acct_users file for check_items and
738  *      config_items. Reply items are Not Recommended(TM) in acct_users,
739  *      except for Fallthrough, which should work
740  *
741  *      This function is mostly a copy of file_authorize
742  */
743 static int fastuser_preacct(void *instance, REQUEST *request)
744 {
745         VALUE_PAIR      *namepair;
746         const char      *name;
747         VALUE_PAIR      *request_pairs;
748         VALUE_PAIR      **config_pairs;
749         VALUE_PAIR      *reply_pairs = NULL;
750         VALUE_PAIR      *check_tmp;
751         VALUE_PAIR      *reply_tmp;
752         PAIR_LIST       *pl = NULL;
753         int             found = 0;
754         struct fastuser_instance *inst = instance;
755
756         namepair = request->username;
757         name = namepair ? (char *) namepair->strvalue : "NONE";
758         request_pairs = request->packet->vps;
759         config_pairs = &request->config_items;
760         
761         /*
762          *      Find the entry for the user.
763          */
764         for (pl = inst->acctusers; pl; pl = pl->next) {
765
766                 if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
767                         continue;
768
769                 if (paircmp(request, request_pairs, pl->check, &reply_pairs) == 0) {
770                         DEBUG2("  acct_users: Matched %s at %d",
771                                pl->name, pl->lineno);
772                         found = 1;
773                         check_tmp = paircopy(pl->check);
774                         reply_tmp = paircopy(pl->reply);
775                         pairmove(&reply_pairs, &reply_tmp);
776                         pairmove(config_pairs, &check_tmp);
777                         pairfree(&reply_tmp);
778                         pairfree(&check_tmp); /* should be NULL */
779                         /*
780                          *      Fallthrough?
781                          */
782                         if (!fallthrough(pl->reply))
783                                 break;
784                 }
785         }
786
787         /*
788          *      See if we succeeded.
789          */
790         if (!found)
791                 return RLM_MODULE_NOOP; /* on to the next module */
792
793         /*
794          *      FIXME: log a warning if there are any reply items other than
795          *      Fallthrough
796          */
797         pairfree(&reply_pairs); /* Don't need these */
798
799         return RLM_MODULE_OK;
800 }
801
802 /*
803  *  Clean up.
804  */
805 static int fastuser_detach(void *instance)
806 {
807         struct fastuser_instance *inst = instance;
808         int hashindex;
809         PAIR_LIST *cur;
810
811         /* Free hash table */
812         for(hashindex=0; hashindex<inst->hashsize; hashindex++) {
813                 if(inst->hashtable[hashindex]) {
814                         cur = inst->hashtable[hashindex];
815                         pairlist_free(&cur);
816                 }
817         } 
818
819         free(inst->compat_mode);
820         free(inst->hashtable);
821         pairlist_free(&inst->defaults);
822         pairlist_free(&inst->acctusers);
823         free(inst->usersfile);
824         free(inst->acctusersfile);
825         free(inst);
826         return 0;
827 }
828
829 /*
830  *      This function is unused
831  */
832 static int fastuser_accounting(void *instance, REQUEST *request)
833 {
834         return RLM_MODULE_FAIL;
835 }
836
837 /* globally exported name */
838 module_t rlm_fastusers = {
839         "fastusers",
840         0,                              /* type: reserved */
841         NULL,                           /* initialization */
842         fastuser_instantiate,           /* instantiation */
843         {
844                 fastuser_authenticate,  /* authentication */
845                 fastuser_authorize,     /* authorization */
846                 fastuser_preacct,       /* preaccounting */
847                 fastuser_accounting,    /* accounting */
848                 NULL,                   /* checksimul */
849                 NULL,                   /* pre-proxy */
850                 NULL,                   /* post-proxy */
851                 NULL                    /* post-auth */
852         },
853         fastuser_detach,                /* detach */
854         NULL                            /* destroy */
855 };
856