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