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