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