*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*
- * Copyright 2000 The FreeRADIUS server project
+ * Copyright 2000,2006 The FreeRADIUS server project
*/
-#include <stdlib.h>
-#include <string.h>
-#include <stdarg.h>
-#include "radiusd.h"
-#include "rad_assert.h"
-#include "conffile.h"
-#include "modpriv.h"
-#include "modules.h"
-#include "modcall.h"
+#include <freeradius-devel/ident.h>
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modpriv.h>
+#include <freeradius-devel/modcall.h>
+#include <freeradius-devel/rad_assert.h>
+
+extern int radius_get_vp(REQUEST *request, const char *name, VALUE_PAIR **vp_p);
/* mutually-recursive static functions need a prototype up front */
-static modcallable *do_compile_modgroup(int, CONF_SECTION *, const char *,
- int, int);
+static modcallable *do_compile_modgroup(modcallable *,
+ int, CONF_SECTION *,
+ int, int);
/* 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.
* There's also the keyword "reject", represented here by MOD_ACTION_REJECT
* to cause an immediate reject. */
-#define MOD_ACTION_RETURN (-1)
-#define MOD_ACTION_REJECT (-2)
+#define MOD_ACTION_RETURN (-1)
+#define MOD_ACTION_REJECT (-2)
/* Here are our basic types: modcallable, modgroup, and modsingle. For an
* explanation of what they are all about, see ../../doc/README.failover */
struct modcallable {
+ modcallable *parent;
struct modcallable *next;
const char *name;
+ enum { MOD_SINGLE = 1, MOD_GROUP, MOD_LOAD_BALANCE, MOD_REDUNDANT_LOAD_BALANCE,
+#ifdef WITH_UNLANG
+ MOD_IF, MOD_ELSE, MOD_ELSIF, MOD_UPDATE, MOD_SWITCH, MOD_CASE,
+#endif
+ MOD_POLICY, MOD_REFERENCE, MOD_XLAT } type;
+ int method;
int actions[RLM_MODULE_NUMCODES];
- enum { MOD_SINGLE, MOD_GROUP, MOD_LOAD_BALANCE, MOD_REDUNDANT_LOAD_BALANCE } type;
};
#define GROUPTYPE_SIMPLE 0
#define GROUPTYPE_COUNT 3
typedef struct {
- modcallable mc;
- int grouptype;
+ modcallable mc; /* self */
+ int grouptype; /* after mc */
modcallable *children;
+ CONF_SECTION *cs;
+ VALUE_PAIR *vps;
} modgroup;
typedef struct {
module_instance_t *modinst;
} modsingle;
+typedef struct {
+ modcallable mc;
+ const char *ref_name;
+ CONF_SECTION *ref_cs;
+} modref;
+
+typedef struct {
+ modcallable mc;
+ int exec;
+ char *xlat_name;
+} modxlat;
-static const LRAD_NAME_NUMBER grouptype_table[] = {
+static const FR_NAME_NUMBER grouptype_table[] = {
{ "", GROUPTYPE_SIMPLE },
{ "redundant ", GROUPTYPE_REDUNDANT },
{ "append ", GROUPTYPE_APPEND },
}
static modgroup *mod_callabletogroup(modcallable *p)
{
- rad_assert((p->type==MOD_GROUP) ||
- (p->type==MOD_LOAD_BALANCE) ||
- (p->type==MOD_REDUNDANT_LOAD_BALANCE));
+ rad_assert((p->type > MOD_SINGLE) && (p->type <= MOD_POLICY));
+
return (modgroup *)p;
}
static modcallable *mod_singletocallable(modsingle *p)
return (modcallable *)p;
}
+static modref *mod_callabletoref(modcallable *p)
+{
+ rad_assert(p->type==MOD_REFERENCE);
+ return (modref *)p;
+}
+static modcallable *mod_reftocallable(modref *p)
+{
+ return (modcallable *)p;
+}
+
+static modxlat *mod_callabletoxlat(modcallable *p)
+{
+ rad_assert(p->type==MOD_XLAT);
+ return (modxlat *)p;
+}
+static modcallable *mod_xlattocallable(modxlat *p)
+{
+ return (modcallable *)p;
+}
+
/* modgroups are grown by adding a modcallable to the end */
+/* FIXME: This is O(N^2) */
static void add_child(modgroup *g, modcallable *c)
{
modcallable **head = &g->children;
rad_assert(c->next == NULL);
*last = c;
+ c->parent = mod_grouptocallable(g);
}
/* Here's where we recognize all of our keywords: first the rcodes, then the
* actions */
-static const LRAD_NAME_NUMBER rcode_table[] = {
+static const FR_NAME_NUMBER rcode_table[] = {
{ "reject", RLM_MODULE_REJECT },
{ "fail", RLM_MODULE_FAIL },
{ "ok", RLM_MODULE_OK },
/*
* Compile action && rcode for later use.
*/
-static int compile_action(modcallable *c, const char *attr, const char *value,
- const char *filename, int lineno)
+static int compile_action(modcallable *c, CONF_PAIR *cp)
{
- int rcode, action;
+ int action;
+ const char *attr, *value;
- rcode = lrad_str2int(rcode_table, attr, -1);
- if (rcode < 0) {
- radlog(L_ERR|L_CONS,
- "%s[%d] Unknown module rcode '%s'.\n",
- filename, lineno, attr);
- return 0;
- }
+ attr = cf_pair_attr(cp);
+ value = cf_pair_value(cp);
+ if (!value) return 0;
if (!strcasecmp(value, "return"))
action = MOD_ACTION_RETURN;
+ else if (!strcasecmp(value, "break"))
+ action = MOD_ACTION_RETURN;
+
else if (!strcasecmp(value, "reject"))
action = MOD_ACTION_REJECT;
else if (strspn(value, "0123456789")==strlen(value)) {
action = atoi(value);
-
+
/*
* Don't allow priority zero, for future use.
*/
if (action == 0) return 0;
} else {
- radlog(L_ERR|L_CONS,
- "%s[%d] Unknown action '%s'.\n",
- filename, lineno, value);
+ cf_log_err(cf_pairtoitem(cp), "Unknown action '%s'.\n",
+ value);
return 0;
}
- c->actions[rcode] = action;
+ if (strcasecmp(attr, "default") != 0) {
+ int rcode;
- return 1;
-}
+ rcode = fr_str2int(rcode_table, attr, -1);
+ if (rcode < 0) {
+ cf_log_err(cf_pairtoitem(cp),
+ "Unknown module rcode '%s'.\n",
+ attr);
+ return 0;
+ }
+ c->actions[rcode] = action;
-#if 0
-static const char *action2str(int action)
-{
- static char buf[32];
- if(action==MOD_ACTION_RETURN)
- return "return";
- if(action==MOD_ACTION_REJECT)
- return "reject";
- snprintf(buf, sizeof buf, "%d", action);
- return buf;
+ } else { /* set all unset values to the default */
+ int i;
+
+ for (i = 0; i < RLM_MODULE_NUMCODES; i++) {
+ if (!c->actions[i]) c->actions[i] = action;
+ }
+ }
+
+ return 1;
}
-#endif
/* Some short names for debugging output */
-static const char *comp2str[] = {
+static const char * const comp2str[] = {
"authenticate",
"authorize",
"preacct",
"pre-proxy",
"post-proxy",
"post-auth"
+#ifdef WITH_COA
+ ,
+ "recv-coa",
+ "send-coa"
+#endif
};
#ifdef HAVE_PTHREAD_H
#define safe_unlock(foo)
#endif
-static int call_modsingle(int component, modsingle *sp, REQUEST *request,
- int default_result)
+static int call_modsingle(int component, modsingle *sp, REQUEST *request)
{
- int myresult = default_result;
+ int myresult;
+ int blocked;
+
+ rad_assert(request != NULL);
+
+ /*
+ * If the request should stop, refuse to do anything.
+ */
+ blocked = (request->master_state == REQUEST_STOP_PROCESSING);
+ if (blocked) return RLM_MODULE_NOOP;
- DEBUG3(" modsingle[%s]: calling %s (%s) for request %d",
+ RDEBUG3(" modsingle[%s]: calling %s (%s) for request %d",
comp2str[component], sp->modinst->name,
sp->modinst->entry->name, request->number);
+
+ if (sp->modinst->force) {
+ myresult = sp->modinst->code;
+ goto fail;
+ }
+
safe_lock(sp->modinst);
+
+ /*
+ * For logging unresponsive children.
+ */
+ request->module = sp->modinst->name;
+
myresult = sp->modinst->entry->module->methods[component](
sp->modinst->insthandle, request);
+
+ request->module = "";
safe_unlock(sp->modinst);
- DEBUG3(" modsingle[%s]: returned from %s (%s) for request %d",
+
+ /*
+ * Wasn't blocked, and now is. Complain!
+ */
+ blocked = (request->master_state == REQUEST_STOP_PROCESSING);
+ if (blocked) {
+ radlog(L_INFO, "WARNING: Module %s became unblocked for request %u",
+ sp->modinst->entry->name, request->number);
+ }
+
+ fail:
+ RDEBUG3(" modsingle[%s]: returned from %s (%s) for request %d",
comp2str[component], sp->modinst->name,
sp->modinst->entry->name, request->number);
}
+static int default_component_results[RLM_COMPONENT_COUNT] = {
+ RLM_MODULE_REJECT, /* AUTH */
+ RLM_MODULE_NOTFOUND, /* AUTZ */
+ RLM_MODULE_NOOP, /* PREACCT */
+ RLM_MODULE_NOOP, /* ACCT */
+ RLM_MODULE_FAIL, /* SESS */
+ RLM_MODULE_NOOP, /* PRE_PROXY */
+ RLM_MODULE_NOOP, /* POST_PROXY */
+ RLM_MODULE_NOOP /* POST_AUTH */
+#ifdef WITH_COA
+ ,
+ RLM_MODULE_NOOP, /* RECV_COA_TYPE */
+ RLM_MODULE_NOOP /* SEND_COA_TYPE */
+#endif
+};
+
+
+static const char *group_name[] = {
+ "",
+ "single",
+ "group",
+ "load-balance group",
+ "redundant-load-balance group",
+#ifdef WITH_UNLANG
+ "if",
+ "else",
+ "elsif",
+ "update",
+ "switch",
+ "case",
+#endif
+ "policy"
+};
+
+/* Here's where we recognize all of our keywords: first the rcodes, then the
+ * actions */
+const FR_NAME_NUMBER mod_rcode_table[] = {
+ { "reject", RLM_MODULE_REJECT },
+ { "fail", RLM_MODULE_FAIL },
+ { "ok", RLM_MODULE_OK },
+ { "handled", RLM_MODULE_HANDLED },
+ { "invalid", RLM_MODULE_INVALID },
+ { "userlock", RLM_MODULE_USERLOCK },
+ { "notfound", RLM_MODULE_NOTFOUND },
+ { "noop", RLM_MODULE_NOOP },
+ { "updated", RLM_MODULE_UPDATED },
+ { NULL, 0 }
+};
+
+static const char *modcall_spaces = "++++++++++++++++++++++++++++++++";
+
+#define MODCALL_STACK_MAX (32)
+
+#define MOD_LOG_OPEN_BRACE(_name) RDEBUG2("%.*s%s %s {", depth + 1, modcall_spaces, _name, c->name)
+#define MOD_LOG_CLOSE_BRACE() RDEBUG2("%.*s} # %s %s = %s", depth + 1, modcall_spaces, \
+ group_name[c->type], c->name ? c->name : "", \
+ fr_int2str(mod_rcode_table, result, "<invalid>"))
+
/*
- * Helper function for call_modgroup, and call_modredundantloadbalance
- *
- * Returns 0 for "stop", and "1" for continue.
+ * Don't call the modules recursively. Instead, do them
+ * iteratively, and manage the call stack ourselves.
*/
-static int call_one(int component, modcallable *p, REQUEST *request,
- int *priority, int *result)
-{
- int r;
+typedef struct modcall_stack_entry_t {
+ int result;
+ int priority;
+ modcallable *c;
+} modcall_stack_entry_t;
-#ifdef RAD_REQUEST_OPTION_STOP_NOW
- /*
- * A module has taken too long to process the request,
- * and we've been told to stop processing it.
- */
- if (request->options & RAD_REQUEST_OPTION_STOP_NOW) {
- *result = RLM_MODULE_FAIL;
- return 0;
- }
-#endif
-
- /* Call this child by recursing into modcall */
- r = modcall(component, p, request);
-
-#if 0
- DEBUG2("%s: action for %s is %s",
- comp2str[component], lrad_int2str(rcode_table, r, "??"),
- action2str(p->actions[r]));
-#endif
-
- /*
- * Find an action to go with the child's result. If it is
- * "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) {
- *result = r;
- return 0;
- }
-
- /* If "reject" break out of the loop and return reject */
- if (p->actions[r] == MOD_ACTION_REJECT) {
- *result = RLM_MODULE_REJECT;
- return 0;
- }
-
- /*
- * 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] >= *priority) {
- *result = r;
- *priority = p->actions[r];
- }
-
- return 1;
-}
+static int modcall_recurse(REQUEST *request, int component, int depth,
+ modcall_stack_entry_t *entry, int do_next_sibling);
-static int call_modgroup(int component, modgroup *g, REQUEST *request,
- int default_result)
+/*
+ * Call a child of a block.
+ */
+static void modcall_child(REQUEST *request, int component, int depth,
+ modcall_stack_entry_t *entry, modcallable *c,
+ int *result, int do_next_sibling)
{
- int myresult = default_result;
- int priority = 0; /* default result has lowest priority */
- modcallable *p;
+ modcall_stack_entry_t *next;
+
+ if (depth >= MODCALL_STACK_MAX) {
+ radlog(L_ERR, "Internal sanity check failed: module stack is too deep");
+ exit(1);
+ }
/*
- * Catch people who have issues.
+ * Initialize the childs stack frame.
*/
- if (!g->children) {
- DEBUG2(" WARNING! Asked to process empty group. Returning %s.", lrad_int2str(rcode_table, myresult, "??"));
- return default_result;
- }
+ next = entry + 1;
+ next->c = c;
+ next->result = entry->result;
+ next->priority = 0;
- /* Loop over the children */
- for (p = g->children; p; p = p->next) {
- if (!call_one(component, p, request, &priority, &myresult)) {
- break;
- }
+ if (!modcall_recurse(request, component,
+ depth, next, do_next_sibling)) {
+ *result = RLM_MODULE_FAIL;
+ return;
}
- return myresult;
+ *result = next->result;
+
+ return;
}
-static int call_modloadbalance(int component, modgroup *g, REQUEST *request,
- int default_result)
+/*
+ * Interpret the various types of blocks.
+ */
+static int modcall_recurse(REQUEST *request, int component, int depth,
+ modcall_stack_entry_t *entry, int do_next_sibling)
{
- int count = 1;
- modcallable *p, *child = NULL;
+ int if_taken, was_if;
+ modcallable *c;
+ int result, priority;
+
+ was_if = if_taken = FALSE;
+ result = RLM_MODULE_FAIL;
+
+redo:
+ priority = -1;
+ c = entry->c;
/*
- * Catch people who have issues.
+ * Nothing more to do. Return the code and priority
+ * which was set by the caller.
*/
- if (!g->children) {
- DEBUG2(" WARNING! Asked to process empty load-balance group. Returning %s.", lrad_int2str(rcode_table, default_result, "??"));
- return default_result;
- }
+ if (!c) return TRUE;
/*
- * Pick a random child.
+ * We've been asked to stop. Do so.
*/
+ if ((request->master_state == REQUEST_STOP_PROCESSING) ||
+ (request->parent &&
+ (request->parent->master_state == REQUEST_STOP_PROCESSING))) {
+ entry->result = RLM_MODULE_FAIL;
+ entry->priority = 9999;
+ return TRUE;
+ }
- /* Loop over the children */
- for(p = g->children; p; p = p->next) {
- if (!child) {
- child = p;
- count = 1;
- continue;
+ /*
+ * Handle "if" conditions.
+ */
+ if (c->type == MOD_IF) {
+ int condition;
+ modgroup *g;
+ const char *p;
+
+ mod_if:
+ g = mod_callabletogroup(c);
+ p = c->name;
+
+ RDEBUG2("%.*s? %s %s", depth + 1, modcall_spaces,
+ group_name[c->type], c->name);
+
+ if (radius_evaluate_condition(request, result,
+ 0, &p, TRUE, &condition)) {
+ RDEBUG2("%.*s? %s %s -> %s", depth + 1, modcall_spaces,
+ group_name[c->type],
+ c->name, condition ? "TRUE" : "FALSE");
+ } else {
+ condition = FALSE;
}
/*
- * Keep track of how many load balancing servers
- * we've gone through.
+ * Didn't pass. Remember that.
*/
- count++;
+ if (!condition) {
+ was_if = TRUE;
+ if_taken = FALSE;
+ goto next_sibling;
+ }
/*
- * See the "camel book" for why this works.
- *
- * If (rand(0..n) < 1), pick the current realm.
- * We add a scale factor of 65536, to avoid
- * floating point.
+ * We took the "if". Go recurse into its' children.
*/
- if ((count * (lrad_rand() & 0xffff)) < (uint32_t) 0x10000) {
- child = p;
- }
- }
- rad_assert(child != NULL);
-
- /* Call the chosen child by recursing into modcall */
- return modcall(component, child, request);
-}
-
-
-/*
- * For more than 2 modules with redundancy + load balancing
- * across all of them, layering the "redundant" and
- * "load-balance" groups gets too complicated. As a result, we
- * implement a special function to do this.
- */
-static int call_modredundantloadbalance(int component, modgroup *g, REQUEST *request,
- int default_result)
-{
- int count = 1;
- int myresult = default_result;
- int priority = 0; /* default result has lowest priority */
- modcallable *p, *child = NULL;
+ was_if = TRUE;
+ if_taken = TRUE;
+ goto do_children;
+ } /* MOD_IF */
/*
- * Catch people who have issues.
+ * "else" if the previous "if" was taken.
+ * "if" if the previous if wasn't taken.
*/
- if (!g->children) {
- DEBUG2(" WARNING! Asked to process empty redundant-load-balance group. Returning %s.", lrad_int2str(rcode_table, default_result, "??"));
- return default_result;
- }
+ if (c->type == MOD_ELSIF) {
+ if (!was_if) goto elsif_error;
+
+ /*
+ * Like MOD_ELSE, but allow for a later "else"
+ */
+ if (if_taken) {
+ RDEBUG2("%.*s ... skipping %s for request %d: Preceding \"if\" was taken",
+ depth + 1, modcall_spaces,
+ group_name[c->type], request->number);
+ was_if = TRUE;
+ if_taken = TRUE;
+ goto next_sibling;
+ }
+
+ /*
+ * Check the "if" condition.
+ */
+ goto mod_if;
+ } /* MOD_ELSIF */
/*
- * Pick a random child.
+ * "else" for a preceding "if".
*/
+ if (c->type == MOD_ELSE) {
+ if (!was_if) { /* error */
+ elsif_error:
+ RDEBUG2("%.*s ... skipping %s for request %d: No preceding \"if\"",
+ depth + 1, modcall_spaces,
+ group_name[c->type], request->number);
+ goto next_sibling;
+ }
- /* Loop over the children */
- for(p = g->children; p; p = p->next) {
- if (!child) {
- child = p;
- count = 1;
- continue;
+ if (if_taken) {
+ RDEBUG2("%.*s ... skipping %s for request %d: Preceding \"if\" was taken",
+ depth + 1, modcall_spaces,
+ group_name[c->type], request->number);
+ was_if = FALSE;
+ if_taken = FALSE;
+ goto next_sibling;
}
/*
- * Keep track of how many load balancing servers
- * we've gone through.
+ * We need to process it. Go do that.
*/
- count++;
+ was_if = FALSE;
+ if_taken = FALSE;
+ goto do_children;
+ } /* MOD_ELSE */
+
+ /*
+ * We're no longer processing if/else/elsif. Reset the
+ * trackers for those conditions.
+ */
+ was_if = FALSE;
+ if_taken = FALSE;
+
+ if (c->type == MOD_SINGLE) {
+ modsingle *sp;
/*
- * See the "camel book" for why this works.
- *
- * If (rand(0..n) < 1), pick the current realm.
- * We add a scale factor of 65536, to avoid
- * floating point.
+ * Process a stand-alone child, and fall through
+ * to dealing with it's parent.
*/
- if ((count * (lrad_rand() & 0xffff)) < (uint32_t) 0x10000) {
- child = p;
+ sp = mod_callabletosingle(c);
+
+ result = call_modsingle(c->method, sp, request);
+ RDEBUG2("%.*s[%s] = %s", depth + 1, modcall_spaces, c->name ? c->name : "",
+ fr_int2str(mod_rcode_table, result, "<invalid>"));
+ goto calculate_result;
+ } /* MOD_SINGLE */
+
+ /*
+ * Update attribute(s)
+ */
+ if (c->type == MOD_UPDATE) {
+ int rcode;
+ modgroup *g = mod_callabletogroup(c);
+
+ MOD_LOG_OPEN_BRACE("update");
+ rcode = radius_update_attrlist(request, g->cs,
+ g->vps, c->name);
+ if (rcode != RLM_MODULE_UPDATED) {
+ result = rcode;
+ } else {
+ result = RLM_MODULE_NOOP;
}
- }
- rad_assert(child != NULL);
+ MOD_LOG_CLOSE_BRACE();
+ goto calculate_result;
+ } /* MOD_IF */
/*
- * Call the chosen child, with fail-over to the next one
- * if it is down.
+ * Child is a group that has children of it's own.
*/
- p = child;
- do {
+ if ((c->type == MOD_GROUP) || (c->type == MOD_POLICY) ||
+ (c->type == MOD_CASE)) {
+ modgroup *g;
+
+ do_children:
+ g = mod_callabletogroup(c);
+
/*
- * Call the chosen entry. If we're done, then
- * stop.
+ * This should really have been caught in the
+ * compiler, and the node never generated. But
+ * doing that requires changing it's API so that
+ * it returns a flag instead of the compiled
+ * MOD_GROUP.
*/
- if (!call_one(component, p, request, &priority, &myresult)) {
- break;
+ if (!g->children) {
+ RDEBUG2("%.*s%s %s { ... } # empty sub-section is ignored",
+ depth + 1, modcall_spaces, group_name[c->type], c->name);
+ goto next_sibling;
}
-
- /*
- * Go to the next one, and wrap around to the beginning if
- * we reach the end.
- */
- p = p->next;
- if (!p) p = g->children;
- } while (p != child);
- /*
- * And return whatever was decided.
- */
- return myresult;
-}
+ MOD_LOG_OPEN_BRACE(group_name[c->type]);
+ modcall_child(request, component,
+ depth + 1, entry, g->children,
+ &result, TRUE);
+ MOD_LOG_CLOSE_BRACE();
+ goto calculate_result;
+ } /* MOD_GROUP */
-int modcall(int component, modcallable *c, REQUEST *request)
-{
- int myresult;
+ if (c->type == MOD_SWITCH) {
+ modcallable *this, *found, *null_case;
+ modgroup *g;
+ char buffer[1024];
- /* 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;
- case RLM_COMPONENT_PRE_PROXY:
- myresult = RLM_MODULE_NOOP;
- break;
- case RLM_COMPONENT_POST_PROXY:
- myresult = RLM_MODULE_NOOP;
- break;
- case RLM_COMPONENT_POST_AUTH:
- myresult = RLM_MODULE_NOOP;
- break;
- default:
- myresult = RLM_MODULE_FAIL;
- break;
- }
+ MOD_LOG_OPEN_BRACE("switch");
- if(c == NULL) {
- DEBUG2("modcall[%s]: NULL object returns %s for request %d",
- comp2str[component],
- lrad_int2str(rcode_table, myresult, "??"),
- request->number);
- return myresult;
- }
+ /*
+ * If there's no %, it refers to an attribute.
+ * Otherwise, expand it.
+ */
+ if (!strchr(c->name, '%')) {
+ VALUE_PAIR *vp = NULL;
+
+ if (radius_get_vp(request, c->name, &vp) && vp) {
+ vp_prints_value(buffer,
+ sizeof(buffer),
+ vp, 0);
+ } else {
+ *buffer = '\0';
+ }
+ } else {
+ radius_xlat(buffer, sizeof(buffer),
+ c->name, request, NULL);
+ }
- switch (c->type) {
- case MOD_LOAD_BALANCE:
- {
- modgroup *g = mod_callabletogroup(c);
-
- DEBUG2("modcall: entering load-balance group %s for request %d",
- c->name, request->number);
-
- myresult = call_modloadbalance(component, g, request,
- myresult);
-
- DEBUG2("modcall: load-balance group %s returns %s for request %d",
- c->name,
- lrad_int2str(rcode_table, myresult, "??"),
- request->number);
+ /*
+ * Find either the exact matching name, or the
+ * "case {...}" statement.
+ */
+ g = mod_callabletogroup(c);
+ null_case = found = NULL;
+ for (this = g->children; this; this = this->next) {
+ if (!this->name) {
+ if (!null_case) null_case = this;
+ continue;
+ }
+ if (strcmp(buffer, this->name) == 0) {
+ found = this;
+ break;
+ }
}
- break;
- case MOD_REDUNDANT_LOAD_BALANCE:
- {
- modgroup *g = mod_callabletogroup(c);
-
- DEBUG2("modcall: entering redundant-load-balance group %s for request %d",
- c->name, request->number);
-
- myresult = call_modredundantloadbalance(component, g, request,
- myresult);
-
- DEBUG2("modcall: redundant-load-balance group %s returns %s for request %d",
- c->name,
- lrad_int2str(rcode_table, myresult, "??"),
- request->number);
- }
- break;
+ if (!found) found = null_case;
- case MOD_GROUP:
- {
- modgroup *g = mod_callabletogroup(c);
-
- DEBUG2("modcall: entering group %s%s for request %d",
- lrad_int2str(grouptype_table, g->grouptype, ""),
- c->name, request->number);
-
- myresult = call_modgroup(component, g, request,
- myresult);
-
- DEBUG2("modcall: leaving group %s%s (returns %s) for request %d",
- lrad_int2str(grouptype_table, g->grouptype, ""),
- c->name,
- lrad_int2str(rcode_table, myresult, "??"),
- request->number);
+ MOD_LOG_OPEN_BRACE(group_name[c->type]);
+ modcall_child(request, component,
+ depth + 1, entry, found,
+ &result, TRUE);
+ MOD_LOG_CLOSE_BRACE();
+ goto calculate_result;
+ } /* MOD_SWITCH */
+
+ if ((c->type == MOD_LOAD_BALANCE) ||
+ (c->type == MOD_REDUNDANT_LOAD_BALANCE)) {
+ int count = 0;
+ modcallable *this, *found;
+ modgroup *g;
+
+ MOD_LOG_OPEN_BRACE("load-balance");
+
+ g = mod_callabletogroup(c);
+ found = NULL;
+ for (this = g->children; this; this = this->next) {
+ if (!found) {
+ found = this;
+ count = 1;
+ continue;
+ }
+ count++;
+
+ if ((count * (fr_rand() & 0xffff)) < (uint32_t) 0x10000) {
+ found = this;
+ }
}
- break;
+
+ MOD_LOG_OPEN_BRACE(group_name[c->type]);
- case MOD_SINGLE:
- {
- modsingle *sp = mod_callabletosingle(c);
-
- myresult = call_modsingle(component, sp, request,
- myresult);
-
- DEBUG2(" modcall[%s]: module \"%s\" returns %s for request %d",
- comp2str[component], c->name,
- lrad_int2str(rcode_table, myresult, "??"),
- request->number);
+ if (c->type == MOD_LOAD_BALANCE) {
+ modcall_child(request, component,
+ depth + 1, entry, found,
+ &result, FALSE);
+
+ } else {
+ this = found;
+
+ do {
+ modcall_child(request, component,
+ depth + 1, entry, found,
+ &result, FALSE);
+ if (found->actions[result] == MOD_ACTION_RETURN) {
+ priority = -1;
+ break;
+ }
+
+ this = this->next;
+ if (!this) this = g->children;
+ } while (this != found);
}
- break;
+ MOD_LOG_CLOSE_BRACE();
+ goto calculate_result;
+ } /* MOD_LOAD_BALANCE */
- default:
- radlog(L_ERR, "Internal error processing module entry");
- break;
- }
+ /*
+ * Reference another virtual server.
+ *
+ * This should really be deleted, and replaced with a
+ * more abstracted / functional version.
+ */
+ if (c->type == MOD_REFERENCE) {
+ modref *mr = mod_callabletoref(c);
+ char const *server = request->server;
- return myresult;
-}
+ if (server == mr->ref_name) {
+ radlog(L_INFO, "WARNING: Suppressing recursive call to server %s", server);
+ goto next_sibling;
+ }
-#if 0
-/* 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;
+ request->server = mr->ref_name;
+ RDEBUG("server %s { # nested call", mr->ref_name);
+ result = indexed_modcall(component, 0, request);
+ RDEBUG("} # server %s with nested call", mr->ref_name);
+ request->server = server;
+ goto calculate_result;
+ } /* MOD_REFERENCE */
- 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);
- }
+ /*
+ * xlat a string without doing anything else
+ *
+ * This should really be deleted, and replaced with a
+ * more abstracted / functional version.
+ */
+ if (c->type == MOD_XLAT) {
+ modxlat *mx = mod_callabletoxlat(c);
+ char buffer[128];
- 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",
- lrad_int2str(rcode_table, i, "??"),
- action2str(c->actions[i]));
- }
+ if (!mx->exec) {
+ radius_xlat(buffer, sizeof(buffer),
+ mx->xlat_name, request, NULL);
+ } else {
+ RDEBUG("`%s`", mx->xlat_name);
+ radius_exec_program(mx->xlat_name, request,
+ 0, NULL, 0,
+ EXEC_TIMEOUT,
+ request->packet->vps,
+ NULL, 1);
+ }
- DEBUG("%.*s}", indent, "\t\t\t\t\t\t\t\t\t\t\t");
-}
+ goto next_sibling;
+ } /* MOD_XLAT */
+
+ /*
+ * Add new module types here.
+ */
-static void dump_tree(int comp, modcallable *c)
-{
- DEBUG("[%s]", comp2str[comp]);
- dump_mc(c, 0);
+calculate_result:
+#if 0
+ RDEBUG("(%s, %d) ? (%s, %d)",
+ fr_int2str(mod_rcode_table, result, "<invalid>"),
+ priority,
+ fr_int2str(mod_rcode_table, entry->result, "<invalid>"),
+ entry->priority);
+#endif
+
+ /*
+ * The child's action says return. Do so.
+ */
+ if ((c->actions[result] == MOD_ACTION_RETURN) &&
+ (priority <= 0)) {
+ entry->result = result;
+ return TRUE;
+ }
+
+ /*
+ * If "reject", break out of the loop and return
+ * reject.
+ */
+ if (c->actions[result] == MOD_ACTION_REJECT) {
+ entry->result = RLM_MODULE_REJECT;
+ return TRUE;
+ }
+
+ /*
+ * The array holds a default priority for this return
+ * code. Grab it in preference to any unset priority.
+ */
+ if (priority < 0) {
+ priority = c->actions[result];
+ }
+
+ /*
+ * We're higher than any previous priority, remember this
+ * return code and priority.
+ */
+ if (priority > entry->priority) {
+ entry->result = result;
+ entry->priority = priority;
+ }
+
+ /*
+ * If we're processing a "case" statement, we return once
+ * it's done, rather than going to the next "case" statement.
+ */
+ if (c->type == MOD_CASE) return TRUE;
+
+next_sibling:
+ if (do_next_sibling) {
+ entry->c = entry->c->next;
+
+ if (entry->c) goto redo;
+ }
+
+ /*
+ * And we're done!
+ */
+ return TRUE;
}
-#else
-static void dump_tree(int comp UNUSED, modcallable *c UNUSED)
+
+
+/**
+ * @brief Call a module, iteratively, with a local stack, rather than
+ * recursively. What did Paul Graham say about Lisp...?
+ */
+int modcall(int component, modcallable *c, REQUEST *request)
{
- return;
+ modcall_stack_entry_t stack[MODCALL_STACK_MAX];
+
+ if ((component < 0) || (component >= RLM_COMPONENT_COUNT)) {
+ return RLM_MODULE_FAIL;
+ }
+
+ /*
+ * Set up the initial stack frame.
+ */
+ stack[0].c = c;
+ stack[0].result = default_component_results[component];
+ stack[0].priority = 0;
+
+ /*
+ * Call the main handler.
+ */
+ if (!modcall_recurse(request, component, 0, &stack[0], TRUE)) {
+ return RLM_MODULE_FAIL;
+ }
+
+ /*
+ * Return the result.
+ */
+ return stack[0].result;
+}
+
+#if 0
+static const char *action2str(int action)
+{
+ static char buf[32];
+ if(action==MOD_ACTION_RETURN)
+ return "return";
+ if(action==MOD_ACTION_REJECT)
+ return "reject";
+ snprintf(buf, sizeof buf, "%d", action);
+ return buf;
+}
+
+/* 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 if ((c->type > MOD_SINGLE) && (c->type <= MOD_POLICY)) {
+ modgroup *g = mod_callabletogroup(c);
+ modcallable *p;
+ DEBUG("%.*s%s {", indent, "\t\t\t\t\t\t\t\t\t\t\t",
+ group_name[c->type]);
+ for(p = g->children;p;p = p->next)
+ dump_mc(p, indent+1);
+ } /* else ignore it for now */
+
+ 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",
+ fr_int2str(rcode_table, 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)
+{
+ RDEBUG("[%s]", comp2str[comp]);
+ dump_mc(c, 0);
+}
+#else
+#define dump_tree(a, b)
#endif
/* These are the default actions. For each component, the group{} block
MOD_ACTION_RETURN /* updated */
}
}
+#ifdef WITH_COA
+ ,
+ /* recv-coa */
+ {
+ /* 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 */
+ }
+ },
+ /* send-coa */
+ {
+ /* 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 */
+ }
+ }
+#endif
};
-static modcallable *do_compile_modsingle(int component, CONF_ITEM *ci,
- const char *filename, int grouptype,
+#ifdef WITH_UNLANG
+static modcallable *do_compile_modupdate(modcallable *parent,
+ int component, CONF_SECTION *cs,
+ const char *name2)
+{
+ int i, ok = FALSE;
+ const char *vp_name;
+ modgroup *g;
+ modcallable *csingle;
+ CONF_ITEM *ci;
+ VALUE_PAIR *head, **tail;
+
+ static const char *attrlist_names[] = {
+ "request", "reply", "proxy-request", "proxy-reply",
+ "config", "control",
+ "coa", "coa-reply", "disconnect", "disconnect-reply",
+ NULL
+ };
+
+ component = component; /* -Wunused */
+
+ if (!cf_section_name2(cs)) {
+ cf_log_err(cf_sectiontoitem(cs),
+ "Require list name for 'update'.\n");
+ return NULL;
+ }
+
+ vp_name = name2;
+ if (strncmp(vp_name, "outer.", 6) == 0) {
+ vp_name += 6;
+ }
+
+ for (i = 0; attrlist_names[i] != NULL; i++) {
+ if (strcmp(vp_name, attrlist_names[i]) == 0) {
+ ok = TRUE;
+ break;
+ }
+ }
+
+ if (!ok) {
+ cf_log_err(cf_sectiontoitem(cs),
+ "Unknown attribute list \"%s\"",
+ name2);
+ return NULL;
+ }
+
+ head = NULL;
+ tail = &head;
+
+ /*
+ * Walk through the children of the update section,
+ * ensuring that they're all known attributes.
+ */
+ for (ci=cf_item_find_next(cs, NULL);
+ ci != NULL;
+ ci=cf_item_find_next(cs, ci)) {
+ CONF_PAIR *cp;
+ VALUE_PAIR *vp;
+
+ if (cf_item_is_section(ci)) {
+ cf_log_err(ci, "\"update\" sections cannot have subsections");
+ return NULL;
+ }
+
+ if (!cf_item_is_pair(ci)) continue;
+
+ cp = cf_itemtopair(ci); /* can't return NULL */
+ vp = cf_pairtovp(cp);
+ if (!vp) {
+ pairfree(&head);
+ cf_log_err(ci, "ERROR: %s", fr_strerror());
+ return NULL;
+ }
+
+ if ((vp->operator != T_OP_EQ) &&
+ (vp->operator != T_OP_CMP_EQ) &&
+ (vp->operator != T_OP_ADD) &&
+ (vp->operator != T_OP_SUB) &&
+ (vp->operator != T_OP_LE) &&
+ (vp->operator != T_OP_GE) &&
+ (vp->operator != T_OP_CMP_FALSE) &&
+ (vp->operator != T_OP_SET)) {
+ pairfree(&head);
+ pairfree(&vp);
+ cf_log_err(ci, "Invalid operator for attribute");
+ return NULL;
+ }
+
+ /*
+ * A few more sanity checks. The enforcement of
+ * <= or >= can only happen for integer
+ * attributes.
+ */
+ if ((vp->operator == T_OP_LE) ||
+ (vp->operator == T_OP_GE)) {
+ if ((vp->type != PW_TYPE_BYTE) &&
+ (vp->type != PW_TYPE_SHORT) &&
+ (vp->type != PW_TYPE_INTEGER)) {
+ pairfree(&head);
+ pairfree(&vp);
+ cf_log_err(ci, "Enforcment of <= or >= is possible only for integer attributes");
+ return NULL;
+ }
+ }
+
+ *tail = vp;
+ tail = &(vp->next);
+ }
+
+ if (!head) {
+ cf_log_err(cf_sectiontoitem(cs),
+ "ERROR: update %s section cannot be empty",
+ name2);
+ return NULL;
+ }
+
+ g = rad_malloc(sizeof(*g)); /* never fails */
+ memset(g, 0, sizeof(*g));
+ csingle = mod_grouptocallable(g);
+
+ csingle->parent = parent;
+ csingle->next = NULL;
+ csingle->name = name2;
+ csingle->type = MOD_UPDATE;
+ csingle->method = component;
+
+ memcpy(csingle->actions, defaultactions[component][GROUPTYPE_SIMPLE],
+ sizeof(csingle->actions));
+
+ g->grouptype = GROUPTYPE_SIMPLE;
+ g->children = NULL;
+ g->cs = cs;
+ g->vps = head;
+
+ return csingle;
+}
+
+
+static modcallable *do_compile_modswitch(modcallable *parent,
+ int component, CONF_SECTION *cs)
+{
+ modcallable *csingle;
+ CONF_ITEM *ci;
+ int had_seen_default = FALSE;
+
+ component = component; /* -Wunused */
+
+ if (!cf_section_name2(cs)) {
+ cf_log_err(cf_sectiontoitem(cs),
+ "You must specify a variable to switch over for 'switch'.");
+ return NULL;
+ }
+
+ if (!cf_item_find_next(cs, NULL)) {
+ cf_log_err(cf_sectiontoitem(cs), "'switch' statments cannot be empty.");
+ return NULL;
+ }
+
+ /*
+ * Walk through the children of the switch section,
+ * ensuring that they're all 'case' statements
+ */
+ for (ci=cf_item_find_next(cs, NULL);
+ ci != NULL;
+ ci=cf_item_find_next(cs, ci)) {
+ CONF_SECTION *subcs;
+ const char *name1, *name2;
+
+ if (!cf_item_is_section(ci)) {
+ if (!cf_item_is_pair(ci)) continue;
+
+ cf_log_err(ci, "\"switch\" sections can only have \"case\" subsections");
+ return NULL;
+ }
+
+ subcs = cf_itemtosection(ci); /* can't return NULL */
+ name1 = cf_section_name1(subcs);
+
+ if (strcmp(name1, "case") != 0) {
+ cf_log_err(ci, "\"switch\" sections can only have \"case\" subsections");
+ return NULL;
+ }
+
+ name2 = cf_section_name2(subcs);
+ if (!name2 && !had_seen_default) {
+ had_seen_default = TRUE;
+ continue;
+ }
+
+ if (!name2 || (name2[0] == '\0')) {
+ cf_log_err(ci, "\"case\" sections must have a name");
+ return NULL;
+ }
+ }
+
+ csingle= do_compile_modgroup(parent, component, cs,
+ GROUPTYPE_SIMPLE, GROUPTYPE_SIMPLE);
+ if (!csingle) return NULL;
+ csingle->type = MOD_SWITCH;
+ return csingle;
+}
+#endif
+
+static modcallable *do_compile_modserver(modcallable *parent,
+ int component, CONF_ITEM *ci,
+ const char *name,
+ CONF_SECTION *cs,
+ const char *server)
+{
+ modcallable *csingle;
+ CONF_SECTION *subcs;
+ modref *mr;
+
+ subcs = cf_section_sub_find_name2(cs, comp2str[component], NULL);
+ if (!subcs) {
+ cf_log_err(ci, "Server %s has no %s section",
+ server, comp2str[component]);
+ return NULL;
+ }
+
+ mr = rad_malloc(sizeof(*mr));
+ memset(mr, 0, sizeof(*mr));
+
+ csingle = mod_reftocallable(mr);
+ csingle->parent = parent;
+ csingle->next = NULL;
+ csingle->name = name;
+ csingle->type = MOD_REFERENCE;
+ csingle->method = component;
+
+ memcpy(csingle->actions, defaultactions[component][GROUPTYPE_SIMPLE],
+ sizeof(csingle->actions));
+
+ mr->ref_name = strdup(server);
+ mr->ref_cs = cs;
+
+ return csingle;
+}
+
+static modcallable *do_compile_modxlat(modcallable *parent,
+ int component, const char *fmt)
+{
+ modcallable *csingle;
+ modxlat *mx;
+
+ mx = rad_malloc(sizeof(*mx));
+ memset(mx, 0, sizeof(*mx));
+
+ csingle = mod_xlattocallable(mx);
+ csingle->parent = parent;
+ csingle->next = NULL;
+ csingle->name = "expand";
+ csingle->type = MOD_XLAT;
+ csingle->method = component;
+
+ memcpy(csingle->actions, defaultactions[component][GROUPTYPE_SIMPLE],
+ sizeof(csingle->actions));
+
+ mx->xlat_name = strdup(fmt);
+ if (fmt[0] != '%') {
+ char *p;
+ mx->exec = TRUE;
+
+ strcpy(mx->xlat_name, fmt + 1);
+ p = strrchr(mx->xlat_name, '`');
+ if (p) *p = '\0';
+ }
+
+ return csingle;
+}
+
+/*
+ * redundant, etc. can refer to modules or groups, but not much else.
+ */
+static int all_children_are_modules(CONF_SECTION *cs, const char *name)
+{
+ CONF_ITEM *ci;
+
+ for (ci=cf_item_find_next(cs, NULL);
+ ci != NULL;
+ ci=cf_item_find_next(cs, ci)) {
+ /*
+ * If we're a redundant, etc. group, then the
+ * intention is to call modules, rather than
+ * processing logic. These checks aren't
+ * *strictly* necessary, but they keep the users
+ * from doing crazy things.
+ */
+ if (cf_item_is_section(ci)) {
+ CONF_SECTION *subcs = cf_itemtosection(ci);
+ const char *name1 = cf_section_name1(subcs);
+
+ if ((strcmp(name1, "if") == 0) ||
+ (strcmp(name1, "else") == 0) ||
+ (strcmp(name1, "elsif") == 0) ||
+ (strcmp(name1, "update") == 0) ||
+ (strcmp(name1, "switch") == 0) ||
+ (strcmp(name1, "case") == 0)) {
+ cf_log_err(ci, "%s sections cannot contain a \"%s\" statement",
+ name, name1);
+ return 0;
+ }
+ continue;
+ }
+
+ if (cf_item_is_pair(ci)) {
+ CONF_PAIR *cp = cf_itemtopair(ci);
+ if (cf_pair_value(cp) != NULL) {
+ cf_log_err(ci,
+ "Entry with no value is invalid");
+ return 0;
+ }
+ }
+ }
+
+ return 1;
+}
+
+
+/*
+ * Compile one entry of a module call.
+ */
+static modcallable *do_compile_modsingle(modcallable *parent,
+ int component, CONF_ITEM *ci,
+ int grouptype,
const char **modname)
{
- int lineno;
+#ifdef WITH_UNLANG
+ int result;
+#endif
const char *modrefname;
modsingle *single;
modcallable *csingle;
module_instance_t *this;
+ CONF_SECTION *cs, *subcs, *modules;
if (cf_item_is_section(ci)) {
- CONF_SECTION *cs = cf_itemtosection(ci);
- const char *name2 = cf_section_name2(cs);
+ const char *name2;
- lineno = cf_section_lineno(cs);
+ cs = cf_itemtosection(ci);
modrefname = cf_section_name1(cs);
+ name2 = cf_section_name2(cs);
if (!name2) name2 = "_UnNamedGroup";
/*
*/
if (strcmp(modrefname, "group") == 0) {
*modname = name2;
- return do_compile_modgroup(component, cs, filename,
- GROUPTYPE_SIMPLE, grouptype);
+ return do_compile_modgroup(parent, component, cs,
+ GROUPTYPE_SIMPLE,
+ grouptype);
+
} else if (strcmp(modrefname, "redundant") == 0) {
*modname = name2;
- return do_compile_modgroup(component, cs, filename,
- GROUPTYPE_REDUNDANT, grouptype);
+
+ if (!all_children_are_modules(cs, modrefname)) {
+ return NULL;
+ }
+
+ return do_compile_modgroup(parent, component, cs,
+ GROUPTYPE_REDUNDANT,
+ grouptype);
+
} else if (strcmp(modrefname, "append") == 0) {
*modname = name2;
- return do_compile_modgroup(component, cs, filename,
- GROUPTYPE_APPEND, grouptype);
+ return do_compile_modgroup(parent, component, cs,
+ GROUPTYPE_APPEND,
+ grouptype);
+
} else if (strcmp(modrefname, "load-balance") == 0) {
*modname = name2;
- csingle= do_compile_modgroup(component, cs, filename,
- GROUPTYPE_SIMPLE, grouptype);
+
+ if (!all_children_are_modules(cs, modrefname)) {
+ return NULL;
+ }
+
+ csingle= do_compile_modgroup(parent, component, cs,
+ GROUPTYPE_SIMPLE,
+ grouptype);
if (!csingle) return NULL;
csingle->type = MOD_LOAD_BALANCE;
return csingle;
+
} else if (strcmp(modrefname, "redundant-load-balance") == 0) {
*modname = name2;
- csingle= do_compile_modgroup(component, cs, filename,
- GROUPTYPE_REDUNDANT, grouptype);
+
+ if (!all_children_are_modules(cs, modrefname)) {
+ return NULL;
+ }
+
+ csingle= do_compile_modgroup(parent, component, cs,
+ GROUPTYPE_REDUNDANT,
+ grouptype);
if (!csingle) return NULL;
csingle->type = MOD_REDUNDANT_LOAD_BALANCE;
return csingle;
- }
+
+#ifdef WITH_UNLANG
+ } else if (strcmp(modrefname, "if") == 0) {
+ if (!cf_section_name2(cs)) {
+ cf_log_err(ci, "'if' without condition.");
+ return NULL;
+ }
+
+ *modname = name2;
+ csingle= do_compile_modgroup(parent, component, cs,
+ GROUPTYPE_SIMPLE,
+ grouptype);
+ if (!csingle) return NULL;
+ csingle->type = MOD_IF;
+
+ if (!radius_evaluate_condition(NULL, 0, 0, modname,
+ FALSE, &result)) {
+ modcallable_free(&csingle);
+ return NULL;
+ }
+ *modname = name2;
+
+ return csingle;
+
+ } else if (strcmp(modrefname, "elsif") == 0) {
+ if (parent &&
+ ((parent->type == MOD_LOAD_BALANCE) ||
+ (parent->type == MOD_REDUNDANT_LOAD_BALANCE))) {
+ cf_log_err(ci, "'elsif' cannot be used in this section.");
+ return NULL;
+ }
+
+ if (!cf_section_name2(cs)) {
+ cf_log_err(ci, "'elsif' without condition.");
+ return NULL;
+ }
+
+ *modname = name2;
+ csingle= do_compile_modgroup(parent, component, cs,
+ GROUPTYPE_SIMPLE,
+ grouptype);
+ if (!csingle) return NULL;
+ csingle->type = MOD_ELSIF;
+
+ if (!radius_evaluate_condition(NULL, 0, 0, modname,
+ FALSE, &result)) {
+ modcallable_free(&csingle);
+ return NULL;
+ }
+ *modname = name2;
+
+ return csingle;
+
+ } else if (strcmp(modrefname, "else") == 0) {
+ if (parent &&
+ ((parent->type == MOD_LOAD_BALANCE) ||
+ (parent->type == MOD_REDUNDANT_LOAD_BALANCE))) {
+ cf_log_err(ci, "'else' cannot be used in this section section.");
+ return NULL;
+ }
+
+ if (cf_section_name2(cs)) {
+ cf_log_err(ci, "Cannot have conditions on 'else'.");
+ return NULL;
+ }
+
+ *modname = name2;
+ csingle= do_compile_modgroup(parent, component, cs,
+ GROUPTYPE_SIMPLE,
+ grouptype);
+ if (!csingle) return NULL;
+ csingle->type = MOD_ELSE;
+ return csingle;
+
+ } else if (strcmp(modrefname, "update") == 0) {
+ *modname = name2;
+
+ csingle = do_compile_modupdate(parent, component, cs,
+ name2);
+ if (!csingle) return NULL;
+
+ return csingle;
+
+ } else if (strcmp(modrefname, "switch") == 0) {
+ *modname = name2;
+
+ csingle = do_compile_modswitch(parent, component, cs);
+ if (!csingle) return NULL;
+
+ return csingle;
+
+ } else if (strcmp(modrefname, "case") == 0) {
+ int i;
+
+ *modname = name2;
+
+ /*
+ * FIXME: How to tell that the parent can only
+ * be a "switch" statement?
+ */
+ if (!parent) {
+ cf_log_err(ci, "\"case\" statements may only appear within a \"switch\" section");
+ return NULL;
+ }
+
+ csingle= do_compile_modgroup(parent, component, cs,
+ GROUPTYPE_SIMPLE,
+ grouptype);
+ if (!csingle) return NULL;
+ csingle->type = MOD_CASE;
+ csingle->name = cf_section_name2(cs); /* may be NULL */
+
+ /*
+ * Set all of it's codes to return, so that
+ * when we pick a 'case' statement, we don't
+ * fall through to processing the next one.
+ */
+ for (i = 0; i < RLM_MODULE_NUMCODES; i++) {
+ csingle->actions[i] = MOD_ACTION_RETURN;
+ }
+
+ return csingle;
+#endif
+ } /* else it's something like sql { fail = 1 ...} */
+
+ } else if (!cf_item_is_pair(ci)) { /* CONF_DATA or some such */
+ return NULL;
+
+ /*
+ * Else it's a module reference, with updated return
+ * codes.
+ */
} else {
+ CONF_SECTION *loop;
CONF_PAIR *cp = cf_itemtopair(ci);
- lineno = cf_pair_lineno(cp);
modrefname = cf_pair_attr(cp);
+
+ /*
+ * Actions (ok = 1), etc. are orthoganal to just
+ * about everything else.
+ */
+ if (cf_pair_value(cp) != NULL) {
+ cf_log_err(ci, "Entry is not a reference to a module");
+ return NULL;
+ }
+
+ if (((modrefname[0] == '%') && (modrefname[1] == '{')) ||
+ (modrefname[0] == '`')) {
+ return do_compile_modxlat(parent, component,
+ modrefname);
+ }
+
+ /*
+ * See if the module is a virtual one. If so,
+ * return that, rather than doing anything here.
+ */
+ subcs = NULL;
+ cs = cf_section_find("instantiate");
+ if (cs) subcs = cf_section_sub_find_name2(cs, NULL,
+ modrefname);
+ if (!subcs &&
+ (cs = cf_section_find("policy")) != NULL) {
+ char buffer[256];
+
+ snprintf(buffer, sizeof(buffer), "%s.%s",
+ modrefname, comp2str[component]);
+
+ /*
+ * Prefer name.section, then name.
+ */
+ subcs = cf_section_sub_find_name2(cs, NULL,
+ buffer);
+ if (!subcs) {
+ subcs = cf_section_sub_find_name2(cs, NULL,
+ modrefname);
+ }
+ }
+
+ /*
+ * Allow policies to over-ride module names.
+ * i.e. the "sql" policy can do some extra things,
+ * and then call the "sql" module.
+ */
+ for (loop = cf_item_parent(ci);
+ loop && subcs;
+ loop = cf_item_parent(cf_sectiontoitem(loop))) {
+ if (loop == subcs) {
+ subcs = NULL;
+ }
+ }
+
+ if (subcs) {
+ DEBUG2(" Module: Loading virtual module %s",
+ modrefname);
+
+ /*
+ * redundant foo {} is a single.
+ */
+ if (cf_section_name2(subcs)) {
+ return do_compile_modsingle(parent,
+ component,
+ cf_sectiontoitem(subcs),
+ grouptype,
+ modname);
+ } else {
+ /*
+ * foo {} is a group.
+ */
+ return do_compile_modgroup(parent,
+ component,
+ subcs,
+ GROUPTYPE_SIMPLE,
+ grouptype);
+ }
+ }
}
/*
- * FIXME: See if the module is a virtual one. If so,
- * return that, rather than doing anything here.
+ * Not a virtual module. It must be a real module.
*/
- this = find_module_instance(modrefname);
- if (!this) {
+ modules = cf_section_find("modules");
+ this = NULL;
+
+ if (modules && cf_section_sub_find_name2(modules, NULL, modrefname)) {
+ this = find_module_instance(modules, modrefname, 1);
+ }
+
+ if (!this) do {
+ int i;
+ char *p;
+
+ /*
+ * Maybe it's module.method
+ */
+ p = strrchr(modrefname, '.');
+ if (p) for (i = RLM_COMPONENT_AUTH;
+ i < RLM_COMPONENT_COUNT;
+ i++) {
+ if (strcmp(p + 1, comp2str[i]) == 0) {
+ char buffer[256];
+
+ strlcpy(buffer, modrefname, sizeof(buffer));
+ buffer[p - modrefname] = '\0';
+ component = i;
+
+ this = find_module_instance(cf_section_find("modules"), buffer, 1);
+ if (this &&
+ !this->entry->module->methods[i]) {
+ *modname = NULL;
+ cf_log_err(ci, "Module %s has no such method %s", buffer, comp2str[i]);
+ return NULL;
+ }
+ break;
+ }
+ }
+ if (this) break;
+
+ if (strncmp(modrefname, "server[", 7) == 0) {
+ char buffer[256];
+
+ strlcpy(buffer, modrefname + 7, sizeof(buffer));
+ p = strrchr(buffer, ']');
+ if (!p || p[1] != '\0' || (p == buffer)) {
+ cf_log_err(ci, "Invalid server reference in \"%s\".", modrefname);
+ return NULL;
+ }
+ *p = '\0';
+
+ cs = cf_section_sub_find_name2(NULL, "server", buffer);
+ if (!cs) {
+ cf_log_err(ci, "No such server \"%s\".", buffer);
+ return NULL;
+ }
+
+ return do_compile_modserver(parent, component, ci,
+ modrefname, cs, buffer);
+ }
+
*modname = NULL;
- radlog(L_ERR|L_CONS, "%s[%d] Unknown module \"%s\".", filename,
- lineno, modrefname);
+ cf_log_err(ci, "Failed to find \"%s\" in the \"modules\" section.", modrefname);
return NULL;
- }
+ } while (0);
/*
* We know it's all OK, allocate the structures, and fill
* them in.
*/
single = rad_malloc(sizeof(*single));
+ memset(single, 0, sizeof(*single));
csingle = mod_singletocallable(single);
+ csingle->parent = parent;
csingle->next = NULL;
- memcpy(csingle->actions, defaultactions[component][grouptype],
- sizeof csingle->actions);
+ if (!parent || (component != RLM_COMPONENT_AUTH)) {
+ memcpy(csingle->actions, defaultactions[component][grouptype],
+ sizeof csingle->actions);
+ } else { /* inside Auth-Type has different rules */
+ memcpy(csingle->actions, defaultactions[RLM_COMPONENT_AUTZ][grouptype],
+ sizeof csingle->actions);
+ }
rad_assert(modrefname != NULL);
csingle->name = modrefname;
csingle->type = MOD_SINGLE;
+ csingle->method = component;
/*
* Singles can override the actions, virtual modules cannot.
* maybe a csingle as a ref?
*/
if (cf_item_is_section(ci)) {
- CONF_SECTION *cs = cf_itemtosection(ci);
- CONF_PAIR *cp;
- const char *attr, *value;
-
- for (ci=cf_item_find_next(cs, NULL);
- ci != NULL;
- ci=cf_item_find_next(cs, ci)) {
+ CONF_ITEM *csi;
+
+ cs = cf_itemtosection(ci);
+ for (csi=cf_item_find_next(cs, NULL);
+ csi != NULL;
+ csi=cf_item_find_next(cs, csi)) {
- 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)));
+ if (cf_item_is_section(csi)) {
+ cf_log_err(csi, "Subsection of module instance call not allowed");
modcallable_free(&csingle);
return NULL;
}
- cp = cf_itemtopair(ci);
- attr = cf_pair_attr(cp);
- value = cf_pair_value(cp);
- lineno = cf_pair_lineno(cp);
+ if (!cf_item_is_pair(csi)) continue;
- if (!compile_action(csingle, attr, value, filename,
- lineno)) {
+ if (!compile_action(csingle, cf_itemtopair(csi))) {
modcallable_free(&csingle);
return NULL;
}
* wanted component
*/
if (!this->entry->module->methods[component]) {
- radlog(L_ERR|L_CONS,
- "%s: \"%s\" modules aren't allowed in '%s' sections -- they have no such method.",
- filename, this->entry->module->name,
- component_names[component]);
+ cf_log_err(ci, "\"%s\" modules aren't allowed in '%s' sections -- they have no such method.", this->entry->module->name,
+ comp2str[component]);
modcallable_free(&csingle);
return NULL;
}
return csingle;
}
-modcallable *compile_modsingle(int component, CONF_ITEM *ci,
- const char *filename, const char **modname)
+modcallable *compile_modsingle(modcallable *parent,
+ int component, CONF_ITEM *ci,
+ const char **modname)
{
- modcallable *ret = do_compile_modsingle(component, ci, filename,
+ modcallable *ret = do_compile_modsingle(parent, component, ci,
GROUPTYPE_SIMPLE,
modname);
dump_tree(component, ret);
return ret;
}
-static modcallable *do_compile_modgroup(int component, CONF_SECTION *cs,
- const char *filename, int grouptype,
- int parentgrouptype)
+
+/*
+ * Internal compile group code.
+ */
+static modcallable *do_compile_modgroup(modcallable *parent,
+ int component, CONF_SECTION *cs,
+ int grouptype, int parentgrouptype)
{
+ int i;
modgroup *g;
modcallable *c;
CONF_ITEM *ci;
- g = rad_malloc(sizeof *g);
+ g = rad_malloc(sizeof(*g));
+ memset(g, 0, sizeof(*g));
g->grouptype = grouptype;
c = mod_grouptocallable(g);
+ c->parent = parent;
+ c->type = MOD_GROUP;
c->next = NULL;
- memcpy(c->actions, defaultactions[component][parentgrouptype],
- sizeof(c->actions));
+ memset(c->actions, 0, sizeof(c->actions));
/*
* Remember the name for printing, etc.
* rbtree, so that groups can reference each other...
*/
c->name = cf_section_name2(cs);
- if (!c->name) c->name = "";
- c->type = MOD_GROUP;
+ if (!c->name) {
+ c->name = cf_section_name1(cs);
+ if (strcmp(c->name, "group") == 0) {
+ c->name = "";
+ } else {
+ c->type = MOD_POLICY;
+ }
+ }
g->children = NULL;
+ /*
+ * Loop over the children of this group.
+ */
for (ci=cf_item_find_next(cs, NULL);
ci != NULL;
ci=cf_item_find_next(cs, ci)) {
- if(cf_item_is_section(ci)) {
+ /*
+ * Sections are references to other groups, or
+ * to modules with updated return codes.
+ */
+ if (cf_item_is_section(ci)) {
const char *junk = NULL;
modcallable *single;
- int lineno;
CONF_SECTION *subcs = cf_itemtosection(ci);
- lineno = cf_section_lineno(subcs);
-
- single = do_compile_modsingle(component, ci, filename,
+ single = do_compile_modsingle(c, component, ci,
grouptype, &junk);
if (!single) {
- radlog(L_ERR|L_CONS,
- "%s[%d] Failed to parse \"%s\" subsection.\n",
- filename, lineno,
+ cf_log_err(ci, "Failed to parse \"%s\" subsection.",
cf_section_name1(subcs));
modcallable_free(&c);
return NULL;
}
add_child(g, single);
+ } else if (!cf_item_is_pair(ci)) { /* CONF_DATA */
+ continue;
+
} 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) {
+ if (!value) {
modcallable *single;
const char *junk = NULL;
- single = do_compile_modsingle(component,
- cf_pairtoitem(cp), filename,
- grouptype, &junk);
+ single = do_compile_modsingle(c,
+ component,
+ ci,
+ grouptype,
+ &junk);
if (!single) {
- radlog(L_ERR|L_CONS,
- "%s[%d] Failed to parse \"%s\" entry.\n",
- filename, lineno, attr);
+ cf_log_err(ci,
+ "Failed to parse \"%s\" entry.",
+ attr);
modcallable_free(&c);
return NULL;
}
/*
* Or a module instance with action.
*/
- } else if (!compile_action(c, attr, value, filename,
- lineno)) {
+ } else if (!compile_action(c, cp)) {
modcallable_free(&c);
return NULL;
} /* else it worked */
}
/*
+ * Set the default actions, if they haven't already been
+ * set.
+ */
+ for (i = 0; i < RLM_MODULE_NUMCODES; i++) {
+ if (!c->actions[i]) {
+ if (!parent || (component != RLM_COMPONENT_AUTH)) {
+ c->actions[i] = defaultactions[component][parentgrouptype][i];
+ } else { /* inside Auth-Type has different rules */
+ c->actions[i] = defaultactions[RLM_COMPONENT_AUTZ][parentgrouptype][i];
+ }
+ }
+ }
+
+ /*
* FIXME: If there are no children, return NULL?
*/
return mod_grouptocallable(g);
}
-modcallable *compile_modgroup(int component, CONF_SECTION *cs,
- const char *filename)
+modcallable *compile_modgroup(modcallable *parent,
+ int component, CONF_SECTION *cs)
{
- modcallable *ret = do_compile_modgroup(component, cs, filename,
+ modcallable *ret = do_compile_modgroup(parent, component, cs,
GROUPTYPE_SIMPLE,
GROUPTYPE_SIMPLE);
dump_tree(component, ret);
}
void add_to_modcallable(modcallable **parent, modcallable *this,
- int component, char *name)
+ int component, const char *name)
{
modgroup *g;
-
+
rad_assert(this != NULL);
if (*parent == NULL) {
modcallable *c;
g = rad_malloc(sizeof *g);
+ memset(g, 0, sizeof(*g));
g->grouptype = GROUPTYPE_SIMPLE;
c = mod_grouptocallable(g);
c->next = NULL;
rad_assert(name != NULL);
c->name = name;
c->type = MOD_GROUP;
+ c->method = component;
g->children = NULL;
*parent = mod_grouptocallable(g);
void modcallable_free(modcallable **pc)
{
modcallable *c, *loop, *next;
+
+ if (!pc || !*pc) return;
+
c = *pc;
- if(c->type==MOD_GROUP) {
- for(loop = mod_callabletogroup(c)->children;
+
+ if ((c->type > MOD_SINGLE) && (c->type <= MOD_POLICY)) {
+ modgroup *g = mod_callabletogroup(c);
+
+ if (g->children) for (loop = g->children;
loop ;
loop = next) {
next = loop->next;
modcallable_free(&loop);
}
+ pairfree(&g->vps);
}
free(c);
*pc = NULL;