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