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