Enable building #WITHOUT_PROXY
[freeradius.git] / src / modules / rlm_files / rlm_files.c
index ace4cd4..e0bc59a 100644 (file)
 /*
  * rlm_files.c authorization: Find a user in the "users" file.
- *             accounting:    Write the "detail" files.
  *
  * Version:    $Id$
  *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ *
+ * Copyright 2002,2006  The FreeRADIUS server project
+ * Copyright 2000  Jeff Carneal <jeff@apex.net>
  */
 
-static const char rcsid[] = "$Id$";
-
-#include       "autoconf.h"
-
-#include       "radiusd.h"
+#include       <freeradius-devel/ident.h>
+RCSID("$Id$")
 
-#include       <sys/socket.h>
-#include       <sys/stat.h>
-#include       <netinet/in.h>
+#include       <freeradius-devel/radiusd.h>
+#include       <freeradius-devel/modules.h>
 
-#include       <stdlib.h>
-#include       <string.h>
-#include       <netdb.h>
 #include       <ctype.h>
 #include       <fcntl.h>
-#include        <limits.h>
-
-#if HAVE_MALLOC_H
-#  include     <malloc.h>
-#endif
-
-#include       "modules.h"
-
-#ifdef WITH_DBM
-#  include     <dbm.h>
-#endif
-#ifdef WITH_NDBM
-#  include     <ndbm.h>
-#endif
+#include       <limits.h>
 
 struct file_instance {
        char *compat_mode;
 
-        /* autz */
-        char *usersfile;
-        PAIR_LIST *users;
+       char *key;
 
-        /* preacct */
-        char *acctusersfile;
-        PAIR_LIST *acctusers;
-};
+       /* autz */
+       char *usersfile;
+       fr_hash_table_t *users;
 
-#if defined(WITH_DBM) || defined(WITH_NDBM)
-/*
- *     See if a potential DBM file is present.
- */
-static int checkdbm(char *users, char *ext)
-{
-       char buffer[256];
-       struct stat st;
 
-       strcpy(buffer, users);
-       strcat(buffer, ext);
+       /* authenticate */
+       char *auth_usersfile;
+       fr_hash_table_t *auth_users;
 
-       return stat(buffer, &st);
-}
+       /* preacct */
+       char *acctusersfile;
+       fr_hash_table_t *acctusers;
 
-/*
- *     Find the named user in the DBM user database.
- *     Returns: -1 not found
- *               0 found but doesn't match.
- *               1 found and matches.
- */
-static int dbm_find(DBM *dbmfile, char *name, VALUE_PAIR *request_pairs,
-               VALUE_PAIR **check_pairs, VALUE_PAIR **reply_pairs)
-{
-       datum           named;
-       datum           contentd;
-       char            *ptr;
-       VALUE_PAIR      *check_tmp;
-       VALUE_PAIR      *reply_tmp;
-       int             ret = 0;
+#ifdef WITH_PROXY
+       /* pre-proxy */
+       char *preproxy_usersfile;
+       fr_hash_table_t *preproxy_users;
 
-       named.dptr = name;
-       named.dsize = strlen(name);
-#ifdef WITH_DBM
-       contentd = fetch(named);
+       /* post-proxy */
+       char *postproxy_usersfile;
+       fr_hash_table_t *postproxy_users;
 #endif
-#ifdef WITH_NDBM
-       contentd = dbm_fetch(dbmfile, named);
-#endif
-       if(contentd.dptr == NULL)
-               return -1;
-
-       check_tmp = NULL;
-       reply_tmp = NULL;
-
-       /*
-        *      Parse the check values
-        */
-       ptr = contentd.dptr;
-       contentd.dptr[contentd.dsize] = '\0';
 
-       if (*ptr != '\n' && userparse(ptr, &check_tmp) != 0) {
-               radlog(L_ERR|L_CONS, "Parse error (check) for user %s", name);
-               pairfree(check_tmp);
-               return -1;
-       }
-       while(*ptr != '\n' && *ptr != '\0') {
-               ptr++;
-       }
-       if(*ptr != '\n') {
-               radlog(L_ERR|L_CONS, "Parse error (no reply pairs) for user %s",
-                       name);
-               pairfree(check_tmp);
-               return -1;
-       }
-       ptr++;
-
-       /*
-        *      Parse the reply values
-        */
-       if (userparse(ptr, &reply_tmp) != 0) {
-               radlog(L_ERR|L_CONS, "Parse error (reply) for user %s", name);
-               pairfree(check_tmp);
-               pairfree(reply_tmp);
-               return -1;
-       }
-
-       /*
-        *      See if the check_pairs match.
-        */
-       if (paircmp(request_pairs, check_tmp, reply_pairs) == 0) {
-               ret = 1;
-               pairmove(reply_pairs, &reply_tmp);
-               pairmove2(reply_pairs, &reply_tmp, PW_FALL_THROUGH);
-               pairmove(check_pairs, &check_tmp);
-       }
-       pairfree(reply_tmp);
-       pairfree(check_tmp);
+       /* post-authenticate */
+       char *postauth_usersfile;
+       fr_hash_table_t *postauth_users;
+};
 
-       return ret;
-}
-#endif /* DBM */
 
 /*
  *     See if a VALUE_PAIR list contains Fall-Through = Yes
@@ -147,109 +71,129 @@ static int dbm_find(DBM *dbmfile, char *name, VALUE_PAIR *request_pairs,
 static int fallthrough(VALUE_PAIR *vp)
 {
        VALUE_PAIR *tmp;
+       tmp = pairfind(vp, PW_FALL_THROUGH, 0);
 
-       tmp = pairfind(vp, PW_FALL_THROUGH);
-
-       return tmp ? tmp->lvalue : 0;
+       return tmp ? tmp->vp_integer : 0;
 }
 
+static const CONF_PARSER module_config[] = {
+       { "usersfile",     PW_TYPE_FILENAME,
+         offsetof(struct file_instance,usersfile), NULL, NULL },
+       { "acctusersfile", PW_TYPE_FILENAME,
+         offsetof(struct file_instance,acctusersfile), NULL, NULL },
+#ifdef WITH_PROXY
+       { "preproxy_usersfile", PW_TYPE_FILENAME,
+         offsetof(struct file_instance,preproxy_usersfile), NULL, NULL },
+       { "postproxy_usersfile", PW_TYPE_FILENAME,
+         offsetof(struct file_instance,postproxy_usersfile), NULL, NULL },
+#endif
+       { "auth_usersfile", PW_TYPE_FILENAME,
+         offsetof(struct file_instance,auth_usersfile), NULL, NULL },
+       { "postauth_usersfile", PW_TYPE_FILENAME,
+         offsetof(struct file_instance,postauth_usersfile), NULL, NULL },
+       { "compat",        PW_TYPE_STRING_PTR,
+         offsetof(struct file_instance,compat_mode), NULL, "cistron" },
+       { "key",           PW_TYPE_STRING_PTR,
+         offsetof(struct file_instance,key), NULL, NULL },
+       { NULL, -1, 0, NULL, NULL }
+};
 
 
-static int file_init(void)
+static uint32_t pairlist_hash(const void *data)
 {
-       return 0;
+       return fr_hash_string(((const PAIR_LIST *)data)->name);
 }
 
-/*
- *     A temporary holding area for config values to be extracted
- *     into, before they are copied into the instance data
- */
-static struct file_instance config;
+static int pairlist_cmp(const void *a, const void *b)
+{
+       return strcmp(((const PAIR_LIST *)a)->name,
+                     ((const PAIR_LIST *)b)->name);
+}
 
-static CONF_PARSER module_config[] = {
-        { "usersfile",     PW_TYPE_STRING_PTR, &config.usersfile, RADIUS_USERS },
-        { "acctusersfile", PW_TYPE_STRING_PTR, &config.acctusersfile, RADIUS_ACCT_USERS },
-       { "compat",        PW_TYPE_STRING_PTR, &config.compat_mode, "cistron" },
-       { NULL, -1, NULL, NULL }
-};
+static void my_pairlist_free(void *data)
+{
+       PAIR_LIST *pl = data;
 
-static int getusersfile(const char *filename, PAIR_LIST **pair_list)
+       pairlist_free(&pl);
+}
+
+
+static int getusersfile(const char *filename, fr_hash_table_t **pht,
+                       char *compat_mode_str)
 {
        int rcode;
-        PAIR_LIST *users = NULL;
-#if defined(WITH_DBM) || defined(WITH_NDBM)
-        if (!use_dbm &&
-            (checkdbm(filename, ".dir") == 0 ||
-             checkdbm(filename, ".db") == 0)) {
-                radlog(L_INFO|L_CONS, "DBM files found but no -b flag " "given - NOT using DBM");
-        }
-#endif
+       PAIR_LIST *users = NULL;
+       PAIR_LIST *entry, *next;
+       fr_hash_table_t *ht, *tailht;
+       int order = 0;
+
+       if (!filename) {
+               *pht = NULL;
+               return 0;
+       }
 
-        if (!use_dbm) {
-               rcode = pairlist_read(filename, &users, 1);
-               if (rcode < 0) {
-                       return -1;
-               }
+       rcode = pairlist_read(filename, &users, 1);
+       if (rcode < 0) {
+               return -1;
        }
 
-        /*
-         *     Walk through the 'users' file list, if we're debugging,
+       /*
+             Walk through the 'users' file list, if we're debugging,
         *      or if we're in compat_mode.
-         */
-        if ((debug_flag) ||
-           (strcmp(config.compat_mode, "cistron") == 0)) {
-                PAIR_LIST *entry;
-                VALUE_PAIR *vp;
+        */
+       if ((debug_flag) ||
+           (strcmp(compat_mode_str, "cistron") == 0)) {
+               VALUE_PAIR *vp;
                int compat_mode = FALSE;
 
-               if (strcmp(config.compat_mode, "cistron") == 0) {
+               if (strcmp(compat_mode_str, "cistron") == 0) {
                        compat_mode = TRUE;
                }
-        
-                entry = users;
-                while (entry) {
+
+               entry = users;
+               while (entry) {
                        if (compat_mode) {
                                DEBUG("[%s]:%d Cistron compatibility checks for entry %s ...",
-                                     filename, entry->lineno,
-                                     entry->name);
+                                               filename, entry->lineno,
+                                               entry->name);
                        }
 
-                        /*
-                         *     Look for improper use of '=' in the
-                         *     check items.  They should be using
-                         *     '==' for on-the-wire RADIUS attributes,
-                         *     and probably ':=' for server
-                         *     configuration items.
-                         */
-                        for (vp = entry->check; vp != NULL; vp = vp->next) {
-                                /*
-                                 *     Ignore attributes which are set
-                                 *     properly.
-                                 */
-                                if (vp->operator != T_OP_EQ) {
-                                        continue;
-                                }
-
-                                /*
-                                 *     If it's a vendor attribute,
-                                 *     or it's a wire protocol, 
-                                 *     ensure it has '=='.
-                                 */
-                                if (((vp->attribute & ~0xffff) != 0) ||
-                                    (vp->attribute < 0x100)) {
+                       /*
+                             Look for improper use of '=' in the
+                             check items.  They should be using
+                             '==' for on-the-wire RADIUS attributes,
+                             and probably ':=' for server
+                             configuration items.
+                        */
+                       for (vp = entry->check; vp != NULL; vp = vp->next) {
+                               /*
+                                     Ignore attributes which are set
+                                     properly.
+                                */
+                               if (vp->operator != T_OP_EQ) {
+                                       continue;
+                               }
+
+                               /*
+                                     If it's a vendor attribute,
+                                *      or it's a wire protocol,
+                                     ensure it has '=='.
+                                */
+                               if ((vp->vendor != 0) ||
+                                               (vp->attribute < 0x100)) {
                                        if (!compat_mode) {
                                                DEBUG("[%s]:%d WARNING! Changing '%s =' to '%s =='\n\tfor comparing RADIUS attribute in check item list for user %s",
-                                                     filename, entry->lineno,
-                                                     vp->name, vp->name,
-                                                     entry->name);
+                                                               filename, entry->lineno,
+                                                               vp->name, vp->name,
+                                                               entry->name);
                                        } else {
                                                DEBUG("\tChanging '%s =' to '%s =='",
-                                                     vp->name, vp->name);
+                                                               vp->name, vp->name);
                                        }
                                        vp->operator = T_OP_CMP_EQ;
                                        continue;
-                                }
-                               
+                               }
+
                                /*
                                 *      Cistron Compatibility mode.
                                 *
@@ -266,423 +210,279 @@ static int getusersfile(const char *filename, PAIR_LIST **pair_list)
                                         *      become ==
                                         */
                                        if ((vp->attribute >= 0x100) &&
-                                           (vp->attribute <= 0xffff) &&
-                                           (vp->attribute != PW_HINT) &&
-                                           (vp->attribute != PW_HUNTGROUP_NAME)) {
+                                                       (vp->attribute <= 0xffff) &&
+                                                       (vp->attribute != PW_HINT) &&
+                                                       (vp->attribute != PW_HUNTGROUP_NAME)) {
                                                DEBUG("\tChanging '%s =' to '%s +='",
-                                                     vp->name, vp->name);
+                                                               vp->name, vp->name);
                                                vp->operator = T_OP_ADD;
                                        } else {
                                                DEBUG("\tChanging '%s =' to '%s =='",
-                                                     vp->name, vp->name);
+                                                               vp->name, vp->name);
                                                vp->operator = T_OP_CMP_EQ;
                                        }
                                }
-                               
-                        } /* end of loop over check items */
-                
-                
-                        /*
-                         *     Look for server configuration items
-                         *     in the reply list.
-                         *
-                         *     It's a common enough mistake, that it's
-                         *     worth doing.
-                         */
-                        for (vp = entry->reply; vp != NULL; vp = vp->next) {
-                                /*
-                                 *     If it's NOT a vendor attribute,
-                                 *     and it's NOT a wire protocol
-                                 *     and we ignore Fall-Through,
-                                 *     then bitch about it, giving a
-                                 *     good warning message.
-                                 */
-                                if (!(vp->attribute & ~0xffff) &&
-                                    (vp->attribute > 0xff) &&
-                                    (vp->attribute > 1000)) {
-                                        log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
-                                                  "\tfound in reply item list for user \"%s\".\n"
-                                                  "\tThis attribute MUST go on the first line"
-                                                  " with the other check items", 
-                                                  filename, entry->lineno, vp->name,
-                                                  entry->name);
-                                }
-                        }
-                
-                        entry = entry->next;
-                }
-        
-        }
-
-       *pair_list = users;
-        return 0;
-}
-
-/*
- *     (Re-)read the "users" file into memory.
- */
-static int file_instantiate(CONF_SECTION *conf, void **instance)
-{
-        struct file_instance *inst;
-       int rcode;
 
-        inst = malloc(sizeof *inst);
-        if (!inst) {
-                radlog(L_ERR|L_CONS, "Out of memory\n");
-                return -1;
-        }
-
-        if (cf_section_parse(conf, module_config) < 0) {
-                free(inst);
-                return -1;
-        }
-
-        inst->usersfile = config.usersfile;
-        inst->acctusersfile = config.acctusersfile;
-        config.usersfile = NULL;
-        config.acctusersfile = NULL;
-
-       rcode = getusersfile(inst->usersfile, &inst->users);
-        if (rcode != 0) {
-                radlog(L_ERR|L_CONS, "Errors reading %s", inst->usersfile);
-                free(inst->usersfile);
-                free(inst->acctusersfile);
-                free(inst);
-                return -1;
-        }
-
-       rcode = getusersfile(inst->acctusersfile, &inst->acctusers);
-        if (rcode != 0) {
-                radlog(L_ERR|L_CONS, "Errors reading %s", inst->acctusersfile);
-                pairlist_free(&inst->users);
-                free(inst->usersfile);
-                free(inst->acctusersfile);
-                free(inst);
-                return -1;
-        }
-
-        *instance = inst;
-        return 0;
-}
+                       } /* end of loop over check items */
 
-/*
- *     Find the named user in the database.  Create the
- *     set of attribute-value pairs to check and reply with
- *     for this user from the database. The main code only
- *     needs to check the password, the rest is done here.
- */
-static int file_authorize(void *instance, REQUEST *request)
-{
-       VALUE_PAIR      *namepair;
-       VALUE_PAIR      *request_pairs;
-       VALUE_PAIR      *check_tmp;
-       VALUE_PAIR      *reply_tmp;
-       PAIR_LIST       *pl;
-       int             found = 0;
-#if defined(WITH_DBM) || defined(WITH_NDBM)
-       int             i, r;
-       char            buffer[256];
-#endif
-       const char      *name;
-       struct file_instance *inst = instance;
-#ifdef WITH_USERCOLLIDE
-       VALUE_PAIR  *auth_type_pair;
-       VALUE_PAIR  *password_pair;
-       VALUE_PAIR  *auth_item;
-       int   auth_type = -1;
-       int   result = 1;
-#endif
-       VALUE_PAIR **check_pairs, **reply_pairs;
+                       /*
+                        *      Look for server configuration items
+                        *      in the reply list.
+                        *
+                        *      It's a common enough mistake, that it's
+                        *      worth doing.
+                        */
+                       for (vp = entry->reply; vp != NULL; vp = vp->next) {
+                               /*
+                                *      If it's NOT a vendor attribute,
+                                *      and it's NOT a wire protocol
+                                *      and we ignore Fall-Through,
+                                *      then bitch about it, giving a
+                                *      good warning message.
+                                */
+                                if ((vp->vendor == 0) &&
+                                       (vp->attribute > 0xff) &&
+                                       (vp->attribute > 1000)) {
+                                       log_debug("[%s]:%d WARNING! Check item \"%s\"\n"
+                                                       "\tfound in reply item list for user \"%s\".\n"
+                                                       "\tThis attribute MUST go on the first line"
+                                                       " with the other check items",
+                                                       filename, entry->lineno, vp->name,
+                                                       entry->name);
+                               }
+                       }
 
+                       entry = entry->next;
+               }
 
-       request_pairs = request->packet->vps;
-       check_pairs = &request->config_items;
-       reply_pairs = &request->reply->vps;
+       }
 
-       /*
-        *      Grab the canonical user name.
-        */
-       namepair = request->username;
-       name = namepair ? (char *) namepair->strvalue : "NONE";
+       ht = fr_hash_table_create(pairlist_hash, pairlist_cmp,
+                                   my_pairlist_free);
+       if (!ht) {
+               pairlist_free(&users);
+               return -1;
+       }
 
-       /*
-        *      Find the entry for the user.
-        */
-#if defined(WITH_DBM) || defined(WITH_NDBM)
-       /*
-        *      FIXME: move to rlm_dbm.c
-        */
-       if (use_dbm) {
-               /*
-                *      FIXME: No Prefix / Suffix support for DBM.
-                */
-#ifdef WITH_DBM
-               if (dbminit(inst->usersfile) != 0)
-#endif
-#ifdef WITH_NDBM
-               if ((dbmfile = dbm_open(inst->usersfile, O_RDONLY, 0)) == NULL)
-#endif
-               {
-                       radlog(L_ERR|L_CONS, "cannot open dbm file %s",
-                               buffer);
-                       return RLM_MODULE_FAIL;
-               }
+       tailht = fr_hash_table_create(pairlist_hash, pairlist_cmp,
+                                       NULL);
+       if (!tailht) {
+               fr_hash_table_free(ht);
+               pairlist_free(&users);
+               return -1;
+       }
 
-               r = dbm_find(dbmfile, name, request_pairs, check_pairs,
-                            reply_pairs);
-               if (r > 0) found = 1;
-               if (r <= 0 || fallthrough(*reply_pairs)) {
-
-                       pairdelete(reply_pairs, PW_FALL_THROUGH);
-
-                       sprintf(buffer, "DEFAULT");
-                       i = 0;
-                       while ((r = dbm_find(dbmfile, buffer, request_pairs,
-                              check_pairs, reply_pairs)) >= 0 || i < 2) {
-                               if (r > 0) {
-                                       found = 1;
-                                       if (!fallthrough(*reply_pairs))
-                                               break;
-                                       pairdelete(reply_pairs,PW_FALL_THROUGH);
-                               }
-                               sprintf(buffer, "DEFAULT%d", i++);
-                       }
-               }
-#ifdef WITH_DBM
-               dbmclose();
-#endif
-#ifdef WITH_NDBM
-               dbm_close(dbmfile);
-#endif
-       } else
        /*
-        *      Note the fallthrough through the #endif.
+        *      Now that we've read it in, put the entries into a hash
+        *      for faster access.
         */
-#endif
+       for (entry = users; entry != NULL; entry = next) {
+               PAIR_LIST *tail;
 
-       for(pl = inst->users; pl; pl = pl->next) {
-#ifdef WITH_USERCOLLIDE
-               result = 1;
-#endif
-               /*
-                *      If the current entry is NOT a default,
-                *      AND the name does NOT match the current entry,
-                *      then skip to the next entry.
-                */
-               if ((strcmp(pl->name, "DEFAULT") != 0) &&
-                   (strcmp(name, pl->name) != 0))  {
-                       continue;
-               }
+               next = entry->next;
+               entry->next = NULL;
+               entry->order = order++;
 
                /*
-                *      If the current request matches against the
-                *      check pairs, then add the reply pairs from the
-                *      entry to the current list of reply pairs.
+                *      Insert it into the hash table, and remember
+                *      the tail of the linked list.
                 */
-               if ((paircmp(request_pairs, pl->check, reply_pairs) == 0)) {
-#ifdef WITH_USERCOLLIDE
-                       /* 
-                        *      We don't compare pass on default users
-                        *      or they never match.  Oops.
+               tail = fr_hash_table_finddata(tailht, entry);
+               if (!tail) {
+                       /*
+                        *      Insert it into the head & tail.
                         */
-                       if(strcmp(pl->name, "DEFAULT")) {
-                               /* 
-                                *      We check the pass as a config
-                                *      item with user collisions Most
-                                *      of this is stolen out of
-                                *      rad_check_password()
-                                */
-                               if ((auth_type_pair = pairfind(pl->check, PW_AUTHTYPE)) != NULL) {
-                                       auth_type = auth_type_pair->lvalue;
-                                       DEBUG2("  file_auth (Usercollide):  auth_type %d", auth_type);
-                               }
-       
-                               /* Find pass in the REQ */
-                               auth_item = request->password;
-                               if (auth_item == NULL) {
-                                       DEBUG2("  file_auth (Usercollide): No password in the request");
-                                       return RLM_MODULE_OK;
-                               }
-               
-                               /* Find the password from the users file. */
-                               if ((password_pair = pairfind(pl->check, PW_CRYPT_PASSWORD)) != NULL)
-                                       auth_type = PW_AUTHTYPE_CRYPT;
-                               else
-                                       password_pair = pairfind(pl->check, PW_PASSWORD);
-                               
-                               switch(auth_type) {
-                               case PW_AUTHTYPE_CRYPT:
-                                       DEBUG2("  file_auth (Usercollide): Checking Crypt");
-                                       if (password_pair == NULL) {
-                                               result = auth_item->strvalue ? 0 : 1;
-                                               break;
-                                       }
-                                       if (strcmp(password_pair->strvalue,
-                                                  crypt(auth_item->strvalue,
-                                                        password_pair->strvalue)) != 0)
-                                               result = 0;
-                                       break;
-                               case PW_AUTHTYPE_LOCAL:
-                                       DEBUG2("  file_auth (Usercollide): Checking Local");
-                                       if (auth_item->attribute != PW_CHAP_PASSWORD) {
-                                               if (password_pair == NULL ||
-                                                   strcmp(password_pair->strvalue,
-                                                          auth_item->strvalue)!=0)
-                                                       result = 0;
-                                               break;
-                                       }
-                               case PW_AUTHTYPE_ACCEPT:
-                                       break;  
-                               default:
-                                       continue;
-                               } /* switch(auth_type) */
-                       } /* if(!default) */
-
-                       if(result) { 
-#endif
-                               DEBUG2("  users: Matched %s at %d", pl->name, pl->lineno);
-                               found = 1;
-                               check_tmp = paircopy(pl->check);
-                               reply_tmp = paircopy(pl->reply);
-                               pairmove(reply_pairs, &reply_tmp);
-                               pairmove(check_pairs, &check_tmp);
-                               pairfree(reply_tmp);
-                               pairfree(check_tmp); /* should be NULL */
-                               /*
-                                *      Fallthrough?
-                                */
-                               if (!fallthrough(pl->reply))
-                                       break;
-#ifdef WITH_USERCOLLIDE
+                       if (!fr_hash_table_insert(ht, entry) ||
+                           !fr_hash_table_insert(tailht, entry)) {
+                               pairlist_free(&next);
+                               fr_hash_table_free(ht);
+                               fr_hash_table_free(tailht);
+                               return -1;
+                       }
+               } else {
+                       tail->next = entry;
+                       if (!fr_hash_table_replace(tailht, entry)) {
+                               pairlist_free(&next);
+                               fr_hash_table_free(ht);
+                               fr_hash_table_free(tailht);
+                               return -1;
                        }
-#endif
                }
        }
-       
-       /*
-        *      See if we succeeded.  If we didn't find the user,
-        *      then exit from the module.
-        */
-       if (!found)
-               return RLM_MODULE_NOTFOUND;
 
-       /*
-        *      Remove server internal parameters.
-        */
-       pairdelete(reply_pairs, PW_FALL_THROUGH);
+       fr_hash_table_free(tailht);
+       *pht = ht;
 
-       return RLM_MODULE_OK;
+       return 0;
 }
 
 /*
- *     Authentication - unused.
+ *     Clean up.
  */
-static int file_authenticate(void *instance, REQUEST *request)
+static int file_detach(void *instance)
 {
-       instance = instance;
-       request = request;
-       return RLM_MODULE_OK;
+       struct file_instance *inst = instance;
+       fr_hash_table_free(inst->users);
+       fr_hash_table_free(inst->acctusers);
+#ifdef WITH_PROXY
+       fr_hash_table_free(inst->preproxy_users);
+       fr_hash_table_free(inst->postproxy_users);
+#endif
+       fr_hash_table_free(inst->auth_users);
+       fr_hash_table_free(inst->postauth_users);
+       free(inst);
+       return 0;
 }
 
+
+
 /*
- *     Pre-Accounting - read the acct_users file for check_items and
- *     config_items. Reply items are Not Recommended(TM) in acct_users,
- *     except for Fallthrough, which should work
- *
- *     This function is mostly a copy of file_authorize
+ *     (Re-)read the "users" file into memory.
  */
-static int file_preacct(void *instance, REQUEST *request)
+static int file_instantiate(CONF_SECTION *conf, void **instance)
 {
-       VALUE_PAIR      *namepair;
-       const char      *name;
-       VALUE_PAIR      *request_pairs;
+       struct file_instance *inst;
+       int rcode;
+
+       inst = rad_malloc(sizeof *inst);
+       if (!inst) {
+               return -1;
+       }
+       memset(inst, 0, sizeof(*inst));
+
+       if (cf_section_parse(conf, inst, module_config) < 0) {
+               free(inst);
+               return -1;
+       }
+
+       rcode = getusersfile(inst->usersfile, &inst->users, inst->compat_mode);
+       if (rcode != 0) {
+         radlog(L_ERR|L_CONS, "Errors reading %s", inst->usersfile);
+               file_detach(inst);
+               return -1;
+       }
+
+       rcode = getusersfile(inst->acctusersfile, &inst->acctusers, inst->compat_mode);
+       if (rcode != 0) {
+               radlog(L_ERR|L_CONS, "Errors reading %s", inst->acctusersfile);
+               file_detach(inst);
+               return -1;
+       }
+
+#ifdef WITH_PROXY
+       /*
+        *  Get the pre-proxy stuff
+        */
+       rcode = getusersfile(inst->preproxy_usersfile, &inst->preproxy_users, inst->compat_mode);
+       if (rcode != 0) {
+               radlog(L_ERR|L_CONS, "Errors reading %s", inst->preproxy_usersfile);
+               file_detach(inst);
+               return -1;
+       }
+
+       rcode = getusersfile(inst->postproxy_usersfile, &inst->postproxy_users, inst->compat_mode);
+       if (rcode != 0) {
+               radlog(L_ERR|L_CONS, "Errors reading %s", inst->postproxy_usersfile);
+               file_detach(inst);
+               return -1;
+       }
+#endif
+
+       rcode = getusersfile(inst->auth_usersfile, &inst->auth_users, inst->compat_mode);
+       if (rcode != 0) {
+               radlog(L_ERR|L_CONS, "Errors reading %s", inst->auth_usersfile);
+               file_detach(inst);
+               return -1;
+       }
+
+       rcode = getusersfile(inst->postauth_usersfile, &inst->postauth_users, inst->compat_mode);
+       if (rcode != 0) {
+               radlog(L_ERR|L_CONS, "Errors reading %s", inst->postauth_usersfile);
+               file_detach(inst);
+               return -1;
+       }
+
+       *instance = inst;
+       return 0;
+}
+
+/*
+ *     Common code called by everything below.
+ */
+static int file_common(struct file_instance *inst, REQUEST *request,
+                      const char *filename, fr_hash_table_t *ht,
+                      VALUE_PAIR *request_pairs, VALUE_PAIR **reply_pairs)
+{
+       const char      *name, *match;
        VALUE_PAIR      **config_pairs;
-       VALUE_PAIR      *reply_pairs = NULL;
        VALUE_PAIR      *check_tmp;
        VALUE_PAIR      *reply_tmp;
-       PAIR_LIST       *pl;
+       const PAIR_LIST *user_pl, *default_pl;
        int             found = 0;
-#if defined(WITH_DBM) || defined(WITH_NDBM)
-       int             i, r;
+       PAIR_LIST       my_pl;
        char            buffer[256];
-#endif
-       struct file_instance *inst = instance;
 
-       namepair = request->username;
-       name = namepair ? (char *) namepair->strvalue : "NONE";
-       request_pairs = request->packet->vps;
+       if (!inst->key) {
+               VALUE_PAIR      *namepair;
+
+               namepair = request->username;
+               name = namepair ? (char *) namepair->vp_strvalue : "NONE";
+       } else {
+               int len;
+
+               len = radius_xlat(buffer, sizeof(buffer), inst->key,
+                                 request, NULL);
+               if (len) name = buffer;
+               else name = "NONE";
+       }
+
        config_pairs = &request->config_items;
-       
+
+       if (!ht) return RLM_MODULE_NOOP;
+
+       my_pl.name = name;
+       user_pl = fr_hash_table_finddata(ht, &my_pl);
+       my_pl.name = "DEFAULT";
+       default_pl = fr_hash_table_finddata(ht, &my_pl);
+
        /*
         *      Find the entry for the user.
         */
-#if defined(WITH_DBM) || defined(WITH_NDBM)
-       /*
-        *      FIXME: move to rlm_dbm.c
-        */
-       if (use_dbm) {
-               /*
-                *      FIXME: No Prefix / Suffix support for DBM.
-                */
-#ifdef WITH_DBM
-               if (dbminit(inst->acctusersfile) != 0)
-#endif
-#ifdef WITH_NDBM
-               if ((dbmfile = dbm_open(inst->acctusersfile, O_RDONLY, 0)) == NULL)
-#endif
-               {
-                       radlog(L_ERR|L_CONS, "cannot open dbm file %s",
-                               buffer);
-                       return RLM_MODULE_FAIL;
-               }
-
-               r = dbm_find(dbmfile, name, request_pairs, config_pairs,
-                            &reply_pairs);
-               if (r > 0) found = 1;
-               if (r <= 0 || fallthrough(*reply_pairs)) {
-
-                 pairdelete(reply_pairs, PW_FALL_THROUGH);
-
-                       sprintf(buffer, "DEFAULT");
-                       i = 0;
-                       while ((r = dbm_find(dbmfile, buffer, request_pairs,
-                              config_pairs, &reply_pairs)) >= 0 || i < 2) {
-                               if (r > 0) {
-                                       found = 1;
-                                       if (!fallthrough(*reply_pairs))
-                                               break;
-                                       pairdelete(reply_pairs,PW_FALL_THROUGH);
-                               }
-                               sprintf(buffer, "DEFAULT%d", i++);
-                       }
+       while (user_pl || default_pl) {
+               const PAIR_LIST *pl;
+
+               if (!default_pl && user_pl) {
+                       pl = user_pl;
+                       match = name;
+                       user_pl = user_pl->next;
+
+               } else if (!user_pl && default_pl) {
+                       pl = default_pl;
+                       match = "DEFAULT";
+                       default_pl = default_pl->next;
+
+               } else if (user_pl->order < default_pl->order) {
+                       pl = user_pl;
+                       match = name;
+                       user_pl = user_pl->next;
+
+               } else {
+                       pl = default_pl;
+                       match = "DEFAULT";
+                       default_pl = default_pl->next;
                }
-#ifdef WITH_DBM
-               dbmclose();
-#endif
-#ifdef WITH_NDBM
-               dbm_close(dbmfile);
-#endif
-       } else
-       /*
-        *      Note the fallthrough through the #endif.
-        */
-#endif
 
-       for(pl = inst->acctusers; pl; pl = pl->next) {
-
-               if (strcmp(name, pl->name) && strcmp(pl->name, "DEFAULT"))
-                       continue;
-
-               if (paircmp(request_pairs, pl->check, &reply_pairs) == 0) {
-                       DEBUG2("  acct_users: Matched %s at %d",
-                              pl->name, pl->lineno);
+               if (paircompare(request, request_pairs, pl->check, reply_pairs) == 0) {
+                       RDEBUG2("%s: Matched entry %s at line %d",
+                              filename, match, pl->lineno);
                        found = 1;
                        check_tmp = paircopy(pl->check);
                        reply_tmp = paircopy(pl->reply);
-                       pairmove(&reply_pairs, &reply_tmp);
+                       pairxlatmove(request, reply_pairs, &reply_tmp);
                        pairmove(config_pairs, &check_tmp);
-                       pairfree(reply_tmp);
-                       pairfree(check_tmp); /* should be NULL */
+                       pairfree(&reply_tmp);
+                       pairfree(&check_tmp);
+
                        /*
                         *      Fallthrough?
                         */
@@ -692,46 +492,108 @@ static int file_preacct(void *instance, REQUEST *request)
        }
 
        /*
-        *      See if we succeeded.
+        *      Remove server internal parameters.
         */
-       if (!found)
-               return RLM_MODULE_NOOP; /* on to the next module */
+       pairdelete(reply_pairs, PW_FALL_THROUGH, 0);
 
        /*
-        *      FIXME: log a warning if there are any reply items other than
-        *      Fallthrough
+        *      See if we succeeded.
         */
-       pairfree(reply_pairs); /* Don't need these */
+       if (!found)
+               return RLM_MODULE_NOOP; /* on to the next module */
 
        return RLM_MODULE_OK;
+
 }
 
+
 /*
- *     Clean up.
+ *     Find the named user in the database.  Create the
+ *     set of attribute-value pairs to check and reply with
+ *     for this user from the database. The main code only
+ *     needs to check the password, the rest is done here.
  */
-static int file_detach(void *instance)
+static int file_authorize(void *instance, REQUEST *request)
 {
-        struct file_instance *inst = instance;
-        pairlist_free(&inst->users);
-        pairlist_free(&inst->acctusers);
-        free(inst->usersfile);
-        free(inst->acctusersfile);
-        free(inst);
-       return 0;
+       struct file_instance *inst = instance;
+
+       return file_common(inst, request, "users", inst->users,
+                          request->packet->vps, &request->reply->vps);
+}
+
+
+/*
+ *     Pre-Accounting - read the acct_users file for check_items and
+ *     config_items. Reply items are Not Recommended(TM) in acct_users,
+ *     except for Fallthrough, which should work
+ */
+static int file_preacct(void *instance, REQUEST *request)
+{
+       struct file_instance *inst = instance;
+
+       return file_common(inst, request, "acct_users", inst->acctusers,
+                          request->packet->vps, &request->reply->vps);
+}
+
+#ifdef WITH_PROXY
+static int file_preproxy(void *instance, REQUEST *request)
+{
+       struct file_instance *inst = instance;
+
+       return file_common(inst, request, "preproxy_users",
+                          inst->preproxy_users,
+                          request->packet->vps, &request->proxy->vps);
+}
+
+static int file_postproxy(void *instance, REQUEST *request)
+{
+       struct file_instance *inst = instance;
+
+       return file_common(inst, request, "postproxy_users",
+                          inst->postproxy_users,
+                          request->proxy_reply->vps, &request->reply->vps);
+}
+#endif
+
+static int file_authenticate(void *instance, REQUEST *request)
+{
+       struct file_instance *inst = instance;
+
+       return file_common(inst, request, "auth_users",
+                          inst->auth_users,
+                          request->packet->vps, &request->reply->vps);
+}
+
+static int file_postauth(void *instance, REQUEST *request)
+{
+       struct file_instance *inst = instance;
+
+       return file_common(inst, request, "postauth_users",
+                          inst->postauth_users,
+                          request->packet->vps, &request->reply->vps);
 }
 
 
 /* globally exported name */
 module_t rlm_files = {
+       RLM_MODULE_INIT,
        "files",
-       0,                              /* type: reserved */
-       file_init,                      /* initialization */
+       RLM_TYPE_CHECK_CONFIG_SAFE | RLM_TYPE_HUP_SAFE,
        file_instantiate,               /* instantiation */
-       file_authorize,                 /* authorization */
-       file_authenticate,              /* authentication */
-       file_preacct,                   /* preaccounting */
-       NULL,                           /* accounting */
        file_detach,                    /* detach */
-       NULL                            /* destroy */
+       {
+               file_authenticate,      /* authentication */
+               file_authorize,         /* authorization */
+               file_preacct,           /* preaccounting */
+               NULL,                   /* accounting */
+               NULL,                   /* checksimul */
+#ifdef WITH_PROXY
+               file_preproxy,          /* pre-proxy */
+               file_postproxy,         /* post-proxy */
+#else
+               NULL, NULL,
+#endif
+               file_postauth           /* post-auth */
+       },
 };