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