This is the "configurable failover" patch, providing a more flexible
authorpacman <pacman>
Thu, 21 Dec 2000 06:31:21 +0000 (06:31 +0000)
committerpacman <pacman>
Thu, 21 Dec 2000 06:31:21 +0000 (06:31 +0000)
module calling sequence.

doc/configurable_failover [new file with mode: 0644]
src/include/modcall.h [new file with mode: 0644]
src/include/modpriv.h [new file with mode: 0644]
src/main/Makefile
src/main/modcall.c [new file with mode: 0644]
src/main/modules.c

diff --git a/doc/configurable_failover b/doc/configurable_failover
new file mode 100644 (file)
index 0000000..cf1755b
--- /dev/null
@@ -0,0 +1,214 @@
+Before configurable failover, we had this:
+
+authorize {
+  preprocess
+  files
+}
+
+which instructed module_authorize to first pass the request through
+rlm_preprocess, and if that returned success, pass it through rlm_files,
+and if that returned success, module_authorize itself would then return
+success. Processing was strictly linear and if one module failed, the whole
+function would fail immediately.
+
+Configurable failover provides more flexibility. It takes advantage of the
+tree structure of radiusd.conf to support a config language that allows you
+to specify groups of modules that should work together in ways other than
+execute-in-order-return-on-fail. Basically you can redesign the flow of
+module_authorize to fit your needs, without touching C code, just by altering
+radiusd.conf.
+
+I will soon explain this new language in detail, but first, if you just want
+to know how to make failover happen without understanding why it works or how
+to tweak it, here's your quick fix: just put a redundant{} block around those
+module instances which refer to redundant databases. Example:
+
+  modules {
+    sql sql1 {
+      server="myfirstserver.example"
+      # Insert other necessary parameters here
+    }
+    sql sql2 {
+      server="mysecondserver.example"
+      # Insert other necessary parameters here
+    }
+  }
+  authenticate {
+    authtype SQL {
+      redundant {
+        sql1
+        sql2
+      }
+    }
+  }
+
+OK, now for the exhaustive documentation. The things unexpectedly CAPITALIZED
+are the key terms...
+
+The fundamental object is called a MODCALLABLE, because it is something that
+can be passed a specific radius request and returns one of the RLM_MODULE_*
+results. It is a function - if you can accept the fact that pieces of
+radiusd.conf are functions. There are two kinds of MODCALLABLEs: GROUPs and
+SINGLEs.
+
+A SINGLE is a reference to a module instance that was set up in the modules{}
+section of radiusd.conf, like "preprocess" or "sql1". When a SINGLE is
+called, the corresponding function in the rlm is invoked, and whichever
+RLM_MODULE_* it returns becomes the RESULT of the SINGLE.
+
+A GROUP is a section of radiusd.conf that includes some MODCALLABLEs.
+Examples of GROUPs above include "authorize{...}", which implements the C
+function module_authorize, and "redundant{...}", which contains two SINGLEs
+that refer to a couple of redundant databases. Note that a GROUP can contain
+other GROUPs - "authtype SQL{...}" is also a GROUP, which implements the C
+function module_authenticate when Auth-Type is set to SQL.
+
+Now here's the fun part - what happens when a GROUP is called? It simply runs
+through all of its children in order, and calls each one, whether it is
+another GROUP or a SINGLE. It then looks at the RESULT of that child, and
+takes some ACTION, which is basically either "return that RESULT immediately"
+or "Keep going". In the first example, any "bad" RESULT from the preprocess
+module causes an immediate return, and any "good" RESULT causes the
+authorize{...} GROUP to proceed to the files module.
+
+We can see the exact rules by writing them out the long way:
+
+authorize {
+  preprocess {
+    notfound = 1
+    noop     = 2
+    ok       = 3
+    updated  = 4
+    fail     = return
+    reject   = return
+    userlock = return
+    invalid  = return
+    handled  = return
+  }
+  files {
+    notfound = 1
+    noop     = 2
+    ok       = 3
+    updated  = 4
+    fail     = return
+    reject   = return
+    userlock = return
+    invalid  = return
+    handled  = return
+  }
+}
+
+This is the same as the first example, with the default behavior explicitly
+spelled out. Each SINGLE becomes its own section, containing a list of
+RESULTs that it may return and what ACTION should follow from them. So
+preprocess is called, and if it returns for example RLM_MODULE_REJECT, then
+the reject=return rule is applied, and the authorize{...} GROUP itself
+immediately returns RLM_MODULE_REJECT.
+
+If preprocess returns RLM_MODULE_NOOP, the corresponding ACTION is "2". An
+integer ACTION serves two purposes - first, it tells the parent GROUP to go
+on to the next module. Second, it is a hint as to how desirable this RESULT
+is as a candidate for the GROUP's own RESULT. So files is called... suppose
+it returns RLM_MODULE_NOTFOUND. The ACTION for notfound inside the files{...}
+block is "1". We have now reached the end of the authorize{...} GROUP and we
+look at the RESULTs we accumulated along the way - there is a noop with
+preference level 2, and a notfound with preference level 1, so the
+authorize{...} GROUP as a whole returns RLM_MODULE_NOOP, which makes sense
+because to say the user was not found at all would be a lie, since preprocess
+apparently found him, or else it would have returned RLM_MODULE_NOTFOUND too.
+
+[Take a deep breath - the worst is over]
+
+That RESULT preference/desirability stuff is pretty complex, but my hope is
+that it will be complex enough to handle the needs of everyone's real-world
+imperfect systems, while staying out of sight most of the time since the
+defaults will be right for the most common configurations.
+
+So where does redundant{...} fit in with all that? Well, redundant{...} is
+simply a group that changes the default ACTIONs to something like
+
+  fail = 1
+  everythingelse = return
+
+so that when one module fails, we keep trying until we find one that doesn't
+fail, then return whatever it returned. And at the end, if they all failed,
+the redundant GROUP as a whole returns RLM_MODULE_FAIL, just as you'd want it
+to (I hope).
+
+There are two other kinds of grouping: group{...} which does not have any
+specialized default ACTIONs, and append{...}, which should be used when you
+have separate but similarly structured databases that are guaranteed not to
+overlap.
+
+That's all that really needs to be said. But now a few random notes:
+
+1. GROUPs may have RESULT=ACTION specifiers too! It would look like this:
+
+  authorize {
+    preprocess
+    redundant {
+      sql1
+      sql2
+      notfound = return
+    }
+    files
+  }
+
+which would prevent rlm_files from being called if neither of the SQL
+instances could find the user.
+
+2. redundant{...} and append{...} are just shortcuts. You could write
+    group {
+      sql1 {
+        fail     = 1
+        notfound = 2
+        noop     = return
+        ok       = return
+        updated  = return
+        reject   = return
+        userlock = return
+        invalid  = return
+        handled  = return
+      }
+      sql2 {
+        fail     = 1
+        notfound = 2
+        noop     = return
+        ok       = return
+        updated  = return
+        reject   = return
+        userlock = return
+        invalid  = return
+        handled  = return
+      }
+    }
+  instead of
+    redundant {
+      sql1
+      sql2
+    }
+  but the latter is just a whole lot easier to read.
+
+3. "authenticate{...}" itself is not a GROUP, even though it contains a list
+of authtype GROUPs, because its semantics are totally different - it uses
+Auth-Type to decide which of its members to call, and their order is
+irrelevant.
+
+4. The default rules are context-sensitive - for authorize, the defaults are
+what you saw above - notfound, noop, ok, and updated are considered
+success, and anything else has an ACTION of "return". For authenticate, the
+default is to return on success *or* reject, and only try the second and
+following items if the first one fails. You can read all the default ACTIONs
+in modcall.c (int defaultactions[][][]), or just trust me. They do the right
+thing.
+
+5. There are some rules that can't be implemented in this language - things
+like "if the absolutelypositivelymandatory module returns notfound, the group
+should immediately return reject". It would be possible to extend the
+language to include that, as "notfound = return-reject", and the obvious
+followup feature would be "notfound = 1-reject", "noop = 2-ok", "ok = 3-ok",
+etc. But I don't feel justified adding that complexity in the first draft.
+There are already enough things here that may never see real-world usage.
+Like append{...}
+
+-- Pac. 9/18/2000
diff --git a/src/include/modcall.h b/src/include/modcall.h
new file mode 100644 (file)
index 0000000..b45e064
--- /dev/null
@@ -0,0 +1,34 @@
+/* modcall.h: the outside interface to the module-calling tree. Includes
+ * functions to build the tree from the config file, and to call it by
+ * feeding it REQUESTs.
+ * 
+ * Version: $Id$ */
+
+#include "conffile.h" /* Need CONF_* definitions */
+
+/*
+ *     For each authorize/authtype/etc, we have an ordered
+ *     tree of instances to call.  This data structure keeps track
+ *     of that order.
+ */
+typedef struct modcallable modcallable;
+
+int modcall(int component, modcallable *c, REQUEST *request);
+
+/* Parse a module-method's config section (e.g. authorize{}) into a tree that
+ * may be called with modcall() */
+modcallable *compile_modgroup(int component, CONF_SECTION *cs,
+                             const char *filename);
+
+/* Create a single modcallable node that references a module instance. This
+ * may be a CONF_SECTION containing action specifiers like "notfound = return"
+ * or a simple CONF_PAIR, in which case the default actions are used. */
+modcallable *compile_modsingle(int component, CONF_ITEM *ci,
+                              const char *filename, char **modname);
+
+/* Add an entry to the end of a modgroup, creating it first if necessary */
+void add_to_modcallable(modcallable **parent, modcallable *this,
+                       int component, int lineno);
+
+/* Free a tree returned by compile_modgroup or compile_modsingle */
+void modcallable_free(modcallable **pc);
diff --git a/src/include/modpriv.h b/src/include/modpriv.h
new file mode 100644 (file)
index 0000000..442f929
--- /dev/null
@@ -0,0 +1,34 @@
+/* modpriv.h: Stuff needed by both modules.c and modcall.c, but should not be
+ * accessed from anywhere else.
+ *
+ * Version: $Id$ */
+#include "radiusd.h"
+#include "modules.h"
+#include "ltdl.h"
+
+/*
+ *     Keep track of which modules we've loaded.
+ */
+typedef struct module_list_t {
+       struct module_list_t    *next;
+       char                    name[MAX_STRING_LEN];
+       module_t                *module;
+       lt_dlhandle             handle;
+} module_list_t;
+
+/*
+ *     Per-instance data structure, to correlate the modules
+ *     with the instance names (may NOT be the module names!), 
+ *     and the per-instance data structures.
+ */
+typedef struct module_instance_t {
+       struct module_instance_t *next;
+       char                    name[MAX_STRING_LEN];
+       module_list_t           *entry;
+       void                    *insthandle;
+#if HAVE_PTHREAD_H
+       pthread_mutex_t         *mutex;
+#endif
+} module_instance_t;
+
+module_instance_t *find_module_instance(const char *instname);
index 938682b..3cfd8a1 100644 (file)
@@ -6,8 +6,8 @@ include ../../Make.inc
 
 SERVER_OBJS    = radiusd.o files.o util.o acct.o nas.o log.o valuepair.o \
                  version.o proxy.o exec.o auth.o timestr.o conffile.o \
-                 modules.o radutmp.o xlat.o threads.o smux.o radius_snmp.o \
-                  client.o request_list.o
+                 modules.o modcall.o radutmp.o xlat.o threads.o smux.o \
+                 radius_snmp.o client.o request_list.o
 INCLUDES       = ../include/radiusd.h ../include/conf.h ../include/autoconf.h 
 
 CFLAGS         += -I../include
@@ -31,7 +31,7 @@ radiusd: $(SERVER_OBJS) ../lib/libradius.a
                $(SERVER_OBJS) $(LIBS) $(LCRYPT) \
                $(PTHREADLIB) $(LIBLTDL) $(MODULE_LIBS) 
 
-radiusd.o: radiusd.c $(INCLUDES)  ../include/request_list.h ../include/modules.h 
+radiusd.o: radiusd.c $(INCLUDES)  ../include/request_list.h ../include/modules.h ../include/modcall.h ../include/modpriv.h
        $(CC) $(CFLAGS) -c radiusd.c
 
 acct.o: acct.c $(INCLUDES) ../include/modules.h
@@ -58,6 +58,9 @@ timestr.o: timestr.c $(INCLUDES)
 modules.o:  modules.c $(INCLUDES)
        $(CC) $(CFLAGS) $(INCLTDL) -c modules.c
 
+modcall.o:  modcall.c $(INCLUDES)
+       $(CC) $(CFLAGS) $(INCLTDL) -c modcall.c
+
 radutmp.o:  radutmp.c $(INCLUDES)
        $(CC) $(CFLAGS) -c radutmp.c
 
diff --git a/src/main/modcall.c b/src/main/modcall.c
new file mode 100644 (file)
index 0000000..6dc9ac4
--- /dev/null
@@ -0,0 +1,803 @@
+#include <assert.h>
+#include <string.h>
+#include <stdarg.h>
+#include "radiusd.h"
+#include "conffile.h"
+#include "modpriv.h"
+#include "modules.h"
+#include "modcall.h"
+
+/* Actions may be a positive integer (the highest one returned in the group
+ * will be returned), or the keyword "return", represented here by
+ * MOD_ACTION_RETURN, to cause an immediate return. */
+#define MOD_ACTION_RETURN (-1)
+
+/* Here are our basic types: modcallable, modgroup, and modsingle. For an
+ * explanation of what they are all about, see ../../doc/README.failover */
+struct modcallable {
+       struct modcallable *next;
+       int actions[RLM_MODULE_NUMCODES];
+       int lineno;
+       enum { MOD_SINGLE, MOD_GROUP } type;
+};
+
+typedef struct {
+       modcallable mc;
+       modcallable *children;
+} modgroup;
+
+typedef struct {
+       modcallable mc;
+       module_instance_t *modinst;
+} modsingle;
+
+/* Simple conversions: modsingle and modgroup are subclasses of modcallable,
+ * so we often want to go back and forth between them. */
+static modsingle *mod_callabletosingle(modcallable *p)
+{
+       assert(p->type==MOD_SINGLE);
+       return (modsingle *)p;
+}
+static modgroup *mod_callabletogroup(modcallable *p)
+{
+       assert(p->type==MOD_GROUP);
+       return (modgroup *)p;
+}
+static modcallable *mod_singletocallable(modsingle *p)
+{
+       return (modcallable *)p;
+}
+static modcallable *mod_grouptocallable(modgroup *p)
+{
+       return (modcallable *)p;
+}
+
+/* modgroups are grown by adding a modcallable to the end */
+static void add_child(modgroup *g, modcallable *c)
+{
+       modcallable **head = &g->children;
+       modcallable *node = *head;
+       modcallable **last = head;
+
+       while (node) {
+               last = &node->next;
+               node = node->next;
+       }
+
+       assert(c->next == NULL);
+       *last = c;
+}
+
+/* Here's where we recognize all of our keywords: first the rcodes, then the
+ * actions */
+static int str2rcode(const char *s, const char *filename, int lineno)
+{
+       if(!strcasecmp(s, "reject"))
+               return RLM_MODULE_REJECT;
+       else if(!strcasecmp(s, "fail"))
+               return RLM_MODULE_FAIL;
+       else if(!strcasecmp(s, "ok"))
+               return RLM_MODULE_OK;
+       else if(!strcasecmp(s, "handled"))
+               return RLM_MODULE_HANDLED;
+       else if(!strcasecmp(s, "invalid"))
+               return RLM_MODULE_INVALID;
+       else if(!strcasecmp(s, "userlock"))
+               return RLM_MODULE_USERLOCK;
+       else if(!strcasecmp(s, "notfound"))
+               return RLM_MODULE_NOTFOUND;
+       else if(!strcasecmp(s, "noop"))
+               return RLM_MODULE_NOOP;
+       else if(!strcasecmp(s, "updated"))
+               return RLM_MODULE_UPDATED;
+       else {
+               radlog(L_ERR|L_CONS,
+                       "%s[%d] Unknown module rcode '%s'.\n",
+                       filename, lineno, s);
+               exit(1);
+       }
+}
+
+static const char *rcode2str[] = {
+       "reject",
+       "fail",
+       "ok",
+       "handled",
+       "invalid",
+       "userlock",
+       "notfound",
+       "noop",
+       "updated"
+};
+
+static int str2action(const char *s, const char *filename, int lineno)
+{
+       if(!strcasecmp(s, "return"))
+               return MOD_ACTION_RETURN;
+       else if(strspn(s, "0123456789")==strlen(s))
+               return atoi(s);
+       else {
+               radlog(L_ERR|L_CONS,
+                       "%s[%d] Unknown action '%s'.\n",
+                       filename, lineno, s);
+               exit(1);
+       }
+}
+
+static const char *action2str(int action)
+{
+       static char buf[32];
+       if(action==MOD_ACTION_RETURN)
+               return "return";
+       snprintf(buf, sizeof buf, "%d", action);
+       return buf;
+}
+
+/* Some short names for debugging output */
+static const char *comp2str[] = {
+       "auth",
+       "autz",
+       "preacct",
+       "acct",
+       "sess"
+};
+
+#if HAVE_PTHREAD_H
+/*
+ *     Lock the mutex for the module
+ */
+static void safe_lock(module_instance_t *instance)
+{
+       if (instance->mutex) pthread_mutex_lock(instance->mutex);
+}
+
+/*
+ *     Unlock the mutex for the module
+ */
+static void safe_unlock(module_instance_t *instance)
+{
+       if (instance->mutex) pthread_mutex_unlock(instance->mutex);
+}
+#else
+/*
+ *     No threads: these functions become NULL's.
+ */
+#define safe_lock(foo)
+#define safe_unlock(foo)
+#endif
+
+static int call_modsingle(int component, modsingle *sp, REQUEST *request,
+                         int default_result)
+{
+       int myresult = default_result;
+
+       safe_lock(sp->modinst);
+       switch(component) {
+       case RLM_COMPONENT_AUTZ:
+               myresult = sp->modinst->entry->module->authorize(
+                                   sp->modinst->insthandle, request);
+               break;
+       case RLM_COMPONENT_AUTH:
+               myresult = sp->modinst->entry->module->authenticate(
+                                   sp->modinst->insthandle, request);
+               break;
+       case RLM_COMPONENT_PREACCT:
+               myresult = sp->modinst->entry->module->preaccounting(
+                                   sp->modinst->insthandle, request);
+               break;
+       case RLM_COMPONENT_ACCT:
+               myresult = sp->modinst->entry->module->accounting(
+                                   sp->modinst->insthandle, request);
+               break;
+       case RLM_COMPONENT_SESS:
+               myresult = sp->modinst->entry->module->checksimul(
+                                   sp->modinst->insthandle, request);
+               break;
+       }
+       safe_unlock(sp->modinst);
+
+       return myresult;
+}
+
+static int call_modgroup(int component, modgroup *g, REQUEST *request,
+                        int default_result)
+{
+       int myresult = default_result;
+       int myresultpref;
+       modcallable *p;
+
+       /* Assign the lowest possible preference to the default return code */
+       myresultpref = 0;
+
+       /* Loop over the children */
+       for(p = g->children; p; p = p->next) {
+               int r = RLM_MODULE_FAIL;
+
+               /* Call this child by recursing into modcall */
+               r = modcall(component, p, request);
+
+               DEBUG2("modcall[%s]: action for %s is %s",
+                       comp2str[component], rcode2str[r],
+                       action2str(p->actions[r]));
+
+               /* Find an action to go with the child's result. If "return",
+                * break out of the loop so the rest of the children in the
+                * list will be skipped. */
+               if(p->actions[r] == MOD_ACTION_RETURN) {
+                       myresult = r;
+                       break;
+               }
+
+               /* Otherwise, the action is a number, the preference level of
+                * this return code. If no higher preference has been seen
+                * yet, remember this one. */
+               if(p->actions[r] >= myresultpref) {
+                       myresult = r;
+                       myresultpref = p->actions[r];
+               }
+       }
+
+       return myresult;
+}
+
+int modcall(int component, modcallable *c, REQUEST *request)
+{
+       int myresult;
+
+       /* Choose a default return value appropriate for the component */
+       switch(component) {
+               case RLM_COMPONENT_AUTZ:   myresult = RLM_MODULE_NOTFOUND;break;
+               case RLM_COMPONENT_AUTH:   myresult = RLM_MODULE_REJECT;  break;
+               case RLM_COMPONENT_PREACCT:myresult = RLM_MODULE_NOOP;    break;
+               case RLM_COMPONENT_ACCT:   myresult = RLM_MODULE_NOOP;    break;
+               case RLM_COMPONENT_SESS:   myresult = RLM_MODULE_FAIL;    break;
+               default: myresult = RLM_MODULE_FAIL;
+       }
+
+       if(!c) {
+         DEBUG2("modcall[%s]: Null object returns %s",
+               comp2str[component], rcode2str[myresult]);
+         return myresult;
+       }
+
+       if(c->type==MOD_GROUP) {
+               modgroup *g = mod_callabletogroup(c);
+
+               DEBUG2("modcall[%s]: Entering group at line %d",
+                       comp2str[component], c->lineno);
+
+               myresult = call_modgroup(component, g, request, myresult);
+
+               DEBUG2("modcall[%s]: Group at line %d returns %s",
+                       comp2str[component], c->lineno, rcode2str[myresult]);
+       } else {
+               modsingle *sp = mod_callabletosingle(c);
+
+               myresult = call_modsingle(component, sp, request, myresult);
+
+               DEBUG2("modcall[%s]: Module at line %d returns %s",
+                        comp2str[component], c->lineno, rcode2str[myresult]);
+       }
+
+       return myresult;
+}
+
+/* If you suspect a bug in the parser, you'll want to use these dump
+ * functions. dump_tree should reproduce a whole tree exactly as it was found
+ * in radiusd.conf, but in long form (all actions explicitly defined) */
+static void dump_mc(modcallable *c, int indent)
+{
+       int i;
+
+       if(c->type==MOD_SINGLE) {
+               modsingle *single = mod_callabletosingle(c);
+               DEBUG("%.*s%s {", indent, "\t\t\t\t\t\t\t\t\t\t\t",
+                       single->modinst->name);
+       } else {
+               modgroup *g = mod_callabletogroup(c);
+               modcallable *p;
+               DEBUG("%.*sgroup {", indent, "\t\t\t\t\t\t\t\t\t\t\t");
+               for(p = g->children;p;p = p->next)
+                       dump_mc(p, indent+1);
+       }
+
+       for(i = 0; i<RLM_MODULE_NUMCODES; ++i) {
+               DEBUG("%.*s%s = %s", indent+1, "\t\t\t\t\t\t\t\t\t\t\t",
+                       rcode2str[i], action2str(c->actions[i]));
+       }
+
+       DEBUG("%.*s}", indent, "\t\t\t\t\t\t\t\t\t\t\t");
+}
+
+static void dump_tree(int comp, modcallable *c)
+{
+       DEBUG("[%s]", comp2str[comp]);
+       dump_mc(c, 0);
+}
+
+#define GROUPTYPE_SIMPLEGROUP 0
+#define GROUPTYPE_REDUNDANT 1
+#define GROUPTYPE_APPEND 2
+#define GROUPTYPE_COUNT 3
+
+/* These are the default actions. For each component, the group{} block
+ * behaves like the code from the old module_*() function. redundant{} and
+ * append{} are based on my guesses of what they will be used for. --Pac. */
+static int
+defaultactions[RLM_COMPONENT_COUNT][GROUPTYPE_COUNT][RLM_MODULE_NUMCODES] =
+{
+       /* authenticate */
+       {
+               /* group */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       1,                      /* fail     */
+                       MOD_ACTION_RETURN,      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       1,                      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       MOD_ACTION_RETURN,      /* notfound */
+                       1,                      /* noop     */
+                       1                       /* updated  */
+               },
+               /* redundant */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       1,                      /* fail     */
+                       MOD_ACTION_RETURN,      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       MOD_ACTION_RETURN,      /* notfound */
+                       MOD_ACTION_RETURN,      /* noop     */
+                       MOD_ACTION_RETURN       /* updated  */
+               },
+               /* append */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       1,                      /* fail     */
+                       MOD_ACTION_RETURN,      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       2,                      /* notfound */
+                       MOD_ACTION_RETURN,      /* noop     */
+                       MOD_ACTION_RETURN       /* updated  */
+               }
+       },
+       /* authorize */
+       {
+               /* group */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       MOD_ACTION_RETURN,      /* fail     */
+                       3,                      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       1,                      /* notfound */
+                       2,                      /* noop     */
+                       4                       /* updated  */
+               },
+               /* redundant */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       1,                      /* fail     */
+                       MOD_ACTION_RETURN,      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       MOD_ACTION_RETURN,      /* notfound */
+                       MOD_ACTION_RETURN,      /* noop     */
+                       MOD_ACTION_RETURN       /* updated  */
+               },
+               /* append */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       1,                      /* fail     */
+                       MOD_ACTION_RETURN,      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       2,                      /* notfound */
+                       MOD_ACTION_RETURN,      /* noop     */
+                       MOD_ACTION_RETURN       /* updated  */
+               }
+       },
+       /* preacct */
+       {
+               /* group */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       MOD_ACTION_RETURN,      /* fail     */
+                       2,                      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       MOD_ACTION_RETURN,      /* notfound */
+                       1,                      /* noop     */
+                       3                       /* updated  */
+               },
+               /* redundant */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       1,                      /* fail     */
+                       MOD_ACTION_RETURN,      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       MOD_ACTION_RETURN,      /* notfound */
+                       MOD_ACTION_RETURN,      /* noop     */
+                       MOD_ACTION_RETURN       /* updated  */
+               },
+               /* append */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       1,                      /* fail     */
+                       MOD_ACTION_RETURN,      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       2,                      /* notfound */
+                       MOD_ACTION_RETURN,      /* noop     */
+                       MOD_ACTION_RETURN       /* updated  */
+               }
+       },
+       /* accounting */
+       {
+               /* group */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       MOD_ACTION_RETURN,      /* fail     */
+                       2,                      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       MOD_ACTION_RETURN,      /* notfound */
+                       1,                      /* noop     */
+                       3                       /* updated  */
+               },
+               /* redundant */
+               {
+                       1,                      /* reject   */
+                       1,                      /* fail     */
+                       3,                      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       1,                      /* invalid  */
+                       1,                      /* userlock */
+                       1,                      /* notfound */
+                       2,                      /* noop     */
+                       4                       /* updated  */
+               },
+               /* append */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       1,                      /* fail     */
+                       MOD_ACTION_RETURN,      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       2,                      /* notfound */
+                       MOD_ACTION_RETURN,      /* noop     */
+                       MOD_ACTION_RETURN       /* updated  */
+               }
+       },
+       /* checksimul */
+       {
+               /* group */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       1,                      /* fail     */
+                       MOD_ACTION_RETURN,      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       MOD_ACTION_RETURN,      /* notfound */
+                       MOD_ACTION_RETURN,      /* noop     */
+                       MOD_ACTION_RETURN       /* updated  */
+               },
+               /* redundant */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       1,                      /* fail     */
+                       MOD_ACTION_RETURN,      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       MOD_ACTION_RETURN,      /* notfound */
+                       MOD_ACTION_RETURN,      /* noop     */
+                       MOD_ACTION_RETURN       /* updated  */
+               },
+               /* append */
+               {
+                       MOD_ACTION_RETURN,      /* reject   */
+                       1,                      /* fail     */
+                       MOD_ACTION_RETURN,      /* ok       */
+                       MOD_ACTION_RETURN,      /* handled  */
+                       MOD_ACTION_RETURN,      /* invalid  */
+                       MOD_ACTION_RETURN,      /* userlock */
+                       MOD_ACTION_RETURN,      /* notfound */
+                       MOD_ACTION_RETURN,      /* noop     */
+                       MOD_ACTION_RETURN       /* updated  */
+               }
+       }
+};
+
+/* Bail out if the module in question does not supply the wanted component */
+static void sanity_check(int component, module_instance_t *inst, int lineno,
+                        const char *filename)
+{
+       switch (component) {
+               case RLM_COMPONENT_AUTH:
+                       if (!inst->entry->module->authenticate) {
+                               radlog(L_ERR|L_CONS,
+                                       "%s[%d] Module %s does not contain "
+                                       "an 'authenticate' entry\n",
+                                       filename, lineno,
+                                       inst->entry->module->name);
+                               exit(1);
+                       }
+                       break;
+               case RLM_COMPONENT_AUTZ:
+                       if (!inst->entry->module->authorize) {
+                               radlog(L_ERR|L_CONS,
+                                       "%s[%d] Module %s does not contain "
+                                       "an 'authorize' entry\n",
+                                       filename, lineno,
+                                       inst->entry->module->name);
+                               exit(1);
+                       }
+                       break;
+               case RLM_COMPONENT_PREACCT:
+                       if (!inst->entry->module->preaccounting) {
+                               radlog(L_ERR|L_CONS,
+                                       "%s[%d] Module %s does not contain "
+                                       "a 'preacct' entry\n",
+                                       filename, lineno,
+                                       inst->entry->module->name);
+                               exit(1);
+                       }
+                       break;
+               case RLM_COMPONENT_ACCT:
+                       if (!inst->entry->module->accounting) {
+                               radlog(L_ERR|L_CONS,
+                                       "%s[%d] Module %s does not contain "
+                                       "an 'accounting' entry\n",
+                                       filename, lineno,
+                                       inst->entry->module->name);
+                               exit(1);
+                       }
+                       break;
+               case RLM_COMPONENT_SESS:
+                       if (!inst->entry->module->checksimul) {
+                               radlog(L_ERR|L_CONS,
+                                       "%s[%d] Module %s does not contain "
+                                       "a 'checksimul' entry\n",
+                                       filename, lineno,
+                                       inst->entry->module->name);
+                               exit(1);
+                       }
+                       break;
+               default:
+                       radlog(L_ERR|L_CONS, "%s[%d] Unknown component %d.\n",
+                              filename, lineno, component);
+                       exit(1);
+       }
+}
+
+/* Parse a CONF_SECTION containing only result=action pairs */
+static void override_actions(modcallable *c, CONF_SECTION *cs,
+                            const char *filename)
+{
+       CONF_ITEM *ci;
+       CONF_PAIR *cp;
+       const char *attr, *value;
+       int lineno, rcode, action;
+
+       for(ci=cf_item_find_next(cs, NULL); ci; ci=cf_item_find_next(cs, ci)) {
+               if(cf_item_is_section(ci)) {
+                       radlog(L_ERR|L_CONS,
+                               "%s[%d] Subsection of module instance call "
+                               "not allowed\n", filename,
+                               cf_section_lineno(cf_itemtosection(ci)));
+                       exit(1);
+               }
+               cp = cf_itemtopair(ci);
+               attr = cf_pair_attr(cp);
+               value = cf_pair_value(cp);
+               lineno = cf_pair_lineno(cp);
+               rcode = str2rcode(attr, filename, lineno);
+               action = str2action(value, filename, lineno);
+               c->actions[rcode] = action;
+       }
+}
+
+static modcallable *do_compile_modsingle(int component, CONF_ITEM *ci,
+                                        const char *filename, int grouptype,
+                                        char **modname)
+{
+       int lineno;
+       const char *modrefname;
+       modsingle *single;
+       modcallable *csingle;
+       module_instance_t *this;
+
+       if(cf_item_is_section(ci)) {
+               CONF_SECTION *cs = cf_itemtosection(ci);
+               lineno = cf_section_lineno(cs);
+               modrefname = cf_section_name1(cs);
+       } else {
+               CONF_PAIR *cp = cf_itemtopair(ci);
+               lineno = cf_pair_lineno(cp);
+               modrefname = cf_pair_attr(cp);
+       }
+
+       single = rad_malloc(sizeof *single);
+       csingle = mod_singletocallable(single);
+       csingle->next = NULL;
+       memcpy(csingle->actions,
+               defaultactions[component][grouptype],
+               sizeof csingle->actions);
+       csingle->lineno = lineno;
+       csingle->type = MOD_SINGLE;
+
+       if(cf_item_is_section(ci)) {
+               /* override default actions with what's in the CONF_SECTION */
+               override_actions(csingle, cf_itemtosection(ci), filename);
+       }
+
+       this = find_module_instance(modrefname);
+       if (this == NULL) {
+               exit(1); /* FIXME */
+       }
+
+       sanity_check(component, this, csingle->lineno, filename);
+
+       single->modinst = this;
+       *modname = this->entry->name;
+       return csingle;
+}
+
+modcallable *compile_modsingle(int component, CONF_ITEM *ci,
+                              const char *filename, char **modname)
+{
+       return do_compile_modsingle(component, ci, filename,
+                                   GROUPTYPE_SIMPLEGROUP, modname);
+}
+
+static modcallable *do_compile_modgroup(int component, CONF_SECTION *cs,
+                                       const char *filename, int grouptype,
+                                       int parentgrouptype)
+{
+       modgroup *g;
+       modcallable *c;
+       CONF_ITEM *ci;
+
+       g = rad_malloc(sizeof *g);
+
+       c = mod_grouptocallable(g);
+       c->next = NULL;
+       memcpy(c->actions, defaultactions[component][parentgrouptype],
+               sizeof c->actions);
+       c->lineno = cf_section_lineno(cs);
+       c->type = MOD_GROUP;
+       g->children = NULL;
+
+       for(ci=cf_item_find_next(cs, NULL); ci; ci=cf_item_find_next(cs, ci)) {
+               if(cf_item_is_section(ci)) {
+                       CONF_SECTION *scs = cf_itemtosection(ci);
+                       const char *name1;
+                       modcallable *childgroup;
+
+                       name1 = cf_section_name1(scs);
+
+                       /* subsections may be group{}, redundant{}, or
+                        * append{}... */
+                       if(!strcmp(name1, "group")) {
+                               childgroup = do_compile_modgroup(component,
+                                       scs, filename, GROUPTYPE_SIMPLEGROUP,
+                                       grouptype);
+                               add_child(g, childgroup);
+                       } else if(!strcmp(name1, "redundant")) {
+                               childgroup = do_compile_modgroup(component,
+                                       scs, filename, GROUPTYPE_REDUNDANT,
+                                       grouptype);
+                               add_child(g, childgroup);
+                       } else if(!strcmp(name1, "append")) {
+                               childgroup = do_compile_modgroup(component,
+                                       scs, filename, GROUPTYPE_APPEND,
+                                       grouptype);
+                               add_child(g, childgroup);
+                       } else {
+                               /* ...or a module instance with some actions
+                                * specified. */
+                               modcallable *single;
+                               char *junk;
+
+                               single = do_compile_modsingle(component,
+                                               cf_sectiontoitem(scs), filename,
+                                               grouptype, &junk);
+                               add_child(g, single);
+                       }
+               } else {
+                       const char *attr, *value;
+                       CONF_PAIR *cp = cf_itemtopair(ci);
+                       int lineno;
+
+                       attr = cf_pair_attr(cp);
+                       value = cf_pair_value(cp);
+                       lineno = cf_pair_lineno(cp);
+
+                       /* A CONF_PAIR is either a module instance with no
+                        * actions specified... */
+                       if(value[0]==0) {
+                               modcallable *single;
+                               char *junk;
+
+                               single = do_compile_modsingle(component,
+                                       cf_pairtoitem(cp), filename,
+                                       grouptype, &junk);
+                               add_child(g, single);
+                       } else {
+                               /* ...or an action to be applied to this
+                                * group. */
+                               int rcode, action;
+                               rcode = str2rcode(attr, filename, lineno);
+                               action = str2action(value, filename, lineno);
+
+                               c->actions[rcode] = action;
+                       }
+               }
+       }
+       return mod_grouptocallable(g);
+}
+
+modcallable *compile_modgroup(int component, CONF_SECTION *cs,
+                             const char *filename)
+{
+       modcallable *ret = do_compile_modgroup(component, cs, filename,
+                                               GROUPTYPE_SIMPLEGROUP,
+                                               GROUPTYPE_SIMPLEGROUP);
+       /*dump_tree(component, ret);*/
+       return ret;
+}
+
+void add_to_modcallable(modcallable **parent, modcallable *this,
+                       int component, int lineno)
+{
+       modgroup *g;
+
+       if(!*parent) {
+               modcallable *c;
+
+               g = rad_malloc(sizeof *g);
+               c = mod_grouptocallable(g);
+               c->next = NULL;
+               memcpy(c->actions,
+                       defaultactions[component][GROUPTYPE_SIMPLEGROUP],
+                       sizeof c->actions);
+               c->lineno = lineno;
+               c->type = MOD_GROUP;
+               g->children = NULL;
+
+               *parent = mod_grouptocallable(g);
+       } else {
+               g = mod_callabletogroup(*parent);
+       }
+
+       add_child(g, this);
+}
+
+void modcallable_free(modcallable **pc)
+{
+       modcallable *c, *loop, *next;
+       c = *pc;
+       if(c->type==MOD_GROUP) {
+               for(loop=mod_callabletogroup(c)->children ; loop ; loop=next) {
+                       next = loop->next;
+                       modcallable_free(&loop);
+               }
+       }
+       free(c);
+       *pc = NULL;
+}
index 83d65d4..c76ba8a 100644 (file)
@@ -18,65 +18,32 @@ static const char rcsid[] = "$Id$";
 #include       <assert.h>
 
 #include       "radiusd.h"
+#include       "modpriv.h"
 #include       "modules.h"
+#include       "modcall.h"
 #include       "conffile.h"
 #include       "ltdl.h"
 
 /*
- *     Keep track of which modules we've loaded.
- */
-typedef struct module_list_t {
-       struct module_list_t    *next;
-       char                    name[MAX_STRING_LEN];
-       module_t                *module;
-       lt_dlhandle             handle;
-} module_list_t;
-
-/*
  *     Internal list of all of the modules we have loaded.
  */
 static module_list_t *module_list = NULL;
 
 /*
- *     Per-instance data structure, to correlate the modules
- *     with the instance names (may NOT be the module names!), 
- *     and the per-instance data structures.
- */
-typedef struct module_instance_t {
-       struct module_instance_t *next;
-       char                    name[MAX_STRING_LEN];
-       module_list_t           *entry;
-        void                    *insthandle;
-#if HAVE_PTHREAD_H
-       pthread_mutex_t         *mutex;
-#endif
-} module_instance_t;
-
-/*
  *     Internal list of each module instance.
  */
 static module_instance_t *module_instance_list = NULL;
 
-/*
- *     For each authorize/authtype/etc, we have an ordered
- *     list of instances to call.  This data structure keeps track
- *     of that order.
- */
-typedef struct config_module_t {
-       struct config_module_t  *next;
-       module_instance_t       *instance;
-} config_module_t;
-
-typedef struct indexed_config_module_t {
-       struct indexed_config_module_t *next;
+typedef struct indexed_modcallable {
+       struct indexed_modcallable *next;
        int idx;
-       config_module_t *modulelist;
-} indexed_config_module_t;
+       modcallable *modulelist;
+} indexed_modcallable;
 
 /*
  *     For each component, keep an ordered list of ones to call.
  */
-static indexed_config_module_t *components[RLM_COMPONENT_COUNT];
+static indexed_modcallable *components[RLM_COMPONENT_COUNT];
 
 /*
  *     The component names.
@@ -102,27 +69,14 @@ static const char *subcomponent_names[RLM_COMPONENT_COUNT] =
   "sesstype"
 };
 
-static void config_list_free(config_module_t **cf)
+static void indexed_modcallable_free(indexed_modcallable **cf)
 {
-       config_module_t *c, *next;
+       indexed_modcallable     *c, *next;
 
        c = *cf;
        while (c) {
                next = c->next;
-               free(c);
-               c = next;
-       }
-       *cf = NULL;
-}
-
-static void indexed_config_list_free(indexed_config_module_t **cf)
-{
-       indexed_config_module_t *c, *next;
-
-       c = *cf;
-       while (c) {
-               next = c->next;
-               config_list_free(&c->modulelist);
+               modcallable_free(&c->modulelist);
                free(c);
                c = next;
        }
@@ -163,7 +117,7 @@ static void module_list_free(void)
         *      Delete the internal component pointers.
         */
        for (i = 0; i < RLM_COMPONENT_COUNT; i++) {
-               indexed_config_list_free(&components[i]);
+               indexed_modcallable_free(&components[i]);
        }
 
        instance_list_free(&module_instance_list);
@@ -298,7 +252,7 @@ static module_list_t *linkto_module(const char *module_name,
 /*
  *     Find a module instance.
  */
-static module_instance_t *find_module_instance(const char *instname)
+module_instance_t *find_module_instance(const char *instname)
 {
        CONF_SECTION *cs, *inst_cs;
        const char *name1, *name2;
@@ -389,7 +343,7 @@ static module_instance_t *find_module_instance(const char *instname)
                free(node);
                return NULL;
        }
-       
+
        /*
         *      We're done.  Fill in the rest of the data structure,
         *      and link it to the module instance list.
@@ -423,9 +377,9 @@ static module_instance_t *find_module_instance(const char *instname)
        return node;
 }
 
-static indexed_config_module_t *lookup_by_index(indexed_config_module_t *head, int idx)
+static indexed_modcallable *lookup_by_index(indexed_modcallable *head, int idx)
 {
-       indexed_config_module_t *p;
+       indexed_modcallable *p;
 
        for (p = head; p != NULL; p = p->next) {
                if( p->idx == idx)
@@ -434,43 +388,11 @@ static indexed_config_module_t *lookup_by_index(indexed_config_module_t *head, i
        return NULL;
 }
 
-/*
- *     Add one entry at the end of the config_module_t list.
- */
-static void add_to_list(int comp, module_instance_t *instance, int idx)
+static indexed_modcallable *new_sublist(int comp, int idx)
 {
-       indexed_config_module_t *subcomp;
-       config_module_t *node;
-       config_module_t **last;
-       config_module_t **head;
-       
-       /* Step 1 - find the list corresponding to the given index. The
-        * caller is responsible for ensuring that one exists by calling
-        * new_sublist before calling add_to_list. */
-       subcomp = lookup_by_index(components[comp], idx);
-       assert(subcomp);
-
-       /* Step 2 - walk to the end of that list */
-       head = &subcomp->modulelist;
-       last = head;
-
-       for (node = *head; node != NULL; node = node->next) {
-               last = &node->next;
-       }
-
-       /* Step 3 - put a new config_module_t there */
-       node = (config_module_t *) rad_malloc(sizeof(config_module_t));
-       node->next = NULL;
-       node->instance = instance;
-
-       *last = node;
-}
-
-static indexed_config_module_t *new_sublist(int comp, int idx)
-{
-       indexed_config_module_t **head = &components[comp];
-       indexed_config_module_t *node = *head;
-       indexed_config_module_t **last = head;
+       indexed_modcallable **head = &components[comp];
+       indexed_modcallable *node = *head;
+       indexed_modcallable **last = head;
 
        while (node) {
                /* It is an error to try to create a sublist that already
@@ -499,71 +421,30 @@ static indexed_config_module_t *new_sublist(int comp, int idx)
        return node;
 }
 
-/* Bail out if the module in question does not supply the wanted component */
-static void sanity_check(int comp, module_t *mod, const char *filename,
-                        int lineno)
+static int indexed_modcall(int comp, int idx, REQUEST *request)
 {
-       switch (comp) {
-       case RLM_COMPONENT_AUTH:
-               if (!mod->authenticate) {
-                       radlog(L_ERR|L_CONS,
-                               "%s[%d] Module %s does not contain "
-                               "an 'authenticate' entry\n",
-                               filename, lineno, mod->name);
-                       exit(1);
-               }
-               break;
-       case RLM_COMPONENT_AUTZ:
-               if (!mod->authorize) {
-                       radlog(L_ERR|L_CONS,
-                               "%s[%d] Module %s does not contain "
-                               "an 'authorize' entry\n",
-                               filename, lineno, mod->name);
-                       exit(1);
-               }
-               break;
-       case RLM_COMPONENT_PREACCT:
-               if (!mod->preaccounting) {
-                       radlog(L_ERR|L_CONS,
-                               "%s[%d] Module %s does not contain "
-                               "a 'preacct' entry\n",
-                               filename, lineno, mod->name);
-                       exit(1);
-               }
-               break;
-       case RLM_COMPONENT_ACCT:
-               if (!mod->accounting) {
-                       radlog(L_ERR|L_CONS,
-                               "%s[%d] Module %s does not contain "
-                               "an 'accounting' entry\n",
-                               filename, lineno, mod->name);
-                       exit(1);
-               }
-               break;
-       case RLM_COMPONENT_SESS:
-               if (!mod->checksimul) {
-                       radlog(L_ERR|L_CONS,
-                               "%s[%d] Module %s does not contain "
-                               "a 'checksimul' entry\n",
-                               filename, lineno, mod->name);
-                       exit(1);
+       indexed_modcallable *this;
+
+       this = lookup_by_index(components[comp], idx);
+       if (!this) {
+               /* Return a default value appropriate for the component */
+               switch(comp) {
+                       case RLM_COMPONENT_AUTZ:    return RLM_MODULE_NOTFOUND;
+                       case RLM_COMPONENT_AUTH:    return RLM_MODULE_REJECT;
+                       case RLM_COMPONENT_PREACCT: return RLM_MODULE_NOOP;
+                       case RLM_COMPONENT_ACCT:    return RLM_MODULE_NOOP;
+                       case RLM_COMPONENT_SESS:    return RLM_MODULE_FAIL;
+                       default:                    return RLM_MODULE_FAIL;
                }
-               break;
-       default:
-               radlog(L_ERR|L_CONS, "%s[%d] Unknown component %d.\n",
-                       filename, lineno, comp);
-               exit(1);
        }
+       return modcall(comp, this->modulelist, request);
 }
 
 /* Load a flat module list, as found inside an authtype{} block */
 static void load_subcomponent_section(CONF_SECTION *cs, int comp, const char *filename)
 {
-       module_instance_t *this;
-       CONF_ITEM *modref;
-        int modreflineno;
-        const char *modrefname;
        int idx;
+       indexed_modcallable *subcomp;
 
        static int meaningless_counter = 1;
 
@@ -574,16 +455,13 @@ static void load_subcomponent_section(CONF_SECTION *cs, int comp, const char *fi
         * nor checked for uniqueness, but all that could be fixed in a few
         * minutes, if anyone finds a real use for indexed config of
         * components other than auth. */
-       switch (comp) {
-       case RLM_COMPONENT_AUTH:
+       if (comp==RLM_COMPONENT_AUTH)
                idx = new_authtype_value(cf_section_name2(cs));
-               break;
-       default:
+       else
                idx = meaningless_counter++;
-               break;
-       }
        
-       if (!new_sublist(comp, idx)) {
+       subcomp = new_sublist(comp, idx);
+       if (!subcomp) {
                radlog(L_ERR|L_CONS,
                       "%s[%d] %s %s already configured - skipping",
                       filename, cf_section_lineno(cs),
@@ -591,40 +469,17 @@ static void load_subcomponent_section(CONF_SECTION *cs, int comp, const char *fi
                return;
        }
 
-       for(modref=cf_item_find_next(cs, NULL)
-           ; modref ;
-           modref=cf_item_find_next(cs, modref)) {
-
-               if(cf_item_is_section(modref)) {
-                       CONF_SECTION *scs;
-                       scs = cf_itemtosection(modref);
-                       modreflineno = cf_section_lineno(scs);
-                       modrefname = cf_section_name1(scs);
-               } else {
-                       CONF_PAIR *cp;
-                       cp = cf_itemtopair(modref);
-                       modreflineno = cf_pair_lineno(cp);
-                       modrefname = cf_pair_attr(cp);
-               }
-
-               this = find_module_instance(modrefname);
-               if (this == NULL) {
-                       /* find_module_instance logs any errors */
-                       exit(1);
-               }
-
-               sanity_check(comp, this->entry->module, filename, modreflineno);
-               add_to_list(comp, this, idx);
-       }
+       subcomp->modulelist = compile_modgroup(comp, cs, filename);
 }
 
 static void load_component_section(CONF_SECTION *cs, int comp, const char *filename)
 {
-       module_instance_t *this;
+       modcallable     *this;
        CONF_ITEM       *modref;
         int            modreflineno;
-        const char     *modrefname;
        int             idx;
+       indexed_modcallable *subcomp;
+       char            *modname;
 
        for(modref=cf_item_find_next(cs, NULL)
            ; modref ;
@@ -633,53 +488,44 @@ static void load_component_section(CONF_SECTION *cs, int comp, const char *filen
                if(cf_item_is_section(modref)) {
                        CONF_SECTION *scs;
                        scs = cf_itemtosection(modref);
+
                        if (!strcmp(cf_section_name1(scs),
                                    subcomponent_names[comp])) {
                                load_subcomponent_section(scs, comp, filename);
                                continue;
                        }
+
                        modreflineno = cf_section_lineno(scs);
-                       modrefname = cf_section_name1(scs);
                } else {
                        CONF_PAIR *cp;
                        cp = cf_itemtopair(modref);
                        modreflineno = cf_pair_lineno(cp);
-                       modrefname = cf_pair_attr(cp);
                }
 
-               /*
-                *      Find an instance for this module.
-                *      This means link to one if it already exists,
-                *      or instantiate one, or load the library and
-                *      instantiate/link.
-                */
-               this = find_module_instance(modrefname);
-               if (this == NULL) {
-                       /* find_module_instance logs any errors */
-                       exit(1);
-               }
-
-               sanity_check(comp, this->entry->module, filename, modreflineno);
+               this = compile_modsingle(comp, modref, filename, &modname);
 
-               switch (comp) {
-               case RLM_COMPONENT_AUTH:
-                       idx = new_authtype_value(this->name);
-                       break;
-               default:
+               if (comp==RLM_COMPONENT_AUTH) {
+                       idx = new_authtype_value(modname);
+               } else {
                        /* See the comment in new_sublist() for explanation
                         * of the special index 0 */
                        idx = 0;
-                       break;
                }
 
-               if (!new_sublist(comp, idx)) {
+               subcomp = new_sublist(comp, idx);
+               if (!subcomp) {
                        radlog(L_ERR|L_CONS,
                            "%s[%d] %s %s already configured - skipping",
                            filename, modreflineno, subcomponent_names[comp],
-                           this->name);
+                           modname);
+                       modcallable_free(&this);
                        continue;
                }
-               add_to_list(comp, this, idx);
+
+               /* If subcomp->modulelist is NULL, add_to_modcallable will
+                * create it */
+               add_to_modcallable(&subcomp->modulelist, this,
+                                       comp, modreflineno);
        }
 }
 
@@ -751,52 +597,13 @@ int setup_modules(void)
        return 0;
 }
 
-#if HAVE_PTHREAD_H
-/*
- *     Lock the mutex for the module
- */
-static void safe_lock(module_instance_t *instance)
-{
-       if (instance->mutex) pthread_mutex_lock(instance->mutex);
-}
-
-/*
- *     Unlock the mutex for the module
- */
-static void safe_unlock(module_instance_t *instance)
-{
-       if (instance->mutex) pthread_mutex_unlock(instance->mutex);
-}
-#else
-/*
- *     No threads: these functions become NULL's.
- */
-#define safe_lock(foo)
-#define safe_unlock(foo)
-#endif
-
 /*
  *     Call all authorization modules until one returns
  *     somethings else than RLM_MODULE_OK
  */
 int module_authorize(REQUEST *request)
 {
-       config_module_t *this;
-       int             rcode = RLM_MODULE_OK;
-
-       this = lookup_by_index(components[RLM_COMPONENT_AUTZ], 0)->modulelist;
-       rcode = RLM_MODULE_OK;
-
-       while (this && rcode == RLM_MODULE_OK) {
-               DEBUG2("  authorize: %s", this->instance->entry->module->name);
-               safe_lock(this->instance);
-               rcode = (this->instance->entry->module->authorize)(
-                        this->instance->insthandle, request);
-               safe_unlock(this->instance);
-               this = this->next;
-       }
-
-       return rcode;
+       return indexed_modcall(RLM_COMPONENT_AUTZ, 0, request);
 }
 
 /*
@@ -804,47 +611,15 @@ int module_authorize(REQUEST *request)
  */
 int module_authenticate(int auth_type, REQUEST *request)
 {
-       config_module_t *this;
-       int             rcode = RLM_MODULE_FAIL;
-
-       this = lookup_by_index(components[RLM_COMPONENT_AUTH],
-                              auth_type)->modulelist;
-
-       while (this && rcode == RLM_MODULE_FAIL) {
-               DEBUG2("  authenticate: %s",
-                      this->instance->entry->module->name);
-               safe_lock(this->instance);
-               rcode = (this->instance->entry->module->authenticate)(
-                        this->instance->insthandle, request);
-               safe_unlock(this->instance);
-               this = this->next;
-       }
-
-       return rcode;
+       return indexed_modcall(RLM_COMPONENT_AUTH, auth_type, request);
 }
 
-
 /*
  *     Do pre-accounting for ALL configured sessions
  */
 int module_preacct(REQUEST *request)
 {
-       config_module_t *this;
-       int             rcode;
-
-       this = lookup_by_index(components[RLM_COMPONENT_PREACCT], 0)->modulelist;
-       rcode = RLM_MODULE_OK;
-
-       while (this && (rcode == RLM_MODULE_OK)) {
-               DEBUG2("  preacct: %s", this->instance->entry->module->name);
-               safe_lock(this->instance);
-               rcode = (this->instance->entry->module->preaccounting)
-                               (this->instance->insthandle, request);
-               safe_unlock(this->instance);
-               this = this->next;
-       }
-
-       return rcode;
+       return indexed_modcall(RLM_COMPONENT_PREACCT, 0, request);
 }
 
 /*
@@ -852,22 +627,7 @@ int module_preacct(REQUEST *request)
  */
 int module_accounting(REQUEST *request)
 {
-       config_module_t *this;
-       int             rcode;
-
-       this = lookup_by_index(components[RLM_COMPONENT_ACCT], 0)->modulelist;
-       rcode = RLM_MODULE_OK;
-
-       while (this && (rcode == RLM_MODULE_OK)) {
-               DEBUG2("  accounting: %s", this->instance->entry->module->name);
-               safe_lock(this->instance);
-               rcode = (this->instance->entry->module->accounting)
-                               (this->instance->insthandle, request);
-               safe_unlock(this->instance);
-               this = this->next;
-       }
-
-       return rcode;
+       return indexed_modcall(RLM_COMPONENT_ACCT, 0, request);
 }
 
 /*
@@ -877,7 +637,6 @@ int module_accounting(REQUEST *request)
  */
 int module_checksimul(REQUEST *request, int maxsimul)
 {
-       config_module_t *this;
        int             rcode;
 
        if(!components[RLM_COMPONENT_SESS])
@@ -890,17 +649,7 @@ int module_checksimul(REQUEST *request, int maxsimul)
        request->simul_max = maxsimul;
        request->simul_mpp = 1;
 
-       this = lookup_by_index(components[RLM_COMPONENT_SESS], 0)->modulelist;
-       rcode = RLM_MODULE_FAIL;
-
-       while (this && (rcode == RLM_MODULE_FAIL)) {
-               DEBUG2("  checksimul: %s", this->instance->entry->module->name);
-               safe_lock(this->instance);
-               rcode = (this->instance->entry->module->checksimul)
-                               (this->instance->insthandle, request);
-               safe_unlock(this->instance);
-               this = this->next;
-       }
+       rcode = indexed_modcall(RLM_COMPONENT_SESS, 0, request);
 
        if(rcode != RLM_MODULE_OK) {
                /* FIXME: Good spot for a *rate-limited* warning to the log */