#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>
static modcallable *do_compile_modgroup(modcallable *,
int, CONF_SECTION *,
int, int);
#define MOD_ACTION_RETURN (-1)
#define MOD_ACTION_REJECT (-2)
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];
};
#define GROUPTYPE_SIMPLE 0
#define GROUPTYPE_REDUNDANT 1
#define GROUPTYPE_APPEND 2
#define GROUPTYPE_COUNT 3
typedef struct {
modcallable mc;
int grouptype;
modcallable *children;
CONF_SECTION *cs;
VALUE_PAIR *vps;
} modgroup;
typedef struct {
modcallable mc;
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 FR_NAME_NUMBER grouptype_table[] = {
{ "", GROUPTYPE_SIMPLE },
{ "redundant ", GROUPTYPE_REDUNDANT },
{ "append ", GROUPTYPE_APPEND },
{ NULL, -1 }
};
static modsingle *mod_callabletosingle(modcallable *p)
{
rad_assert(p->type==MOD_SINGLE);
return (modsingle *)p;
}
static modgroup *mod_callabletogroup(modcallable *p)
{
rad_assert((p->type > MOD_SINGLE) && (p->type <= MOD_POLICY));
return (modgroup *)p;
}
static modcallable *mod_singletocallable(modsingle *p)
{
return (modcallable *)p;
}
static modcallable *mod_grouptocallable(modgroup *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;
}
static void add_child(modgroup *g, modcallable *c)
{
modcallable **head = &g->children;
modcallable *node = *head;
modcallable **last = head;
if (!c) return;
while (node) {
last = &node->next;
node = node->next;
}
rad_assert(c->next == NULL);
*last = c;
c->parent = mod_grouptocallable(g);
}
static const FR_NAME_NUMBER 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 int compile_action(modcallable *c, CONF_PAIR *cp)
{
int action;
const char *attr, *value;
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);
if (action == 0) return 0;
} else {
cf_log_err(cf_pairtoitem(cp), "Unknown action '%s'.\n",
value);
return 0;
}
if (strcasecmp(attr, "default") != 0) {
int rcode;
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;
} else {
int i;
for (i = 0; i < RLM_MODULE_NUMCODES; i++) {
if (!c->actions[i]) c->actions[i] = action;
}
}
return 1;
}
static const char * const comp2str[] = {
"authenticate",
"authorize",
"preacct",
"accounting",
"session",
"pre-proxy",
"post-proxy",
"post-auth"
#ifdef WITH_COA
,
"recv-coa",
"send-coa"
#endif
};
#ifdef HAVE_PTHREAD_H
static void safe_lock(module_instance_t *instance)
{
if (instance->mutex)
pthread_mutex_lock(instance->mutex);
}
static void safe_unlock(module_instance_t *instance)
{
if (instance->mutex)
pthread_mutex_unlock(instance->mutex);
}
#else
#define safe_lock(foo)
#define safe_unlock(foo)
#endif
static int call_modsingle(int component, modsingle *sp, REQUEST *request)
{
int myresult;
rad_assert(request != NULL);
RDEBUG3(" modsingle[%s]: calling %s (%s) for request %d",
comp2str[component], sp->modinst->name,
sp->modinst->entry->name, request->number);
if (sp->modinst->dead) {
myresult = RLM_MODULE_FAIL;
goto fail;
}
safe_lock(sp->modinst);
request->module = sp->modinst->name;
myresult = sp->modinst->entry->module->methods[component](
sp->modinst->insthandle, request);
request->module = "";
safe_unlock(sp->modinst);
fail:
RDEBUG3(" modsingle[%s]: returned from %s (%s) for request %d",
comp2str[component], sp->modinst->name,
sp->modinst->entry->name, request->number);
return myresult;
}
static int default_component_results[RLM_COMPONENT_COUNT] = {
RLM_MODULE_REJECT,
RLM_MODULE_NOTFOUND,
RLM_MODULE_NOOP,
RLM_MODULE_NOOP,
RLM_MODULE_FAIL,
RLM_MODULE_NOOP,
RLM_MODULE_NOOP,
RLM_MODULE_NOOP
#ifdef WITH_COA
,
RLM_MODULE_NOOP,
RLM_MODULE_NOOP
#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"
};
static const char *modcall_spaces = "++++++++++++++++++++++++++++++++";
#define MODCALL_STACK_MAX (32)
typedef struct modcall_stack {
int pointer;
int priority[MODCALL_STACK_MAX];
int result[MODCALL_STACK_MAX];
modcallable *children[MODCALL_STACK_MAX];
modcallable *start[MODCALL_STACK_MAX];
} modcall_stack;
int modcall(int component, modcallable *c, REQUEST *request)
{
int myresult;
modcall_stack stack;
modcallable *parent, *child;
modsingle *sp;
int if_taken, was_if;
if ((component < 0) || (component >= RLM_COMPONENT_COUNT)) {
return RLM_MODULE_FAIL;
}
stack.pointer = 0;
stack.priority[0] = 0;
stack.children[0] = c;
stack.start[0] = NULL;
myresult = stack.result[0] = default_component_results[component];
was_if = if_taken = FALSE;
while (1) {
if ((request->master_state == REQUEST_STOP_PROCESSING) ||
(request->parent &&
(request->parent->master_state == REQUEST_STOP_PROCESSING))) {
myresult = RLM_MODULE_FAIL;
break;
}
child = stack.children[stack.pointer];
if (!child) {
myresult = stack.result[stack.pointer];
break;
}
parent = child->parent;
#ifdef WITH_UNLANG
if ((child->type == MOD_ELSE) || (child->type == MOD_ELSIF)) {
myresult = stack.result[stack.pointer];
if (!was_if) {
RDEBUG2("%.*s ... skipping %s for request %d: No preceding \"if\"",
stack.pointer + 1, modcall_spaces,
group_name[child->type],
request->number);
goto unroll;
}
if (if_taken) {
RDEBUG2("%.*s ... skipping %s for request %d: Preceding \"if\" was taken",
stack.pointer + 1, modcall_spaces,
group_name[child->type],
request->number);
goto unroll;
}
}
if ((child->type == MOD_IF) || (child->type == MOD_ELSIF)) {
int condition = TRUE;
const char *p = child->name;
RDEBUG2("%.*s? %s %s",
stack.pointer + 1, modcall_spaces,
(child->type == MOD_IF) ? "if" : "elsif",
child->name);
if (radius_evaluate_condition(request, myresult,
0, &p, TRUE, &condition)) {
RDEBUG2("%.*s? %s %s -> %s",
stack.pointer + 1, modcall_spaces,
(child->type == MOD_IF) ? "if" : "elsif",
child->name, (condition != FALSE) ? "TRUE" : "FALSE");
} else {
condition = FALSE;
}
if (!condition) {
stack.result[stack.pointer] = myresult;
stack.children[stack.pointer] = NULL;
was_if = TRUE;
if_taken = FALSE;
goto next_section;
}
}
if (child->type == MOD_UPDATE) {
int rcode;
modgroup *g = mod_callabletogroup(child);
rcode = radius_update_attrlist(request, g->cs,
g->vps, child->name);
if (rcode != RLM_MODULE_UPDATED) {
myresult = rcode;
} else {
}
goto handle_result;
}
#endif
if (child->type == MOD_REFERENCE) {
modref *mr = mod_callabletoref(child);
const char *server = request->server;
if (server == mr->ref_name) {
RDEBUG("WARNING: Suppressing recursive call to server %s", server);
myresult = RLM_MODULE_NOOP;
goto handle_result;
}
request->server = mr->ref_name;
RDEBUG("server %s { # nested call", mr->ref_name);
myresult = indexed_modcall(component, 0, request);
RDEBUG("} # server %s with nested call", mr->ref_name);
request->server = server;
goto handle_result;
}
if (child->type == MOD_XLAT) {
modxlat *mx = mod_callabletoxlat(child);
char buffer[128];
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,
request->packet->vps,
NULL, 1);
}
goto handle_result;
}
if (child->type != MOD_SINGLE) {
int count = 1;
modcallable *p, *q;
#ifdef WITH_UNLANG
modcallable *null_case;
#endif
modgroup *g = mod_callabletogroup(child);
stack.pointer++;
if (stack.pointer >= MODCALL_STACK_MAX) {
radlog(L_ERR, "Internal sanity check failed: module stack is too deep");
exit(1);
}
stack.priority[stack.pointer] = 0;
stack.result[stack.pointer] = default_component_results[component];
switch (child->type) {
#ifdef WITH_UNLANG
char buffer[1024];
case MOD_IF:
case MOD_ELSE:
case MOD_ELSIF:
case MOD_CASE:
#endif
case MOD_GROUP:
case MOD_POLICY:
stack.children[stack.pointer] = g->children;
break;
case MOD_LOAD_BALANCE:
case MOD_REDUNDANT_LOAD_BALANCE:
q = NULL;
for(p = g->children; p; p = p->next) {
if (!q) {
q = p;
count = 1;
continue;
}
count++;
if ((count * (fr_rand() & 0xffff)) < (uint32_t) 0x10000) {
q = p;
}
}
stack.children[stack.pointer] = q;
break;
#ifdef WITH_UNLANG
case MOD_SWITCH:
if (!strchr(child->name, '%')) {
VALUE_PAIR *vp = NULL;
radius_get_vp(request, child->name,
&vp);
if (vp) {
vp_prints_value(buffer,
sizeof(buffer),
vp, 0);
} else {
*buffer = '\0';
}
} else {
radius_xlat(buffer, sizeof(buffer),
child->name, request, NULL);
}
null_case = q = NULL;
for(p = g->children; p; p = p->next) {
if (!p->name) {
if (!null_case) null_case = p;
continue;
}
if (strcmp(buffer, p->name) == 0) {
q = p;
break;
}
}
if (!q) q = null_case;
stack.children[stack.pointer] = q;
break;
#endif
default:
RDEBUG2("Internal sanity check failed in modcall %d", child->type);
exit(1);
break;
}
stack.start[stack.pointer] = stack.children[stack.pointer];
RDEBUG2("%.*s- entering %s %s {...}",
stack.pointer, modcall_spaces,
group_name[child->type],
child->name ? child->name : "");
if (!stack.children[stack.pointer]) {
RDEBUG2("%.*s- %s %s returns %s",
stack.pointer + 1, modcall_spaces,
group_name[child->type],
child->name ? child->name : "",
fr_int2str(rcode_table,
stack.result[stack.pointer],
"??"));
goto do_return;
}
continue;
}
sp = mod_callabletosingle(child);
myresult = call_modsingle(child->method, sp, request);
handle_result:
RDEBUG2("%.*s[%s] returns %s",
stack.pointer + 1, modcall_spaces,
child->name ? child->name : "",
fr_int2str(rcode_table, myresult, "??"));
if (component != RLM_COMPONENT_SESS) request->simul_max = myresult;
unroll:
if (child->actions[myresult] == MOD_ACTION_RETURN) {
stack.result[stack.pointer] = myresult;
stack.children[stack.pointer] = NULL;
goto do_return;
}
if (child->actions[myresult] == MOD_ACTION_REJECT) {
stack.children[stack.pointer] = NULL;
stack.result[stack.pointer] = RLM_MODULE_REJECT;
goto do_return;
}
if (child->actions[myresult] >= stack.priority[stack.pointer]) {
stack.result[stack.pointer] = myresult;
stack.priority[stack.pointer] = child->actions[myresult];
}
#ifdef WITH_UNLANG
next_section:
#endif
if (!parent) {
rad_assert(stack.pointer == 0);
myresult = stack.result[0];
break;
}
rad_assert(child != NULL);
switch (parent->type) {
#ifdef WITH_UNLANG
case MOD_IF:
case MOD_ELSE:
case MOD_ELSIF:
case MOD_CASE:
#endif
case MOD_GROUP:
case MOD_POLICY:
stack.children[stack.pointer] = child->next;
break;
#ifdef WITH_UNLANG
case MOD_SWITCH:
#endif
case MOD_LOAD_BALANCE:
stack.children[stack.pointer] = NULL;
break;
case MOD_REDUNDANT_LOAD_BALANCE:
if (child->next) {
stack.children[stack.pointer] = child->next;
} else {
modgroup *g = mod_callabletogroup(parent);
stack.children[stack.pointer] = g->children;
}
if (stack.children[stack.pointer] == stack.start[stack.pointer]) {
stack.children[stack.pointer] = NULL;
}
break;
default:
RDEBUG2("Internal sanity check failed in modcall next %d", child->type);
exit(1);
}
if (!stack.children[stack.pointer]) {
do_return:
rad_assert(stack.pointer > 0);
myresult = stack.result[stack.pointer];
stack.pointer--;
if (stack.pointer == 0) break;
RDEBUG2("%.*s- %s %s returns %s",
stack.pointer + 1, modcall_spaces,
group_name[parent->type],
parent->name ? parent->name : "",
fr_int2str(rcode_table, myresult, "??"));
#ifdef WITH_UNLANG
if ((parent->type == MOD_IF) ||
(parent->type == MOD_ELSIF)) {
if_taken = was_if = TRUE;
} else {
if_taken = was_if = FALSE;
}
#endif
child = stack.children[stack.pointer];
parent = child->parent;
goto unroll;
}
}
return myresult;
}
#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;
}
static void dump_mc(modcallable *c, int indent)
{
int i;
if(c->type==MOD_SINGLE) {
modsingle *single = mod_callabletosingle(c);
RDEBUG("%.*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;
RDEBUG("%.*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);
}
for(i = 0; i<RLM_MODULE_NUMCODES; ++i) {
RDEBUG("%.*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]));
}
RDEBUG("%.*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
static const int
defaultactions[RLM_COMPONENT_COUNT][GROUPTYPE_COUNT][RLM_MODULE_NUMCODES] =
{
{
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
1,
1
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
2,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
}
},
{
{
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
3,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
1,
2,
4
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
2,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
}
},
{
{
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
2,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
1,
3
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
2,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
}
},
{
{
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
2,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
1,
3
},
{
1,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
1,
1,
1,
2,
4
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
2,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
}
},
{
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
}
},
{
{
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
3,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
1,
2,
4
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
2,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
}
},
{
{
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
3,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
1,
2,
4
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
2,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
}
},
{
{
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
3,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
1,
2,
4
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
2,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
}
}
#ifdef WITH_COA
,
{
{
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
3,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
1,
2,
4
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
2,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
}
},
{
{
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
3,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
1,
2,
4
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
},
{
MOD_ACTION_RETURN,
1,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN,
2,
MOD_ACTION_RETURN,
MOD_ACTION_RETURN
}
}
#endif
};
#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;
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;
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);
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;
}
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));
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;
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;
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;
}
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);
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;
}
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 (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;
}
static modcallable *do_compile_modsingle(modcallable *parent,
int component, CONF_ITEM *ci,
int grouptype,
const char **modname)
{
#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)) {
const char *name2;
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(parent, component, cs,
GROUPTYPE_SIMPLE,
grouptype);
} else if (strcmp(modrefname, "redundant") == 0) {
*modname = name2;
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(parent, component, cs,
GROUPTYPE_APPEND,
grouptype);
} else if (strcmp(modrefname, "load-balance") == 0) {
*modname = name2;
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;
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 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;
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);
for (i = 0; i < RLM_MODULE_NUMCODES; i++) {
csingle->actions[i] = MOD_ACTION_RETURN;
}
return csingle;
#endif
}
} else if (!cf_item_is_pair(ci)) {
return NULL;
} else {
CONF_PAIR *cp = cf_itemtopair(ci);
modrefname = cf_pair_attr(cp);
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);
}
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");
if (cs) subcs = cf_section_sub_find_name2(cs, NULL,
modrefname);
}
if (subcs) {
DEBUG2(" Module: Loading virtual module %s",
modrefname);
if (cf_section_name2(subcs)) {
return do_compile_modsingle(parent,
component,
cf_sectiontoitem(subcs),
grouptype,
modname);
} else {
return do_compile_modgroup(parent,
component,
subcs,
GROUPTYPE_SIMPLE,
grouptype);
}
}
}
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;
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;
cf_log_err(ci, "Failed to load module \"%s\".", modrefname);
return NULL;
} while (0);
single = rad_malloc(sizeof(*single));
memset(single, 0, sizeof(*single));
csingle = mod_singletocallable(single);
csingle->parent = parent;
csingle->next = NULL;
if (!parent || (component != RLM_COMPONENT_AUTH)) {
memcpy(csingle->actions, defaultactions[component][grouptype],
sizeof csingle->actions);
} else {
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;
if (cf_item_is_section(ci)) {
cs = cf_itemtosection(ci);
for (ci=cf_item_find_next(cs, NULL);
ci != NULL;
ci=cf_item_find_next(cs, ci)) {
if (cf_item_is_section(ci)) {
cf_log_err(ci, "Subsection of module instance call not allowed");
modcallable_free(&csingle);
return NULL;
}
if (!cf_item_is_pair(ci)) continue;
if (!compile_action(csingle, cf_itemtopair(ci))) {
modcallable_free(&csingle);
return NULL;
}
}
}
if (!this->entry->module->methods[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;
}
single->modinst = this;
*modname = this->entry->module->name;
return csingle;
}
modcallable *compile_modsingle(modcallable *parent,
int component, CONF_ITEM *ci,
const char **modname)
{
modcallable *ret = do_compile_modsingle(parent, component, ci,
GROUPTYPE_SIMPLE,
modname);
dump_tree(component, ret);
return ret;
}
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));
memset(g, 0, sizeof(*g));
g->grouptype = grouptype;
c = mod_grouptocallable(g);
c->parent = parent;
c->type = MOD_GROUP;
c->next = NULL;
memset(c->actions, 0, sizeof(c->actions));
c->name = cf_section_name2(cs);
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;
for (ci=cf_item_find_next(cs, NULL);
ci != NULL;
ci=cf_item_find_next(cs, ci)) {
if (cf_item_is_section(ci)) {
const char *junk = NULL;
modcallable *single;
CONF_SECTION *subcs = cf_itemtosection(ci);
single = do_compile_modsingle(c, component, ci,
grouptype, &junk);
if (!single) {
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)) {
continue;
} else {
const char *attr, *value;
CONF_PAIR *cp = cf_itemtopair(ci);
attr = cf_pair_attr(cp);
value = cf_pair_value(cp);
if (!value) {
modcallable *single;
const char *junk = NULL;
single = do_compile_modsingle(c,
component,
ci,
grouptype,
&junk);
if (!single) {
cf_log_err(ci,
"Failed to parse \"%s\" entry.",
attr);
modcallable_free(&c);
return NULL;
}
add_child(g, single);
} else if (!compile_action(c, cp)) {
modcallable_free(&c);
return NULL;
}
}
}
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 {
c->actions[i] = defaultactions[RLM_COMPONENT_AUTZ][parentgrouptype][i];
}
}
}
return mod_grouptocallable(g);
}
modcallable *compile_modgroup(modcallable *parent,
int component, CONF_SECTION *cs)
{
modcallable *ret = do_compile_modgroup(parent, component, cs,
GROUPTYPE_SIMPLE,
GROUPTYPE_SIMPLE);
dump_tree(component, ret);
return ret;
}
void add_to_modcallable(modcallable **parent, modcallable *this,
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;
memcpy(c->actions,
defaultactions[component][GROUPTYPE_SIMPLE],
sizeof(c->actions));
rad_assert(name != NULL);
c->name = name;
c->type = MOD_GROUP;
c->method = component;
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_SINGLE) {
modgroup *g = mod_callabletogroup(c);
for(loop = g->children;
loop ;
loop = next) {
next = loop->next;
modcallable_free(&loop);
}
pairfree(&g->vps);
}
free(c);
*pc = NULL;
}