From 50b37d4a27d3295a29afca2286f1a5a086142cec Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 11:49:46 +0200 Subject: Adding upstream version 3.2.1+dfsg. Signed-off-by: Daniel Baumann --- src/main/modcall.c | 4041 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 4041 insertions(+) create mode 100644 src/main/modcall.c (limited to 'src/main/modcall.c') diff --git a/src/main/modcall.c b/src/main/modcall.c new file mode 100644 index 0000000..aa6abf8 --- /dev/null +++ b/src/main/modcall.c @@ -0,0 +1,4041 @@ +/* + * @name modcall.c + * + * Version: $Id$ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + * + * Copyright 2000,2006 The FreeRADIUS server project + */ + +RCSID("$Id$") + +#include +#include +#include +#include +#include + + +/* mutually-recursive static functions need a prototype up front */ +static modcallable *do_compile_modgroup(modcallable *, + rlm_components_t, CONF_SECTION *, + int, 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) + +/* Here are our basic types: modcallable, modgroup, and modsingle. For an + * explanation of what they are all about, see doc/configurable_failover.rst */ +struct modcallable { + modcallable *parent; + struct modcallable *next; + char const *name; + char const *debug_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, + MOD_FOREACH, MOD_BREAK, MOD_RETURN, +#endif + MOD_POLICY, MOD_REFERENCE, MOD_XLAT } type; + rlm_components_t method; + int actions[RLM_MODULE_NUMCODES]; +}; + +#define MOD_LOG_OPEN_BRACE RDEBUG2("%s {", c->debug_name) + +#define MOD_LOG_CLOSE_BRACE RDEBUG2("} # %s = %s", c->debug_name, fr_int2str(mod_rcode_table, result, "")) + +typedef struct { + modcallable mc; /* self */ + enum { + GROUPTYPE_SIMPLE = 0, + GROUPTYPE_REDUNDANT, + GROUPTYPE_COUNT + } grouptype; /* after mc */ + modcallable *children; + modcallable *tail; /* of the children list */ + CONF_SECTION *cs; + vp_map_t *map; /* update */ + vp_tmpl_t *vpt; /* switch */ + fr_cond_t *cond; /* if/elsif */ + bool done_pass2; +} modgroup; + +typedef struct { + modcallable mc; + module_instance_t *modinst; +} modsingle; + +typedef struct { + modcallable mc; + char const *ref_name; + CONF_SECTION *ref_cs; +} modref; + +typedef struct { + modcallable mc; + int exec; + char *xlat_name; +} modxlat; + +/* 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) +{ + 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; +} + +/* modgroups are grown by adding a modcallable to the end */ +static void add_child(modgroup *g, modcallable *c) +{ + if (!c) return; + + (void) talloc_steal(g, c); + + if (!g->children) { + g->children = g->tail = c; + } else { + rad_assert(g->tail->next == NULL); + g->tail->next = c; + g->tail = c; + } + + c->parent = mod_grouptocallable(g); +} + +/* 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 } +}; + + +/* + * Compile action && rcode for later use. + */ +static int compile_action(modcallable *c, CONF_PAIR *cp) +{ + int action; + char const *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); + + /* + * Don't allow priority zero, for future use. + */ + if (action == 0) return 0; + } else { + cf_log_err_cp(cp, "Unknown action '%s'.\n", + value); + return 0; + } + + if (strcasecmp(attr, "default") != 0) { + int rcode; + + rcode = fr_str2int(mod_rcode_table, attr, -1); + if (rcode < 0) { + cf_log_err_cp(cp, + "Unknown module rcode '%s'.\n", + attr); + return 0; + } + c->actions[rcode] = action; + + } 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; +} + +/* Some short names for debugging output */ +static char const * 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 +/* + * 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 rlm_rcode_t CC_HINT(nonnull) call_modsingle(rlm_components_t component, modsingle *sp, REQUEST *request) +{ + int blocked; + int indent = request->log.indent; + char const *old; + + /* + * If the request should stop, refuse to do anything. + */ + blocked = (request->master_state == REQUEST_STOP_PROCESSING); + if (blocked) return RLM_MODULE_NOOP; + + RDEBUG3("modsingle[%s]: calling %s (%s)", + comp2str[component], sp->modinst->name, + sp->modinst->entry->name); + request->log.indent = 0; + + if (sp->modinst->force) { + request->rcode = sp->modinst->code; + goto fail; + } + + /* + * For logging unresponsive children. + */ + old = request->module; + request->module = sp->modinst->name; + + safe_lock(sp->modinst); + request->rcode = sp->modinst->entry->module->methods[component](sp->modinst->insthandle, request); + safe_unlock(sp->modinst); + + request->module = old; + + /* + * Wasn't blocked, and now is. Complain! + */ + blocked = (request->master_state == REQUEST_STOP_PROCESSING); + if (blocked) { + RWARN("Module %s became unblocked", sp->modinst->entry->name); + } + + fail: + request->log.indent = indent; + RDEBUG3("modsingle[%s]: returned from %s (%s)", + comp2str[component], sp->modinst->name, + sp->modinst->entry->name); + + return request->rcode; +} + +static int default_component_results[MOD_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 +}; + + +extern char const *unlang_keyword[]; + +char const *unlang_keyword[] = { + "", + "single", + "group", + "load-balance group", + "redundant-load-balance group", +#ifdef WITH_UNLANG + "if", + "else", + "elsif", + "update", + "switch", + "case", + "foreach", + "break", + "return", +#endif + "policy", + "reference", + "xlat", + NULL +}; + +static char const modcall_spaces[] = " "; + +#define MODCALL_STACK_MAX (32) + +/* + * Don't call the modules recursively. Instead, do them + * iteratively, and manage the call stack ourselves. + */ +typedef struct modcall_stack_entry_t { + rlm_rcode_t result; + int priority; + int unwind; /* unwind to this one if it exists */ + modcallable *c; +} modcall_stack_entry_t; + + +static bool modcall_recurse(REQUEST *request, rlm_components_t component, int depth, + modcall_stack_entry_t *entry, bool do_next_sibling); + +/* + * Call a child of a block. + */ +static void modcall_child(REQUEST *request, rlm_components_t component, int depth, + modcall_stack_entry_t *entry, modcallable *c, + rlm_rcode_t *result, bool do_next_sibling) +{ + modcall_stack_entry_t *next; + + if (depth >= MODCALL_STACK_MAX) { + ERROR("Internal sanity check failed: module stack is too deep"); + fr_exit(1); + } + + /* + * Initialize the childs stack frame. + */ + next = entry + 1; + next->c = c; + next->result = entry->result; + next->priority = 0; + next->unwind = 0; + + if (!modcall_recurse(request, component, + depth, next, do_next_sibling)) { + *result = RLM_MODULE_FAIL; + return; + } + + /* + * Unwind back up the stack + */ + if (next->unwind != 0) { + entry->unwind = next->unwind; + } + + *result = next->result; + + return; +} + + +/* + * Interpret the various types of blocks. + */ +static bool modcall_recurse(REQUEST *request, rlm_components_t component, int depth, + modcall_stack_entry_t *entry, bool do_next_sibling) +{ + bool if_taken, was_if; + modcallable *c; + int priority; + rlm_rcode_t result; + + was_if = if_taken = false; + result = RLM_MODULE_UNKNOWN; + RINDENT(); + +redo: + priority = -1; + c = entry->c; + + /* + * Nothing more to do. Return the code and priority + * which was set by the caller. + */ + if (!c) goto finish; + + if (fr_debug_lvl >= 3) { + VERIFY_REQUEST(request); + } + + rad_assert(c->debug_name != NULL); /* if this happens, all bets are off. */ + + /* + * 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; + goto finish; + } + +#ifdef WITH_UNLANG + /* + * Handle "if" conditions. + */ + if (c->type == MOD_IF) { + int condition; + modgroup *g; + + mod_if: + g = mod_callabletogroup(c); + rad_assert(g->cond != NULL); + + RDEBUG2("%s %s{", unlang_keyword[c->type], c->name); + + /* + * Use "result" UNLESS it wasn't set, in which + * case we use the previous result on the stack. + */ + condition = radius_evaluate_cond(request, result != RLM_MODULE_UNKNOWN ? result : entry->result, 0, g->cond); + if (condition < 0) { + condition = false; + REDEBUG("Failed retrieving values required to evaluate condition"); + } else { + RDEBUG2("%s %s -> %s", + unlang_keyword[c->type], + c->name, condition ? "TRUE" : "FALSE"); + } + + /* + * Didn't pass. Remember that. + */ + if (!condition) { + was_if = true; + if_taken = false; + goto next_sibling; + } + + /* + * We took the "if". Go recurse into its' children. + */ + was_if = true; + if_taken = true; + goto do_children; + } /* MOD_IF */ + + /* + * "else" if the previous "if" was taken. + * "if" if the previous if wasn't taken. + */ + if (c->type == MOD_ELSIF) { + if (!was_if) goto elsif_error; + + /* + * Like MOD_ELSE, but allow for a later "else" + */ + if (if_taken) { + RDEBUG2("... skipping %s: Preceding \"if\" was taken", + unlang_keyword[c->type]); + was_if = true; + if_taken = true; + goto next_sibling; + } + + /* + * Check the "if" condition. + */ + goto mod_if; + } /* MOD_ELSIF */ + + /* + * "else" for a preceding "if". + */ + if (c->type == MOD_ELSE) { + if (!was_if) { /* error */ + elsif_error: + RDEBUG2("... skipping %s: No preceding \"if\"", + unlang_keyword[c->type]); + goto next_sibling; + } + + if (if_taken) { + RDEBUG2("... skipping %s: Preceding \"if\" was taken", + unlang_keyword[c->type]); + was_if = false; + if_taken = false; + goto next_sibling; + } + + /* + * We need to process it. Go do that. + */ + 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; +#endif /* WITH_UNLANG */ + + if (c->type == MOD_SINGLE) { + modsingle *sp; + + /* + * Process a stand-alone child, and fall through + * to dealing with it's parent. + */ + sp = mod_callabletosingle(c); + + result = call_modsingle(c->method, sp, request); + RDEBUG2("[%s] = %s", c->name ? c->name : "", + fr_int2str(mod_rcode_table, result, "")); + goto calculate_result; + } /* MOD_SINGLE */ + +#ifdef WITH_UNLANG + /* + * Update attribute(s) + */ + if (c->type == MOD_UPDATE) { + int rcode; + modgroup *g = mod_callabletogroup(c); + vp_map_t *map; + + MOD_LOG_OPEN_BRACE; + RINDENT(); + for (map = g->map; map != NULL; map = map->next) { + rcode = map_to_request(request, map, map_to_vp, NULL); + if (rcode < 0) { + result = (rcode == -2) ? RLM_MODULE_INVALID : RLM_MODULE_FAIL; + REXDENT(); + MOD_LOG_CLOSE_BRACE; + goto calculate_result; + } + } + REXDENT(); + result = RLM_MODULE_NOOP; + MOD_LOG_CLOSE_BRACE; + goto calculate_result; + } /* MOD_IF */ + + /* + * Loop over a set of attributes. + */ + if (c->type == MOD_FOREACH) { + int i, foreach_depth = -1; + VALUE_PAIR *vps, *vp; + modcall_stack_entry_t *next = NULL; + vp_cursor_t copy; + modgroup *g = mod_callabletogroup(c); + + if (depth >= MODCALL_STACK_MAX) { + ERROR("Internal sanity check failed: module stack is too deep"); + fr_exit(1); + } + + /* + * Figure out how deep we are in nesting by looking at request_data + * stored previously. + */ + for (i = 0; i < 8; i++) { + if (!request_data_reference(request, (void *)radius_get_vp, i)) { + foreach_depth = i; + break; + } + } + + if (foreach_depth < 0) { + REDEBUG("foreach Nesting too deep!"); + result = RLM_MODULE_FAIL; + goto calculate_result; + } + + /* + * Copy the VPs from the original request, this ensures deterministic + * behaviour if someone decides to add or remove VPs in the set were + * iterating over. + */ + if (tmpl_copy_vps(request, &vps, request, g->vpt) < 0) { /* nothing to loop over */ + MOD_LOG_OPEN_BRACE; + result = RLM_MODULE_NOOP; + MOD_LOG_CLOSE_BRACE; + goto calculate_result; + } + + rad_assert(vps != NULL); + fr_cursor_init(©, &vps); + + RDEBUG2("foreach %s ", c->name); + + /* + * This is the actual body of the foreach loop + */ + for (vp = fr_cursor_first(©); + vp != NULL; + vp = fr_cursor_next(©)) { +#ifndef NDEBUG + if (fr_debug_lvl >= 2) { + char buffer[1024]; + + vp_prints_value(buffer, sizeof(buffer), vp, '"'); + RDEBUG2("# Foreach-Variable-%d = %s", foreach_depth, buffer); + } +#endif + + /* + * Add the vp to the request, so that + * xlat.c, xlat_foreach() can find it. + */ + request_data_add(request, (void *)radius_get_vp, foreach_depth, &vp, false); + + /* + * Initialize the childs stack frame. + */ + next = entry + 1; + next->c = g->children; + next->result = entry->result; + next->priority = 0; + next->unwind = 0; + + if (!modcall_recurse(request, component, depth + 1, next, true)) { + break; + } + + /* + * We've been asked to unwind to the + * enclosing "foreach". We're here, so + * we can stop unwinding. + */ + if (next->unwind == MOD_BREAK) { + entry->unwind = 0; + break; + } + + /* + * Unwind all the way. + */ + if (next->unwind == MOD_RETURN) { + entry->unwind = MOD_RETURN; + break; + } + } /* loop over VPs */ + + /* + * Free the copied vps and the request data + * If we don't remove the request data, something could call + * the xlat outside of a foreach loop and trigger a segv. + */ + fr_pair_list_free(&vps); + request_data_get(request, (void *)radius_get_vp, foreach_depth); + + rad_assert(next != NULL); + result = next->result; + priority = next->priority; + MOD_LOG_CLOSE_BRACE; + goto calculate_result; + } /* MOD_FOREACH */ + + /* + * Break out of a "foreach" loop, or return from a nested + * group. + */ + if ((c->type == MOD_BREAK) || (c->type == MOD_RETURN)) { + int i; + VALUE_PAIR **copy_p; + + RDEBUG2("%s", unlang_keyword[c->type]); + + for (i = 8; i >= 0; i--) { + copy_p = request_data_get(request, (void *)radius_get_vp, i); + if (copy_p) { + if (c->type == MOD_BREAK) { + RDEBUG2("# break Foreach-Variable-%d", i); + break; + } + } + } + + /* + * Leave result / priority on the stack, and stop processing the section. + */ + entry->unwind = c->type; + goto finish; + } /* MOD_BREAK */ + +#endif /* WITH_UNLANG */ + + /* + * Child is a group that has children of it's own. + */ + if ((c->type == MOD_GROUP) || (c->type == MOD_POLICY) +#ifdef WITH_UNLANG + || (c->type == MOD_CASE) +#endif + ) { + modgroup *g; + +#ifdef WITH_UNLANG + do_children: +#endif + g = mod_callabletogroup(c); + + /* + * 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 (!g->children) { + if (c->type == MOD_CASE) { + result = RLM_MODULE_NOOP; + goto calculate_result; + } + + RDEBUG2("%s { ... } # empty sub-section is ignored", c->name); + goto next_sibling; + } + + MOD_LOG_OPEN_BRACE; + modcall_child(request, component, + depth + 1, entry, g->children, + &result, true); + MOD_LOG_CLOSE_BRACE; + goto calculate_result; + } /* MOD_GROUP */ + +#ifdef WITH_UNLANG + if (c->type == MOD_SWITCH) { + modcallable *this, *found, *null_case; + modgroup *g, *h; + fr_cond_t cond; + value_data_t data; + vp_map_t map; + vp_tmpl_t vpt; + + MOD_LOG_OPEN_BRACE; + + g = mod_callabletogroup(c); + + memset(&cond, 0, sizeof(cond)); + memset(&map, 0, sizeof(map)); + + cond.type = COND_TYPE_MAP; + cond.data.map = ↦ + + map.op = T_OP_CMP_EQ; + map.ci = cf_section_to_item(g->cs); + + rad_assert(g->vpt != NULL); + + null_case = found = NULL; + data.ptr = NULL; + + /* + * The attribute doesn't exist. We can skip + * directly to the default 'case' statement. + */ + if ((g->vpt->type == TMPL_TYPE_ATTR) && (tmpl_find_vp(NULL, request, g->vpt) < 0)) { + find_null_case: + for (this = g->children; this; this = this->next) { + rad_assert(this->type == MOD_CASE); + + h = mod_callabletogroup(this); + if (h->vpt) continue; + + found = this; + break; + } + + goto do_null_case; + } + + /* + * Expand the template if necessary, so that it + * is evaluated once instead of for each 'case' + * statement. + */ + if ((g->vpt->type == TMPL_TYPE_XLAT_STRUCT) || + (g->vpt->type == TMPL_TYPE_XLAT) || + (g->vpt->type == TMPL_TYPE_EXEC)) { + char *p; + ssize_t len; + + len = tmpl_aexpand(request, &p, request, g->vpt, NULL, NULL); + if (len < 0) goto find_null_case; + data.strvalue = p; + tmpl_init(&vpt, TMPL_TYPE_LITERAL, data.strvalue, len); + } + + /* + * Find either the exact matching name, or the + * "case {...}" statement. + */ + for (this = g->children; this; this = this->next) { + rad_assert(this->type == MOD_CASE); + + h = mod_callabletogroup(this); + + /* + * Remember the default case + */ + if (!h->vpt) { + if (!null_case) null_case = this; + continue; + } + + /* + * If we're switching over an attribute + * AND we haven't pre-parsed the data for + * the case statement, then cast the data + * to the type of the attribute. + */ + if ((g->vpt->type == TMPL_TYPE_ATTR) && + (h->vpt->type != TMPL_TYPE_DATA)) { + map.rhs = g->vpt; + map.lhs = h->vpt; + cond.cast = g->vpt->tmpl_da; + + /* + * Remove unnecessary casting. + */ + if ((h->vpt->type == TMPL_TYPE_ATTR) && + (g->vpt->tmpl_da->type == h->vpt->tmpl_da->type)) { + cond.cast = NULL; + } + + /* + * Use the pre-expanded string. + */ + } else if ((g->vpt->type == TMPL_TYPE_XLAT_STRUCT) || + (g->vpt->type == TMPL_TYPE_XLAT) || + (g->vpt->type == TMPL_TYPE_EXEC)) { + map.rhs = h->vpt; + map.lhs = &vpt; + cond.cast = NULL; + + /* + * Else evaluate the 'switch' statement. + */ + } else { + map.rhs = h->vpt; + map.lhs = g->vpt; + cond.cast = NULL; + } + + if (radius_evaluate_map(request, RLM_MODULE_UNKNOWN, 0, + &cond) == 1) { + found = this; + break; + } + } + + if (!found) found = null_case; + + do_null_case: + talloc_free(data.ptr); + modcall_child(request, component, depth + 1, entry, found, &result, true); + MOD_LOG_CLOSE_BRACE; + goto calculate_result; + } /* MOD_SWITCH */ +#endif + + if ((c->type == MOD_LOAD_BALANCE) || + (c->type == MOD_REDUNDANT_LOAD_BALANCE)) { + uint32_t count = 0; + modcallable *this, *found; + modgroup *g; + + MOD_LOG_OPEN_BRACE; + + g = mod_callabletogroup(c); + found = g->children; + rad_assert(g->children != NULL); + + /* + * Choose a child at random. + */ + for (this = g->children; this; this = this->next) { + count++; + + if ((count * (fr_rand() & 0xffff)) < (uint32_t) 0x10000) { + found = this; + } + } + + 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, this, + &result, false); + if (this->actions[result] == MOD_ACTION_RETURN) { + priority = -1; + break; + } + + this = this->next; + if (!this) this = g->children; + } while (this != found); + } + MOD_LOG_CLOSE_BRACE; + goto calculate_result; + } /* MOD_LOAD_BALANCE */ + + /* + * 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; + + if (server == mr->ref_name) { + RWDEBUG("Suppressing recursive call to server %s", server); + goto next_sibling; + } + + 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 */ + + /* + * 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]; + + if (!mx->exec) { + radius_xlat(buffer, sizeof(buffer), request, mx->xlat_name, NULL, NULL); + } else { + RDEBUG("`%s`", mx->xlat_name); + radius_exec_program(request, NULL, 0, NULL, request, mx->xlat_name, request->packet->vps, + false, true, EXEC_TIMEOUT); + } + + goto next_sibling; + } /* MOD_XLAT */ + + /* + * Add new module types here. + */ + +calculate_result: +#if 0 + RDEBUG("(%s, %d) ? (%s, %d)", + fr_int2str(mod_rcode_table, result, ""), + priority, + fr_int2str(mod_rcode_table, entry->result, ""), + entry->priority); +#endif + + + rad_assert(result != RLM_MODULE_UNKNOWN); + + /* + * The child's action says return. Do so. + */ + if ((c->actions[result] == MOD_ACTION_RETURN) && + (priority <= 0)) { + entry->result = result; + goto finish; + } + + /* + * If "reject", break out of the loop and return + * reject. + */ + if (c->actions[result] == MOD_ACTION_REJECT) { + entry->result = RLM_MODULE_REJECT; + goto finish; + } + + /* + * 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; + } + +#ifdef WITH_UNLANG + /* + * 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) goto finish; +#endif + + /* + * If we've been told to stop processing + * it, do so. + */ + if (entry->unwind == MOD_BREAK) { + RDEBUG2("# unwind to enclosing foreach"); + goto finish; + } + + if (entry->unwind == MOD_RETURN) { + goto finish; + } + +next_sibling: + if (do_next_sibling) { + entry->c = entry->c->next; + + if (entry->c) goto redo; + } + +finish: + /* + * And we're done! + */ + REXDENT(); + return true; +} + + +/** Call a module, iteratively, with a local stack, rather than recursively + * + * What did Paul Graham say about Lisp...? + */ +int modcall(rlm_components_t component, modcallable *c, REQUEST *request) +{ + modcall_stack_entry_t stack[MODCALL_STACK_MAX]; + +#ifndef NDEBUG + memset(stack, 0, sizeof(stack)); +#endif + /* + * Set up the initial stack frame. + */ + stack[0].c = c; + stack[0].result = default_component_results[component]; + stack[0].priority = 0; + stack[0].unwind = 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 char const *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", + unlang_keyword[c->type]); + for(p = g->children;p;p = p->next) + dump_mc(p, indent+1); + } /* else ignore it for now */ + + for(i = 0; i"), + action2str(c->actions[i])); + } + + DEBUG("%.*s}", indent, "\t\t\t\t\t\t\t\t\t\t\t"); +} + +static void dump_tree(rlm_components_t comp, modcallable *c) +{ + DEBUG("[%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 + * behaves like the code from the old module_*() function. redundant{} + * are based on my guesses of what they will be used for. --Pac. */ +static const int +defaultactions[MOD_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 */ + } + }, + /* 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 */ + } + }, + /* 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 */ + } + }, + /* 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 */ + MOD_ACTION_RETURN, /* ok */ + MOD_ACTION_RETURN, /* handled */ + 1, /* invalid */ + 1, /* userlock */ + 1, /* notfound */ + 2, /* noop */ + 4 /* 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 */ + } + }, + /* pre-proxy */ + { + /* 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 */ + } + }, + /* post-proxy */ + { + /* 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 */ + } + }, + /* post-auth */ + { + /* 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 */ + } + } +#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 */ + } + }, + /* 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 */ + } + } +#endif +}; + +static const int authtype_actions[GROUPTYPE_COUNT][RLM_MODULE_NUMCODES] = +{ + /* group */ + { + MOD_ACTION_RETURN, /* reject */ + MOD_ACTION_RETURN, /* fail */ + 4, /* ok */ + MOD_ACTION_RETURN, /* handled */ + MOD_ACTION_RETURN, /* invalid */ + MOD_ACTION_RETURN, /* userlock */ + 1, /* notfound */ + 2, /* 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 */ + } +}; + +/** Validate and fixup a map that's part of an update section. + * + * @param map to validate. + * @param ctx data to pass to fixup function (currently unused). + * @return 0 if valid else -1. + */ +int modcall_fixup_update(vp_map_t *map, UNUSED void *ctx) +{ + CONF_PAIR *cp = cf_item_to_pair(map->ci); + /* + * Anal-retentive checks. + */ + if (DEBUG_ENABLED3) { + if ((map->lhs->type == TMPL_TYPE_ATTR) && (map->lhs->name[0] != '&')) { + WARN("%s[%d]: Please change attribute reference to '&%s %s ...'", + cf_pair_filename(cp), cf_pair_lineno(cp), + map->lhs->name, fr_int2str(fr_tokens, map->op, "")); + } + + if ((map->rhs->type == TMPL_TYPE_ATTR) && (map->rhs->name[0] != '&')) { + WARN("%s[%d]: Please change attribute reference to '... %s &%s'", + cf_pair_filename(cp), cf_pair_lineno(cp), + fr_int2str(fr_tokens, map->op, ""), map->rhs->name); + } + } + + /* + * Values used by unary operators should be literal ANY + * + * We then free the template and alloc a NULL one instead. + */ + if (map->op == T_OP_CMP_FALSE) { + if ((map->rhs->type != TMPL_TYPE_LITERAL) || (strcmp(map->rhs->name, "ANY") != 0)) { + WARN("%s[%d] Wildcard deletion MUST use '!* ANY'", + cf_pair_filename(cp), cf_pair_lineno(cp)); + } + + TALLOC_FREE(map->rhs); + + map->rhs = tmpl_alloc(map, TMPL_TYPE_NULL, NULL, 0); + } + + /* + * Lots of sanity checks for insane people... + */ + + /* + * What exactly where you expecting to happen here? + */ + if ((map->lhs->type == TMPL_TYPE_ATTR) && + (map->rhs->type == TMPL_TYPE_LIST)) { + cf_log_err(map->ci, "Can't copy list into an attribute"); + return -1; + } + + /* + * Depending on the attribute type, some operators are disallowed. + */ + if ((map->lhs->type == TMPL_TYPE_ATTR) && (!fr_assignment_op[map->op] && !fr_equality_op[map->op])) { + cf_log_err(map->ci, "Invalid operator \"%s\" in update section. " + "Only assignment or filter operators are allowed", + fr_int2str(fr_tokens, map->op, "")); + return -1; + } + + if (map->lhs->type == TMPL_TYPE_LIST) { + /* + * Can't copy an xlat expansion or literal into a list, + * we don't know what type of attribute we'd need + * to create. + * + * The only exception is where were using a unary + * operator like !*. + */ + if (map->op != T_OP_CMP_FALSE) switch (map->rhs->type) { + case TMPL_TYPE_XLAT: + case TMPL_TYPE_LITERAL: + cf_log_err(map->ci, "Can't copy value into list (we don't know which attribute to create)"); + return -1; + + default: + break; + } + + /* + * Only += and :=, and !*, and ^= operators are supported + * for lists. + */ + switch (map->op) { + case T_OP_CMP_FALSE: + break; + + case T_OP_ADD: + if ((map->rhs->type != TMPL_TYPE_LIST) && + (map->rhs->type != TMPL_TYPE_EXEC)) { + cf_log_err(map->ci, "Invalid source for list assignment '%s += ...'", map->lhs->name); + return -1; + } + break; + + case T_OP_SET: + if (map->rhs->type == TMPL_TYPE_EXEC) { + WARN("%s[%d]: Please change ':=' to '=' for list assignment", + cf_pair_filename(cp), cf_pair_lineno(cp)); + } + + if (map->rhs->type != TMPL_TYPE_LIST) { + cf_log_err(map->ci, "Invalid source for list assignment '%s := ...'", map->lhs->name); + return -1; + } + break; + + case T_OP_EQ: + if (map->rhs->type != TMPL_TYPE_EXEC) { + cf_log_err(map->ci, "Invalid source for list assignment '%s = ...'", map->lhs->name); + return -1; + } + break; + + case T_OP_PREPEND: + if ((map->rhs->type != TMPL_TYPE_LIST) && + (map->rhs->type != TMPL_TYPE_EXEC)) { + cf_log_err(map->ci, "Invalid source for list assignment '%s ^= ...'", map->lhs->name); + return -1; + } + break; + + default: + cf_log_err(map->ci, "Operator \"%s\" not allowed for list assignment", + fr_int2str(fr_tokens, map->op, "")); + return -1; + } + } + + /* + * If the map has a unary operator there's no further + * processing we need to, as RHS is unused. + */ + if (map->op == T_OP_CMP_FALSE) return 0; + + /* + * If LHS is an attribute, and RHS is a literal, we can + * preparse the information into a TMPL_TYPE_DATA. + * + * Unless it's a unary operator in which case we + * ignore map->rhs. + */ + if ((map->lhs->type == TMPL_TYPE_ATTR) && (map->rhs->type == TMPL_TYPE_LITERAL)) { + /* + * It's a literal string, just copy it. + * Don't escape anything. + */ + if (!cf_new_escape && + (map->lhs->tmpl_da->type == PW_TYPE_STRING) && + (cf_pair_value_type(cp) == T_SINGLE_QUOTED_STRING)) { + tmpl_cast_in_place_str(map->rhs); + + } else { + /* + * RHS is hex, try to parse it as + * type-specific data. + */ + if (map->lhs->auto_converted && + (map->rhs->name[0] == '0') && (map->rhs->name[1] == 'x') && + (map->rhs->len > 2) && ((map->rhs->len & 0x01) == 0)) { + vp_tmpl_t *vpt = map->rhs; + map->rhs = NULL; + + if (!map_cast_from_hex(map, T_BARE_WORD, vpt->name)) { + map->rhs = vpt; + cf_log_err(map->ci, "Cannot parse RHS hex as the data type of the attribute %s", map->lhs->tmpl_da->name); + return -1; + } + talloc_free(vpt); + + } else if (tmpl_cast_in_place(map->rhs, map->lhs->tmpl_da->type, map->lhs->tmpl_da) < 0) { + cf_log_err(map->ci, "%s", fr_strerror()); + return -1; + } + + /* + * Fixup LHS da if it doesn't match the type + * of the RHS. + */ + if (map->lhs->tmpl_da->type != map->rhs->tmpl_data_type) { + DICT_ATTR const *da; + + da = dict_attrbytype(map->lhs->tmpl_da->attr, map->lhs->tmpl_da->vendor, + map->rhs->tmpl_data_type); + if (!da) { + cf_log_err(map->ci, "Cannot find %s variant of attribute \"%s\"", + fr_int2str(dict_attr_types, map->rhs->tmpl_data_type, + ""), map->lhs->tmpl_da->name); + return -1; + } + map->lhs->tmpl_da = da; + } + } + } /* else we can't precompile the data */ + + return 0; +} + + +#ifdef WITH_UNLANG +static modcallable *do_compile_modupdate(modcallable *parent, rlm_components_t component, + CONF_SECTION *cs, char const *name2) +{ + int rcode; + modgroup *g; + modcallable *csingle; + + vp_map_t *head; + + /* + * This looks at cs->name2 to determine which list to update + */ + rcode = map_afrom_cs(&head, cs, PAIR_LIST_REQUEST, PAIR_LIST_REQUEST, modcall_fixup_update, NULL, 128); + if (rcode < 0) return NULL; /* message already printed */ + if (!head) { + cf_log_err_cs(cs, "'update' sections cannot be empty"); + return NULL; + } + + g = talloc_zero(parent, modgroup); + csingle = mod_grouptocallable(g); + + csingle->parent = parent; + csingle->next = NULL; + + if (name2) { + csingle->name = name2; + } else { + csingle->name = ""; + } + 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->map = talloc_steal(g, head); + + return csingle; +} + + +static modcallable *do_compile_modswitch (modcallable *parent, rlm_components_t component, CONF_SECTION *cs) +{ + CONF_ITEM *ci; + FR_TOKEN type; + char const *name2; + bool had_seen_default = false; + modcallable *csingle; + modgroup *g; + ssize_t slen; + vp_tmpl_t *vpt; + + name2 = cf_section_name2(cs); + if (!name2) { + cf_log_err_cs(cs, "You must specify a variable to switch over for 'switch'"); + return NULL; + } + + if (!cf_item_find_next(cs, NULL)) { + cf_log_err_cs(cs, "'switch' statements cannot be empty"); + return NULL; + } + + /* + * Create the template. If we fail, AND it's a bare word + * with &Foo-Bar, it MAY be an attribute defined by a + * module. Allow it for now. The pass2 checks below + * will fix it up. + */ + type = cf_section_name2_type(cs); + slen = tmpl_afrom_str(cs, &vpt, name2, strlen(name2), type, REQUEST_CURRENT, PAIR_LIST_REQUEST, true); + if ((slen < 0) && ((type != T_BARE_WORD) || (name2[0] != '&'))) { + char *spaces, *text; + + fr_canonicalize_error(cs, &spaces, &text, slen, fr_strerror()); + + cf_log_err_cs(cs, "Syntax error"); + cf_log_err_cs(cs, "%s", name2); + cf_log_err_cs(cs, "%s^ %s", spaces, text); + + talloc_free(spaces); + talloc_free(text); + + return NULL; + } + + /* + * Otherwise a NULL vpt may refer to an attribute defined + * by a module. That is checked in pass 2. + */ + + if (vpt->type == TMPL_TYPE_LIST) { + cf_log_err_cs(cs, "Syntax error: Cannot switch over list '%s'", name2); + return NULL; + } + + /* + * Warn about confusing things. + */ + if ((vpt->type == TMPL_TYPE_ATTR) && (*name2 != '&')) { + WARN("%s[%d]: Please change \"switch %s\" to \"switch &%s\"", + cf_section_filename(cs), cf_section_lineno(cs), + name2, name2); + } + + /* + * 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; + char const *name1; + + if (!cf_item_is_section(ci)) { + if (!cf_item_is_pair(ci)) continue; + + cf_log_err(ci, "\"switch\" sections can only have \"case\" subsections"); + talloc_free(vpt); + return NULL; + } + + subcs = cf_item_to_section(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"); + talloc_free(vpt); + return NULL; + } + + name2 = cf_section_name2(subcs); + if (!name2) { + if (!had_seen_default) { + had_seen_default = true; + continue; + } + + cf_log_err(ci, "Cannot have two 'default' case statements"); + talloc_free(vpt); + return NULL; + } + } + + csingle = do_compile_modgroup(parent, component, cs, + GROUPTYPE_SIMPLE, + GROUPTYPE_SIMPLE, + MOD_SWITCH); + if (!csingle) { + talloc_free(vpt); + return NULL; + } + + g = mod_callabletogroup(csingle); + g->vpt = talloc_steal(g, vpt); + + return csingle; +} + +static modcallable *do_compile_modcase(modcallable *parent, rlm_components_t component, CONF_SECTION *cs) +{ + int i; + char const *name2; + modcallable *csingle; + modgroup *g; + vp_tmpl_t *vpt; + + if (!parent || (parent->type != MOD_SWITCH)) { + cf_log_err_cs(cs, "\"case\" statements may only appear within a \"switch\" section"); + return NULL; + } + + /* + * case THING means "match THING" + * case means "match anything" + */ + name2 = cf_section_name2(cs); + if (name2) { + ssize_t slen; + FR_TOKEN type; + + type = cf_section_name2_type(cs); + + slen = tmpl_afrom_str(cs, &vpt, name2, strlen(name2), type, REQUEST_CURRENT, PAIR_LIST_REQUEST, true); + if ((slen < 0) && ((type != T_BARE_WORD) || (name2[0] != '&'))) { + char *spaces, *text; + + fr_canonicalize_error(cs, &spaces, &text, slen, fr_strerror()); + + cf_log_err_cs(cs, "Syntax error"); + cf_log_err_cs(cs, "%s", name2); + cf_log_err_cs(cs, "%s^ %s", spaces, text); + + talloc_free(spaces); + talloc_free(text); + + return NULL; + } + + if (vpt->type == TMPL_TYPE_LIST) { + cf_log_err_cs(cs, "Syntax error: Cannot match list '%s'", name2); + return NULL; + } + + /* + * Otherwise a NULL vpt may refer to an attribute defined + * by a module. That is checked in pass 2. + */ + + } else { + vpt = NULL; + } + + csingle = do_compile_modgroup(parent, component, cs, + GROUPTYPE_SIMPLE, + GROUPTYPE_SIMPLE, + MOD_CASE); + if (!csingle) { + talloc_free(vpt); + return NULL; + } + + /* + * The interpretor expects this to be NULL for the + * default case. do_compile_modgroup sets it to name2, + * unless name2 is NULL, in which case it sets it to name1. + */ + csingle->name = name2; + + g = mod_callabletogroup(csingle); + g->vpt = talloc_steal(g, vpt); + + /* + * 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; +} + +static modcallable *do_compile_modforeach(modcallable *parent, + rlm_components_t component, CONF_SECTION *cs) +{ + FR_TOKEN type; + char const *name2; + modcallable *csingle; + modgroup *g; + ssize_t slen; + vp_tmpl_t *vpt; + + name2 = cf_section_name2(cs); + if (!name2) { + cf_log_err_cs(cs, + "You must specify an attribute to loop over in 'foreach'"); + return NULL; + } + + if (!cf_item_find_next(cs, NULL)) { + cf_log_err_cs(cs, "'foreach' blocks cannot be empty"); + return NULL; + } + + /* + * Create the template. If we fail, AND it's a bare word + * with &Foo-Bar, it MAY be an attribute defined by a + * module. Allow it for now. The pass2 checks below + * will fix it up. + */ + type = cf_section_name2_type(cs); + slen = tmpl_afrom_str(cs, &vpt, name2, strlen(name2), type, REQUEST_CURRENT, PAIR_LIST_REQUEST, true); + if ((slen < 0) && ((type != T_BARE_WORD) || (name2[0] != '&'))) { + char *spaces, *text; + + fr_canonicalize_error(cs, &spaces, &text, slen, fr_strerror()); + + cf_log_err_cs(cs, "Syntax error"); + cf_log_err_cs(cs, "%s", name2); + cf_log_err_cs(cs, "%s^ %s", spaces, text); + + talloc_free(spaces); + talloc_free(text); + + return NULL; + } + + /* + * If we don't have a negative return code, we must have a vpt + * (mostly to quiet coverity). + */ + rad_assert(vpt); + + if ((vpt->type != TMPL_TYPE_ATTR) && (vpt->type != TMPL_TYPE_LIST)) { + cf_log_err_cs(cs, "MUST use attribute or list reference in 'foreach'"); + return NULL; + } + + /* + * Fix up the template to iterate over all instances of + * the attribute. In a perfect consistent world, users would do + * foreach &attr[*], but that's taking the consistency thing a bit far. + */ + vpt->tmpl_num = NUM_ALL; + + csingle = do_compile_modgroup(parent, component, cs, + GROUPTYPE_SIMPLE, GROUPTYPE_SIMPLE, + MOD_FOREACH); + + if (!csingle) { + talloc_free(vpt); + return NULL; + } + + g = mod_callabletogroup(csingle); + g->vpt = vpt; + + return csingle; +} + +static modcallable *do_compile_modbreak(modcallable *parent, + rlm_components_t component, CONF_ITEM const *ci) +{ + CONF_SECTION const *cs = NULL; + + for (cs = cf_item_parent(ci); + cs != NULL; + cs = cf_item_parent(cf_section_to_item(cs))) { + if (strcmp(cf_section_name1(cs), "foreach") == 0) { + break; + } + } + + if (!cs) { + cf_log_err(ci, "'break' can only be used in a 'foreach' section"); + return NULL; + } + + return do_compile_modgroup(parent, component, NULL, + GROUPTYPE_SIMPLE, GROUPTYPE_SIMPLE, + MOD_BREAK); +} +#endif + +static modcallable *do_compile_modserver(modcallable *parent, + rlm_components_t component, CONF_ITEM *ci, + char const *name, + CONF_SECTION *cs, + char const *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 = talloc_zero(parent, modref); + + 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, + rlm_components_t component, char const *fmt) +{ + modcallable *csingle; + modxlat *mx; + + mx = talloc_zero(parent, modxlat); + + 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 = talloc_strdup(mx, fmt); + if (!mx->xlat_name) { + talloc_free(mx); + return NULL; + } + + 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, char const *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_item_to_section(ci); + char const *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_item_to_pair(ci); + if (cf_pair_value(cp) != NULL) { + cf_log_err(ci, + "Entry with no value is invalid"); + return 0; + } + } + } + + return 1; +} + +/** Load a named module from "instantiate" or "policy". + * + * If it's "foo.method", look for "foo", and return "method" as the method + * we wish to use, instead of the input component. + * + * @param[out] pcomponent Where to write the method we found, if any. If no method is specified + * will be set to MOD_COUNT. + * @param[in] real_name Complete name string e.g. foo.authorize. + * @param[in] virtual_name Virtual module name e.g. foo. + * @param[in] method_name Method override (may be NULL) or the method name e.g. authorize. + * @return the CONF_SECTION specifying the virtual module. + */ +static CONF_SECTION *virtual_module_find_cs(rlm_components_t *pcomponent, + char const *real_name, char const *virtual_name, char const *method_name) +{ + CONF_SECTION *cs, *subcs; + rlm_components_t method = *pcomponent; + char buffer[256]; + + /* + * Turn the method name into a method enum. + */ + if (method_name) { + rlm_components_t i; + + for (i = MOD_AUTHENTICATE; i < MOD_COUNT; i++) { + if (strcmp(comp2str[i], method_name) == 0) break; + } + + if (i != MOD_COUNT) { + method = i; + } else { + method_name = NULL; + virtual_name = real_name; + } + } + + /* + * Look for "foo" in the "instantiate" section. If we + * find it, AND there's no method name, we've found the + * right thing. + * + * Return it to the caller, with the updated method. + */ + cs = cf_section_find("instantiate"); + if (cs) { + /* + * Found "foo". Load it as "foo", or "foo.method". + */ + subcs = cf_section_sub_find_name2(cs, NULL, virtual_name); + if (subcs) { + *pcomponent = method; + return subcs; + } + } + + /* + * Look for it in "policy". + * + * If there's no policy section, we can't do anything else. + */ + cs = cf_section_find("policy"); + if (!cs) return NULL; + + /* + * "foo.authorize" means "load policy "foo" as method "authorize". + * + * And bail out if there's no policy "foo". + */ + if (method_name) { + subcs = cf_section_sub_find_name2(cs, NULL, virtual_name); + if (subcs) *pcomponent = method; + + return subcs; + } + + /* + * "foo" means "look for foo.component" first, to allow + * method overrides. If that's not found, just look for + * a policy "foo". + * + */ + snprintf(buffer, sizeof(buffer), "%s.%s", + virtual_name, comp2str[method]); + subcs = cf_section_sub_find_name2(cs, NULL, buffer); + if (subcs) return subcs; + + return cf_section_sub_find_name2(cs, NULL, virtual_name); +} + + +/* + * Compile one entry of a module call. + */ +static modcallable *do_compile_modsingle(modcallable *parent, + rlm_components_t component, CONF_ITEM *ci, + int grouptype, + char const **modname) +{ + char const *modrefname, *p; + modsingle *single; + modcallable *csingle; + module_instance_t *this; + CONF_SECTION *cs, *subcs, *modules; + CONF_SECTION *loop; + char const *realname; + rlm_components_t method = component; + + if (cf_item_is_section(ci)) { + char const *name2; + + cs = cf_item_to_section(ci); + modrefname = cf_section_name1(cs); + name2 = cf_section_name2(cs); + if (!name2) name2 = ""; + + /* + * group{}, redundant{}, or append{} may appear + * where a single module instance was expected. + * In that case, we hand it off to + * compile_modgroup + */ + if (strcmp(modrefname, "group") == 0) { + *modname = name2; + return do_compile_modgroup(parent, component, cs, + GROUPTYPE_SIMPLE, + grouptype, MOD_GROUP); + + } 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, MOD_GROUP); + + } else if (strcmp(modrefname, "load-balance") == 0) { + *modname = name2; + + if (!all_children_are_modules(cs, modrefname)) { + return NULL; + } + + return do_compile_modgroup(parent, component, cs, + GROUPTYPE_SIMPLE, + grouptype, MOD_LOAD_BALANCE); + + } else if (strcmp(modrefname, "redundant-load-balance") == 0) { + *modname = name2; + + if (!all_children_are_modules(cs, modrefname)) { + return NULL; + } + + return do_compile_modgroup(parent, component, cs, + GROUPTYPE_REDUNDANT, + grouptype, MOD_REDUNDANT_LOAD_BALANCE); + +#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, MOD_IF); + if (!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; + return do_compile_modgroup(parent, component, cs, + GROUPTYPE_SIMPLE, + grouptype, MOD_ELSIF); + + } 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; + return do_compile_modgroup(parent, component, cs, + GROUPTYPE_SIMPLE, + grouptype, MOD_ELSE); + + } else if (strcmp(modrefname, "update") == 0) { + *modname = name2; + + return do_compile_modupdate(parent, component, cs, + name2); + + } else if (strcmp(modrefname, "switch") == 0) { + *modname = name2; + + return do_compile_modswitch (parent, component, cs); + + } else if (strcmp(modrefname, "case") == 0) { + *modname = name2; + + return do_compile_modcase(parent, component, cs); + + } else if (strcmp(modrefname, "foreach") == 0) { + *modname = name2; + + return do_compile_modforeach(parent, component, cs); + +#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_PAIR *cp = cf_item_to_pair(ci); + modrefname = cf_pair_attr(cp); + + /* + * Actions (ok = 1), etc. are orthogonal 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; + } + + /* + * In-place xlat's via %{...}. + * + * This should really be removed from the server. + */ + if (((modrefname[0] == '%') && (modrefname[1] == '{')) || + (modrefname[0] == '`')) { + return do_compile_modxlat(parent, component, + modrefname); + } + } + +#ifdef WITH_UNLANG + /* + * These can't be over-ridden. + */ + if (strcmp(modrefname, "break") == 0) { + if (!cf_item_is_pair(ci)) { + cf_log_err(ci, "Invalid use of 'break' as section name."); + return NULL; + } + + return do_compile_modbreak(parent, component, ci); + } + + if (strcmp(modrefname, "return") == 0) { + if (!cf_item_is_pair(ci)) { + cf_log_err(ci, "Invalid use of 'return' as section name."); + return NULL; + } + + return do_compile_modgroup(parent, component, NULL, + GROUPTYPE_SIMPLE, GROUPTYPE_SIMPLE, + MOD_RETURN); + } +#endif + + /* + * Run a virtual server. This is really terrible and + * should be deleted. + */ + if (strncmp(modrefname, "server[", 7) == 0) { + char buffer[256]; + + if (!cf_item_is_pair(ci)) { + cf_log_err(ci, "Invalid syntax"); + return NULL; + } + + 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; + } + + buffer[p - buffer] = '\0'; + + cs = cf_section_sub_find_name2(NULL, "server", buffer); + if (!cs) { + cf_log_err(ci, "No such server \"%s\".", buffer); + return NULL; + } + + /* + * Ignore stupid attempts to over-ride the return + * code. + */ + return do_compile_modserver(parent, component, ci, + modrefname, cs, buffer); + } + + /* + * We now have a name. It can be one of two forms. A + * bare module name, or a section named for the module, + * with over-rides for the return codes. + * + * The name can refer to a real module, in the "modules" + * section. In that case, the name will be either the + * first or second name of the sub-section of "modules". + * + * Or, the name can refer to a policy, in the "policy" + * section. In that case, the name will be first name of + * the sub-section of "policy". Unless it's a "redudant" + * block... + * + * Or, the name can refer to a "module.method", in which + * case we're calling a different method than normal for + * this section. + * + * Or, the name can refer to a virtual module, in the + * "instantiate" section. In that case, the name will be + * the first of the sub-section of "instantiate". Unless + * it's a "redudant" block... + * + * We try these in sequence, from the bottom up. This is + * so that things in "instantiate" and "policy" can + * over-ride calls to real modules. + */ + + + /* + * Try: + * + * instantiate { ... name { ...} ... } + * instantiate { ... name.method { ...} ... } + * policy { ... name { .. } .. } + * policy { ... name.method { .. } .. } + * + * The only difference between things in "instantiate" + * and "policy" is that "instantiate" will cause modules + * to be instantiated in a particular order. + */ + subcs = NULL; + p = strrchr(modrefname, '.'); + if (!p) { + subcs = virtual_module_find_cs(&method, modrefname, modrefname, NULL); + } else { + char buffer[256]; + + strlcpy(buffer, modrefname, sizeof(buffer)); + buffer[p - modrefname] = '\0'; + + subcs = virtual_module_find_cs(&method, modrefname, buffer, buffer + (p - modrefname) + 1); + } + + /* + * Check that we're not creating a loop. We may + * be compiling an "sql" module reference inside + * of an "sql" policy. If so, we allow the + * second "sql" to refer to the module. + */ + for (loop = cf_item_parent(ci); + loop && subcs; + loop = cf_item_parent(cf_section_to_item(loop))) { + if (loop == subcs) { + subcs = NULL; + } + } + + /* + * We've found the relevant entry. It MUST be a + * sub-section. + * + * However, it can be a "redundant" block, or just a + * section name. + */ + if (subcs) { + /* + * modules.c takes care of ensuring that this is: + * + * group foo { ... + * load-balance foo { ... + * redundant foo { ... + * redundant-load-balance foo { ... + * + * We can just recurs to compile the section as + * if it was found here. + */ + if (cf_section_name2(subcs)) { + csingle = do_compile_modsingle(parent, + method, + cf_section_to_item(subcs), + grouptype, + modname); + } else { + /* + * We have: + * + * foo { ... + * + * So we compile it like it was: + * + * group foo { ... + */ + csingle = do_compile_modgroup(parent, + method, + subcs, + GROUPTYPE_SIMPLE, + grouptype, MOD_GROUP); + } + + /* + * Return the compiled thing if we can. + */ + if (!csingle) return NULL; + if (cf_item_is_pair(ci)) return csingle; + + /* + * Else we have a reference to a policy, and that reference + * over-rides the return codes for the policy! + */ + goto action_override; + } + + /* + * Not a virtual module. It must be a real module. + */ + modules = cf_section_find("modules"); + this = NULL; + realname = modrefname; + + if (modules) { + /* + * Try to load the optional module. + */ + if (realname[0] == '-') realname++; + + /* + * As of v3, the "modules" section contains + * modules we use. Configuration for other + * modules belongs in raddb/mods-available/, + * which isn't loaded into the "modules" section. + */ + this = module_instantiate_method(modules, realname, &method); + if (this) goto allocate_csingle; + + /* + * We were asked to MAYBE load it and it + * doesn't exist. Return a soft error. + */ + if (realname != modrefname) { + *modname = modrefname; + return NULL; + } + } + + /* + * Can't de-reference it to anything. Ugh. + */ + *modname = NULL; + cf_log_err(ci, "Failed to find \"%s\" as a module or policy.", modrefname); + cf_log_err(ci, "Please verify that the configuration exists in %s/mods-enabled/%s.", get_radius_dir(), modrefname); + return NULL; + + /* + * We know it's all OK, allocate the structures, and fill + * them in. + */ +allocate_csingle: + /* + * Check if the module in question has the necessary + * component. + */ + if (!this->entry->module->methods[method]) { + cf_log_err(ci, "\"%s\" modules aren't allowed in '%s' sections -- they have no such method.", this->entry->module->name, + comp2str[method]); + return NULL; + } + + single = talloc_zero(parent, modsingle); + single->modinst = this; + *modname = this->entry->module->name; + + csingle = mod_singletocallable(single); + csingle->parent = parent; + csingle->next = NULL; + if (!parent || (component != MOD_AUTHENTICATE)) { + memcpy(csingle->actions, defaultactions[component][grouptype], + sizeof csingle->actions); + } else { /* inside Auth-Type has different rules */ + memcpy(csingle->actions, authtype_actions[grouptype], + sizeof csingle->actions); + } + rad_assert(modrefname != NULL); + csingle->name = realname; + csingle->type = MOD_SINGLE; + csingle->method = method; + +action_override: + /* + * Over-ride the default return codes of the module. + */ + if (cf_item_is_section(ci)) { + CONF_ITEM *csi; + + cs = cf_item_to_section(ci); + for (csi=cf_item_find_next(cs, NULL); + csi != NULL; + csi=cf_item_find_next(cs, csi)) { + + if (cf_item_is_section(csi)) { + cf_log_err(csi, "Subsection of module instance call not allowed"); + talloc_free(csingle); + return NULL; + } + + if (!cf_item_is_pair(csi)) continue; + + if (!compile_action(csingle, cf_item_to_pair(csi))) { + talloc_free(csingle); + return NULL; + } + } + } + + return csingle; +} + +modcallable *compile_modsingle(TALLOC_CTX *ctx, + modcallable **parent, + rlm_components_t component, CONF_ITEM *ci, + char const **modname) +{ + modcallable *ret; + + if (!*parent) { + modcallable *c; + modgroup *g; + CONF_SECTION *parentcs; + + g = talloc_zero(ctx, modgroup); + 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)); + + parentcs = cf_item_parent(ci); + c->name = cf_section_name2(parentcs); + if (!c->name) { + c->name = cf_section_name1(parentcs); + } + + c->type = MOD_GROUP; + c->method = component; + g->children = NULL; + + *parent = mod_grouptocallable(g); + } + + ret = do_compile_modsingle(*parent, component, ci, + GROUPTYPE_SIMPLE, + modname); + dump_tree(component, ret); + return ret; +} + + +/* + * Internal compile group code. + */ +static modcallable *do_compile_modgroup(modcallable *parent, + rlm_components_t component, CONF_SECTION *cs, + int grouptype, int parentgrouptype, int mod_type) +{ + int i; + modgroup *g; + modcallable *c; + CONF_ITEM *ci; + + g = talloc_zero(parent, modgroup); + g->grouptype = grouptype; + g->children = NULL; + g->cs = cs; + + c = mod_grouptocallable(g); + c->parent = parent; + c->type = mod_type; + c->next = NULL; + memset(c->actions, 0, sizeof(c->actions)); + + if (!cs) { /* only for "break" and "return" */ + c->name = ""; + goto set_codes; + } + + /* + * Remember the name for printing, etc. + * + * FIXME: We may also want to put the names into a + * rbtree, so that groups can reference each other... + */ + c->name = cf_section_name2(cs); + if (!c->name) { + c->name = cf_section_name1(cs); + if ((strcmp(c->name, "group") == 0) || + (strcmp(c->name, "redundant") == 0)) { + c->name = ""; + } else if (c->type == MOD_GROUP) { + c->type = MOD_POLICY; + } + } + +#ifdef WITH_UNLANG + /* + * Do load-time optimizations + */ + if ((c->type == MOD_IF) || (c->type == MOD_ELSIF) || (c->type == MOD_ELSE)) { + modgroup *f, *p; + + rad_assert(parent != NULL); + + if (c->type == MOD_IF) { + g->cond = cf_data_find(g->cs, "if"); + rad_assert(g->cond != NULL); + + check_if: + if (g->cond->type == COND_TYPE_FALSE) { + INFO(" # Skipping contents of '%s' as it is always 'false' -- %s:%d", + unlang_keyword[g->mc.type], + cf_section_filename(g->cs), cf_section_lineno(g->cs)); + goto set_codes; + } + + } else if (c->type == MOD_ELSIF) { + + g->cond = cf_data_find(g->cs, "if"); + rad_assert(g->cond != NULL); + + rad_assert(parent != NULL); + p = mod_callabletogroup(parent); + + if (!p->tail) goto elsif_fail; + + /* + * We're in the process of compiling the + * section, so the parent's tail is the + * previous "if" statement. + */ + f = mod_callabletogroup(p->tail); + if ((f->mc.type != MOD_IF) && + (f->mc.type != MOD_ELSIF)) { + elsif_fail: + cf_log_err_cs(g->cs, "Invalid location for 'elsif'. There is no preceding 'if' statement"); + talloc_free(g); + return NULL; + } + + /* + * If we took the previous condition, we + * don't need to take this one. + * + * We reset our condition to 'true', so + * that subsequent sections can check + * that they don't need to be executed. + */ + if (f->cond->type == COND_TYPE_TRUE) { + skip_true: + INFO(" # Skipping contents of '%s' as previous '%s' is always 'true' -- %s:%d", + unlang_keyword[g->mc.type], + unlang_keyword[f->mc.type], + cf_section_filename(g->cs), cf_section_lineno(g->cs)); + g->cond = f->cond; + goto set_codes; + } + goto check_if; + + } else { + rad_assert(c->type == MOD_ELSE); + + rad_assert(parent != NULL); + p = mod_callabletogroup(parent); + + if (!p->tail) goto else_fail; + + f = mod_callabletogroup(p->tail); + if ((f->mc.type != MOD_IF) && + (f->mc.type != MOD_ELSIF)) { + else_fail: + cf_log_err_cs(g->cs, "Invalid location for 'else'. There is no preceding 'if' statement"); + talloc_free(g); + return NULL; + } + + /* + * If we took the previous condition, we + * don't need to take this one. + */ + if (f->cond->type == COND_TYPE_TRUE) goto skip_true; + } + + /* + * Else we need to compile this section + */ + } +#endif + + /* + * Loop over the children of this group. + */ + for (ci=cf_item_find_next(cs, NULL); + ci != NULL; + ci=cf_item_find_next(cs, ci)) { + + /* + * Sections are references to other groups, or + * to modules with updated return codes. + */ + if (cf_item_is_section(ci)) { + char const *junk = NULL; + modcallable *single; + CONF_SECTION *subcs = cf_item_to_section(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)); + talloc_free(c); + return NULL; + } + add_child(g, single); + + } else if (!cf_item_is_pair(ci)) { /* CONF_DATA */ + continue; + + } else { + char const *attr, *value; + CONF_PAIR *cp = cf_item_to_pair(ci); + + attr = cf_pair_attr(cp); + value = cf_pair_value(cp); + + /* + * A CONF_PAIR is either a module + * instance with no actions + * specified ... + */ + if (!value) { + modcallable *single; + char const *junk = NULL; + + single = do_compile_modsingle(c, + component, + ci, + grouptype, + &junk); + if (!single) { + if (cf_item_is_pair(ci) && + cf_pair_attr(cf_item_to_pair(ci))[0] == '-') { + continue; + } + + cf_log_err(ci, + "Failed to parse \"%s\" entry.", + attr); + talloc_free(c); + return NULL; + } + add_child(g, single); + + /* + * Or a module instance with action. + */ + } else if (!compile_action(c, cp)) { + talloc_free(c); + return NULL; + } /* else it worked */ + } + } + +set_codes: + /* + * 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 != MOD_AUTHENTICATE)) { + c->actions[i] = defaultactions[component][parentgrouptype][i]; + } else { /* inside Auth-Type has different rules */ + c->actions[i] = authtype_actions[parentgrouptype][i]; + } + } + } + + switch (c->type) { + default: + break; + + case MOD_GROUP: + if (grouptype != GROUPTYPE_REDUNDANT) break; + /* FALL-THROUGH */ + + case MOD_LOAD_BALANCE: + case MOD_REDUNDANT_LOAD_BALANCE: + if (!g->children) { + cf_log_err_cs(g->cs, "%s sections cannot be empty", + cf_section_name1(g->cs)); + talloc_free(c); + return NULL; + } + } + + /* + * FIXME: If there are no children, return NULL? + */ + return mod_grouptocallable(g); +} + +modcallable *compile_modgroup(modcallable *parent, + rlm_components_t component, CONF_SECTION *cs) +{ + modcallable *ret = do_compile_modgroup(parent, component, cs, + GROUPTYPE_SIMPLE, + GROUPTYPE_SIMPLE, MOD_GROUP); + + if (rad_debug_lvl > 3) { + modcall_debug(ret, 2); + } + + return ret; +} + +void add_to_modcallable(modcallable *parent, modcallable *this) +{ + modgroup *g; + + rad_assert(this != NULL); + rad_assert(parent != NULL); + + g = mod_callabletogroup(parent); + + add_child(g, this); +} + + +#ifdef WITH_UNLANG +static bool pass2_xlat_compile(CONF_ITEM const *ci, vp_tmpl_t **pvpt, bool convert, + DICT_ATTR const *da) +{ + ssize_t slen; + char *fmt; + char const *error; + xlat_exp_t *head; + vp_tmpl_t *vpt; + + vpt = *pvpt; + + rad_assert(vpt->type == TMPL_TYPE_XLAT); + + fmt = talloc_typed_strdup(vpt, vpt->name); + slen = xlat_tokenize(vpt, fmt, &head, &error); + + if (slen < 0) { + char *spaces, *text; + + fr_canonicalize_error(vpt, &spaces, &text, slen, vpt->name); + + cf_log_err(ci, "Failed parsing expanded string:"); + cf_log_err(ci, "%s", text); + cf_log_err(ci, "%s^ %s", spaces, error); + + talloc_free(spaces); + talloc_free(text); + return false; + } + + /* + * Convert %{Attribute-Name} to &Attribute-Name + */ + if (convert) { + vp_tmpl_t *attr; + + attr = xlat_to_tmpl_attr(talloc_parent(vpt), head); + if (attr) { + /* + * If it's a virtual attribute, leave it + * alone. + */ + if (attr->tmpl_da->flags.virtual) { + talloc_free(attr); + return true; + } + + /* + * If the attribute is of incompatible + * type, leave it alone. + */ + if (da && (da->type != attr->tmpl_da->type)) { + talloc_free(attr); + return true; + } + + if (cf_item_is_pair(ci)) { + CONF_PAIR *cp = cf_item_to_pair(ci); + + WARN("%s[%d]: Please change \"%%{%s}\" to &%s", + cf_pair_filename(cp), cf_pair_lineno(cp), + attr->name, attr->name); + } else { + CONF_SECTION *cs = cf_item_to_section(ci); + + WARN("%s[%d]: Please change \"%%{%s}\" to &%s", + cf_section_filename(cs), cf_section_lineno(cs), + attr->name, attr->name); + } + TALLOC_FREE(*pvpt); + *pvpt = attr; + return true; + } + } + + /* + * Re-write it to be a pre-parsed XLAT structure. + */ + vpt->type = TMPL_TYPE_XLAT_STRUCT; + vpt->tmpl_xlat = head; + + return true; +} + + +#ifdef HAVE_REGEX +static bool pass2_regex_compile(CONF_ITEM const *ci, vp_tmpl_t *vpt) +{ + ssize_t slen; + regex_t *preg; + + rad_assert(vpt->type == TMPL_TYPE_REGEX); + + /* + * It's a dynamic expansion. We can't expand the string, + * but we can pre-parse it as an xlat struct. In that + * case, we convert it to a pre-compiled XLAT. + * + * This is a little more complicated than it needs to be + * because radius_evaluate_map() keys off of the src + * template type, instead of the operators. And, the + * pass2_xlat_compile() function expects to get passed an + * XLAT instead of a REGEX. + */ + if (strchr(vpt->name, '%')) { + vpt->type = TMPL_TYPE_XLAT; + return pass2_xlat_compile(ci, &vpt, false, NULL); + } + + slen = regex_compile(vpt, &preg, vpt->name, vpt->len, + vpt->tmpl_iflag, vpt->tmpl_mflag, true, false); + if (slen <= 0) { + char *spaces, *text; + + fr_canonicalize_error(vpt, &spaces, &text, slen, vpt->name); + + cf_log_err(ci, "Invalid regular expression:"); + cf_log_err(ci, "%s", text); + cf_log_err(ci, "%s^ %s", spaces, fr_strerror()); + + talloc_free(spaces); + talloc_free(text); + + return false; + } + + vpt->type = TMPL_TYPE_REGEX_STRUCT; + vpt->tmpl_preg = preg; + + return true; +} +#endif + +static bool pass2_fixup_undefined(CONF_ITEM const *ci, vp_tmpl_t *vpt) +{ + DICT_ATTR const *da; + + rad_assert(vpt->type == TMPL_TYPE_ATTR_UNDEFINED); + + da = dict_attrbyname(vpt->tmpl_unknown_name); + if (!da) { + cf_log_err(ci, "Unknown attribute '%s'", vpt->tmpl_unknown_name); + return false; + } + + vpt->tmpl_da = da; + vpt->type = TMPL_TYPE_ATTR; + return true; +} + +static bool pass2_callback(void *ctx, fr_cond_t *c) +{ + vp_map_t *map; + vp_tmpl_t *vpt; + + /* + * These don't get optimized. + */ + if ((c->type == COND_TYPE_TRUE) || + (c->type == COND_TYPE_FALSE)) { + return true; + } + + /* + * Call children. + */ + if (c->type == COND_TYPE_CHILD) return pass2_callback(ctx, c->data.child); + + /* + * A few simple checks here. + */ + if (c->type == COND_TYPE_EXISTS) { + if (c->data.vpt->type == TMPL_TYPE_XLAT) { + return pass2_xlat_compile(c->ci, &c->data.vpt, true, NULL); + } + + rad_assert(c->data.vpt->type != TMPL_TYPE_REGEX); + + /* + * The existence check might have been &Foo-Bar, + * where Foo-Bar is defined by a module. + */ + if (c->pass2_fixup == PASS2_FIXUP_ATTR) { + if (!pass2_fixup_undefined(c->ci, c->data.vpt)) return false; + c->pass2_fixup = PASS2_FIXUP_NONE; + } + + /* + * Convert virtual &Attr-Foo to "%{Attr-Foo}" + */ + vpt = c->data.vpt; + if ((vpt->type == TMPL_TYPE_ATTR) && vpt->tmpl_da->flags.virtual) { + vpt->tmpl_xlat = xlat_from_tmpl_attr(vpt, vpt); + vpt->type = TMPL_TYPE_XLAT_STRUCT; + } + + return true; + } + + /* + * And tons of complicated checks. + */ + rad_assert(c->type == COND_TYPE_MAP); + + map = c->data.map; /* shorter */ + + /* + * Auth-Type := foo + * + * Where "foo" is dynamically defined. + */ + if (c->pass2_fixup == PASS2_FIXUP_TYPE) { + if (!dict_valbyname(map->lhs->tmpl_da->attr, + map->lhs->tmpl_da->vendor, + map->rhs->name)) { + cf_log_err(map->ci, "Invalid reference to non-existent %s %s { ... }", + map->lhs->tmpl_da->name, + map->rhs->name); + return false; + } + + /* + * These guys can't have a paircompare fixup applied. + */ + c->pass2_fixup = PASS2_FIXUP_NONE; + return true; + } + + if (c->pass2_fixup == PASS2_FIXUP_ATTR) { + if (map->lhs->type == TMPL_TYPE_ATTR_UNDEFINED) { + if (!pass2_fixup_undefined(map->ci, map->lhs)) return false; + } + + if (map->rhs->type == TMPL_TYPE_ATTR_UNDEFINED) { + if (!pass2_fixup_undefined(map->ci, map->rhs)) return false; + } + + c->pass2_fixup = PASS2_FIXUP_NONE; + } + + /* + * Just in case someone adds a new fixup later. + */ + rad_assert((c->pass2_fixup == PASS2_FIXUP_NONE) || + (c->pass2_fixup == PASS2_PAIRCOMPARE)); + + /* + * Precompile xlat's + */ + if (map->lhs->type == TMPL_TYPE_XLAT) { + /* + * Compile the LHS to an attribute reference only + * if the RHS is a literal. + * + * @todo v3.1: allow anything anywhere. + */ + if (map->rhs->type != TMPL_TYPE_LITERAL) { + if (!pass2_xlat_compile(map->ci, &map->lhs, false, NULL)) { + return false; + } + } else { + if (!pass2_xlat_compile(map->ci, &map->lhs, true, NULL)) { + return false; + } + + /* + * Attribute compared to a literal gets + * the literal cast to the data type of + * the attribute. + * + * The code in parser.c did this for + * + * &Attr == data + * + * But now we've just converted "%{Attr}" + * to &Attr, so we've got to do it again. + */ + if ((map->lhs->type == TMPL_TYPE_ATTR) && + (map->rhs->type == TMPL_TYPE_LITERAL)) { + /* + * RHS is hex, try to parse it as + * type-specific data. + */ + if (map->lhs->auto_converted && + (map->rhs->name[0] == '0') && (map->rhs->name[1] == 'x') && + (map->rhs->len > 2) && ((map->rhs->len & 0x01) == 0)) { + vpt = map->rhs; + map->rhs = NULL; + + if (!map_cast_from_hex(map, T_BARE_WORD, vpt->name)) { + map->rhs = vpt; + cf_log_err(map->ci, "Cannot parse RHS hex as the data type of the attribute %s", map->lhs->tmpl_da->name); + return -1; + } + talloc_free(vpt); + + } else if ((map->rhs->len > 0) || + (map->op != T_OP_CMP_EQ) || + (map->lhs->tmpl_da->type == PW_TYPE_STRING) || + (map->lhs->tmpl_da->type == PW_TYPE_OCTETS)) { + + if (tmpl_cast_in_place(map->rhs, map->lhs->tmpl_da->type, map->lhs->tmpl_da) < 0) { + cf_log_err(map->ci, "Failed to parse data type %s from string: %s", + fr_int2str(dict_attr_types, map->lhs->tmpl_da->type, ""), + map->rhs->name); + return false; + } /* else the cast was successful */ + + } else { /* RHS is empty, it's just a check for empty / non-empty string */ + vpt = talloc_steal(c, map->lhs); + map->lhs = NULL; + talloc_free(c->data.map); + + /* + * "%{Foo}" == '' ---> !Foo + * "%{Foo}" != '' ---> Foo + */ + c->type = COND_TYPE_EXISTS; + c->data.vpt = vpt; + c->negate = !c->negate; + + WARN("%s[%d]: Please change (\"%%{%s}\" %s '') to %c&%s", + cf_section_filename(cf_item_to_section(c->ci)), + cf_section_lineno(cf_item_to_section(c->ci)), + vpt->name, c->negate ? "==" : "!=", + c->negate ? '!' : ' ', vpt->name); + + /* + * No more RHS, so we can't do more optimizations + */ + return true; + } + } + } + } + + if (map->rhs->type == TMPL_TYPE_XLAT) { + /* + * Convert the RHS to an attribute reference only + * if the LHS is an attribute reference, AND is + * of the same type as the RHS. + * + * We can fix this when the code in evaluate.c + * can handle strings on the LHS, and attributes + * on the RHS. For now, the code in parser.c + * forbids this. + */ + if (map->lhs->type == TMPL_TYPE_ATTR) { + DICT_ATTR const *da = c->cast; + + if (!c->cast) da = map->lhs->tmpl_da; + + if (!pass2_xlat_compile(map->ci, &map->rhs, true, da)) { + return false; + } + + } else { + if (!pass2_xlat_compile(map->ci, &map->rhs, false, NULL)) { + return false; + } + } + } + + /* + * Convert bare refs to %{Foreach-Variable-N} + */ + if ((map->lhs->type == TMPL_TYPE_LITERAL) && + (strncmp(map->lhs->name, "Foreach-Variable-", 17) == 0)) { + char *fmt; + ssize_t slen; + + fmt = talloc_asprintf(map->lhs, "%%{%s}", map->lhs->name); + slen = tmpl_afrom_str(map, &vpt, fmt, talloc_array_length(fmt) - 1, + T_DOUBLE_QUOTED_STRING, REQUEST_CURRENT, PAIR_LIST_REQUEST, true); + if (slen < 0) { + char *spaces, *text; + + fr_canonicalize_error(map->ci, &spaces, &text, slen, fr_strerror()); + + cf_log_err(map->ci, "Failed converting %s to xlat", map->lhs->name); + cf_log_err(map->ci, "%s", fmt); + cf_log_err(map->ci, "%s^ %s", spaces, text); + + talloc_free(spaces); + talloc_free(text); + talloc_free(fmt); + + return false; + } + talloc_free(map->lhs); + map->lhs = vpt; + } + +#ifdef HAVE_REGEX + if (map->rhs->type == TMPL_TYPE_REGEX) { + if (!pass2_regex_compile(map->ci, map->rhs)) { + return false; + } + } + rad_assert(map->lhs->type != TMPL_TYPE_REGEX); +#endif + + /* + * Convert &Packet-Type to "%{Packet-Type}", because + * these attributes don't really exist. The code to + * find an attribute reference doesn't work, but the + * xlat code does. + */ + vpt = c->data.map->lhs; + if ((vpt->type == TMPL_TYPE_ATTR) && vpt->tmpl_da->flags.virtual) { + if (!c->cast) c->cast = vpt->tmpl_da; + vpt->tmpl_xlat = xlat_from_tmpl_attr(vpt, vpt); + vpt->type = TMPL_TYPE_XLAT_STRUCT; + } + + /* + * Convert RHS to expansions, too. + */ + vpt = c->data.map->rhs; + if ((vpt->type == TMPL_TYPE_ATTR) && vpt->tmpl_da->flags.virtual) { + vpt->tmpl_xlat = xlat_from_tmpl_attr(vpt, vpt); + vpt->type = TMPL_TYPE_XLAT_STRUCT; + } + + /* + * @todo v3.1: do the same thing for the RHS... + */ + + /* + * Only attributes can have a paircompare registered, and + * they can only be with the current REQUEST, and only + * with the request pairs. + */ + if ((map->lhs->type != TMPL_TYPE_ATTR) || + (map->lhs->tmpl_request != REQUEST_CURRENT) || + (map->lhs->tmpl_list != PAIR_LIST_REQUEST)) { + return true; + } + + if (!radius_find_compare(map->lhs->tmpl_da)) return true; + + if (map->rhs->type == TMPL_TYPE_REGEX) { + cf_log_err(map->ci, "Cannot compare virtual attribute %s via a regex", + map->lhs->name); + return false; + } + + if (c->cast) { + cf_log_err(map->ci, "Cannot cast virtual attribute %s", + map->lhs->name); + return false; + } + + if (map->op != T_OP_CMP_EQ) { + cf_log_err(map->ci, "Must use '==' for comparisons with virtual attribute %s", + map->lhs->name); + return false; + } + + /* + * Mark it as requiring a paircompare() call, instead of + * fr_pair_cmp(). + */ + c->pass2_fixup = PASS2_PAIRCOMPARE; + + return true; +} + + +/* + * Compile the RHS of update sections to xlat_exp_t + */ +static bool modcall_pass2_update(modgroup *g) +{ + vp_map_t *map; + + for (map = g->map; map != NULL; map = map->next) { + if (map->rhs->type == TMPL_TYPE_XLAT) { + rad_assert(map->rhs->tmpl_xlat == NULL); + + /* + * FIXME: compile to attribute && handle + * the conversion in map_to_vp(). + */ + if (!pass2_xlat_compile(map->ci, &map->rhs, false, NULL)) { + return false; + } + } + + rad_assert(map->rhs->type != TMPL_TYPE_REGEX); + + /* + * Deal with undefined attributes now. + */ + if (map->lhs->type == TMPL_TYPE_ATTR_UNDEFINED) { + if (!pass2_fixup_undefined(map->ci, map->lhs)) return false; + } + + if (map->rhs->type == TMPL_TYPE_ATTR_UNDEFINED) { + if (!pass2_fixup_undefined(map->ci, map->rhs)) return false; + } + } + + return true; +} +#endif + +/* + * Do a second-stage pass on compiling the modules. + */ +bool modcall_pass2(modcallable *mc) +{ + ssize_t slen; + char const *name2; + modcallable *c; + modgroup *g; + + for (c = mc; c != NULL; c = c->next) { + switch (c->type) { + default: + rad_assert(0 == 1); + break; + +#ifdef WITH_UNLANG + case MOD_UPDATE: + g = mod_callabletogroup(c); + if (g->done_pass2) goto do_next; + + name2 = cf_section_name2(g->cs); + if (!name2) { + c->debug_name = unlang_keyword[c->type]; + } else { + c->debug_name = talloc_asprintf(c, "update %s", name2); + } + + if (!modcall_pass2_update(g)) { + return false; + } + g->done_pass2 = true; + break; + + case MOD_XLAT: /* @todo: pre-parse xlat's */ + case MOD_REFERENCE: + case MOD_BREAK: + case MOD_RETURN: +#endif + + case MOD_SINGLE: + c->debug_name = c->name; + break; /* do nothing */ + +#ifdef WITH_UNLANG + case MOD_IF: + case MOD_ELSIF: + g = mod_callabletogroup(c); + if (g->done_pass2) goto do_next; + + name2 = cf_section_name2(g->cs); + c->debug_name = talloc_asprintf(c, "%s %s", unlang_keyword[c->type], name2); + + /* + * The compilation code takes care of + * simplifying 'true' and 'false' + * conditions. For others, we have to do + * a second pass to parse && compile + * xlats. + */ + if (!((g->cond->type == COND_TYPE_TRUE) || + (g->cond->type == COND_TYPE_FALSE))) { + if (!fr_condition_walk(g->cond, pass2_callback, NULL)) { + return false; + } + } + + if (!modcall_pass2(g->children)) return false; + g->done_pass2 = true; + break; +#endif + +#ifdef WITH_UNLANG + case MOD_SWITCH: + g = mod_callabletogroup(c); + if (g->done_pass2) goto do_next; + + name2 = cf_section_name2(g->cs); + c->debug_name = talloc_asprintf(c, "%s %s", unlang_keyword[c->type], name2); + + /* + * We had &Foo-Bar, where Foo-Bar is + * defined by a module. + */ + if (!g->vpt) { + rad_assert(c->name != NULL); + rad_assert(c->name[0] == '&'); + rad_assert(cf_section_name2_type(g->cs) == T_BARE_WORD); + + slen = tmpl_afrom_str(g->cs, &g->vpt, c->name, strlen(c->name), + cf_section_name2_type(g->cs), + REQUEST_CURRENT, PAIR_LIST_REQUEST, true); + if (slen < 0) { + char *spaces, *text; + + parse_error: + fr_canonicalize_error(g->cs, &spaces, &text, slen, fr_strerror()); + + cf_log_err_cs(g->cs, "Syntax error"); + cf_log_err_cs(g->cs, "%s", c->name); + cf_log_err_cs(g->cs, "%s^ %s", spaces, text); + + talloc_free(spaces); + talloc_free(text); + + return false; + } + + goto do_children; + } + + /* + * Statically compile xlats + */ + if (g->vpt->type == TMPL_TYPE_XLAT) { + if (!pass2_xlat_compile(cf_section_to_item(g->cs), + &g->vpt, true, NULL)) { + return false; + } + + goto do_children; + } + + /* + * Convert virtual &Attr-Foo to "%{Attr-Foo}" + */ + if ((g->vpt->type == TMPL_TYPE_ATTR) && g->vpt->tmpl_da->flags.virtual) { + g->vpt->tmpl_xlat = xlat_from_tmpl_attr(g->vpt, g->vpt); + g->vpt->type = TMPL_TYPE_XLAT_STRUCT; + } + + /* + * We may have: switch Foo-Bar { + * + * where Foo-Bar is an attribute defined + * by a module. Since there's no leading + * &, it's parsed as a literal. But if + * we can parse it as an attribute, + * switch to using that. + */ + if (g->vpt->type == TMPL_TYPE_LITERAL) { + vp_tmpl_t *vpt; + + slen = tmpl_afrom_str(g->cs, &vpt, c->name, strlen(c->name), cf_section_name2_type(g->cs), + REQUEST_CURRENT, PAIR_LIST_REQUEST, true); + if (slen < 0) goto parse_error; + if (vpt->type == TMPL_TYPE_ATTR) { + talloc_free(g->vpt); + g->vpt = vpt; + } + + goto do_children; + } + + /* + * Warn about old-style configuration. + * + * DEPRECATED: switch User-Name { ... + * ALLOWED : switch &User-Name { ... + */ + if ((g->vpt->type == TMPL_TYPE_ATTR) && + (c->name[0] != '&')) { + WARN("%s[%d]: Please change %s to &%s", + cf_section_filename(g->cs), + cf_section_lineno(g->cs), + c->name, c->name); + } + + do_children: + if (!modcall_pass2(g->children)) return false; + g->done_pass2 = true; + break; + + case MOD_CASE: + g = mod_callabletogroup(c); + if (g->done_pass2) goto do_next; + + name2 = cf_section_name2(g->cs); + if (!name2) { + c->debug_name = unlang_keyword[c->type]; + } else { + c->debug_name = talloc_asprintf(c, "%s %s", unlang_keyword[c->type], name2); + } + + rad_assert(c->parent != NULL); + rad_assert(c->parent->type == MOD_SWITCH); + + /* + * The statement may refer to an + * attribute which doesn't exist until + * all of the modules have been loaded. + * Check for that now. + */ + if (!g->vpt && c->name && + (c->name[0] == '&') && + (cf_section_name2_type(g->cs) == T_BARE_WORD)) { + slen = tmpl_afrom_str(g->cs, &g->vpt, c->name, strlen(c->name), + cf_section_name2_type(g->cs), + REQUEST_CURRENT, PAIR_LIST_REQUEST, true); + if (slen < 0) goto parse_error; + } + + /* + * We have "case {...}". There's no + * argument, so we don't need to check + * it. + */ + if (!g->vpt) goto do_children; + + /* + * Do type-specific checks on the case statement + */ + if (g->vpt->type == TMPL_TYPE_LITERAL) { + modgroup *f; + + f = mod_callabletogroup(mc->parent); + rad_assert(f->vpt != NULL); + + /* + * We're switching over an + * attribute. Check that the + * values match. + */ + if (f->vpt->type == TMPL_TYPE_ATTR) { + rad_assert(f->vpt->tmpl_da != NULL); + + if (tmpl_cast_in_place(g->vpt, f->vpt->tmpl_da->type, f->vpt->tmpl_da) < 0) { + cf_log_err_cs(g->cs, "Invalid argument for case statement: %s", + fr_strerror()); + return false; + } + } + + goto do_children; + } + + if (g->vpt->type == TMPL_TYPE_ATTR_UNDEFINED) { + if (!pass2_fixup_undefined(cf_section_to_item(g->cs), g->vpt)) { + return false; + } + } + + /* + * Compile and sanity check xlat + * expansions. + */ + if (g->vpt->type == TMPL_TYPE_XLAT) { + modgroup *f; + + f = mod_callabletogroup(mc->parent); + rad_assert(f->vpt != NULL); + + /* + * Don't expand xlat's into an + * attribute of a different type. + */ + if (f->vpt->type == TMPL_TYPE_ATTR) { + if (!pass2_xlat_compile(cf_section_to_item(g->cs), + &g->vpt, true, f->vpt->tmpl_da)) { + return false; + } + } else { + if (!pass2_xlat_compile(cf_section_to_item(g->cs), + &g->vpt, true, NULL)) { + return false; + } + } + } + + /* + * Virtual attribute fixes for "case" statements, too. + */ + if ((g->vpt->type == TMPL_TYPE_ATTR) && g->vpt->tmpl_da->flags.virtual) { + g->vpt->tmpl_xlat = xlat_from_tmpl_attr(g->vpt, g->vpt); + g->vpt->type = TMPL_TYPE_XLAT_STRUCT; + } + + if (!modcall_pass2(g->children)) return false; + g->done_pass2 = true; + break; + + case MOD_FOREACH: + g = mod_callabletogroup(c); + if (g->done_pass2) goto do_next; + + name2 = cf_section_name2(g->cs); + c->debug_name = talloc_asprintf(c, "%s %s", unlang_keyword[c->type], name2); + + /* + * Already parsed, handle the children. + */ + if (g->vpt) goto check_children; + + /* + * We had &Foo-Bar, where Foo-Bar is + * defined by a module. + */ + rad_assert(c->name != NULL); + rad_assert(c->name[0] == '&'); + rad_assert(cf_section_name2_type(g->cs) == T_BARE_WORD); + + /* + * The statement may refer to an + * attribute which doesn't exist until + * all of the modules have been loaded. + * Check for that now. + */ + slen = tmpl_afrom_str(g->cs, &g->vpt, c->name, strlen(c->name), cf_section_name2_type(g->cs), + REQUEST_CURRENT, PAIR_LIST_REQUEST, true); + if (slen < 0) goto parse_error; + + check_children: + rad_assert((g->vpt->type == TMPL_TYPE_ATTR) || (g->vpt->type == TMPL_TYPE_LIST)); + if (g->vpt->tmpl_num != NUM_ALL) { + cf_log_err_cs(g->cs, "MUST NOT use instance selectors in 'foreach'"); + return false; + } + if (!modcall_pass2(g->children)) return false; + g->done_pass2 = true; + break; + + case MOD_ELSE: + c->debug_name = unlang_keyword[c->type]; + goto do_recurse; + + case MOD_POLICY: + g = mod_callabletogroup(c); + c->debug_name = talloc_asprintf(c, "%s %s", unlang_keyword[c->type], cf_section_name1(g->cs)); + goto do_recurse; +#endif + + case MOD_GROUP: + case MOD_LOAD_BALANCE: + case MOD_REDUNDANT_LOAD_BALANCE: + c->debug_name = unlang_keyword[c->type]; + +#ifdef WITH_UNLANG + do_recurse: +#endif + g = mod_callabletogroup(c); + if (!g->cs) { + c->debug_name = mc->name; /* for authorize, etc. */ + + } else if (c->type == MOD_GROUP) { /* for Auth-Type, etc. */ + char const *name1 = cf_section_name1(g->cs); + + if (strcmp(name1, unlang_keyword[c->type]) != 0) { + name2 = cf_section_name2(g->cs); + + if (!name2) { + c->debug_name = name1; + } else { + c->debug_name = talloc_asprintf(c, "%s %s", name1, name2); + } + } + } + + if (g->done_pass2) goto do_next; + if (!modcall_pass2(g->children)) return false; + g->done_pass2 = true; + break; + } + + do_next: + rad_assert(c->debug_name != NULL); + } + + return true; +} + +void modcall_debug(modcallable *mc, int depth) +{ + modcallable *this; + modgroup *g; + vp_map_t *map; + char buffer[1024]; + + for (this = mc; this != NULL; this = this->next) { + switch (this->type) { + default: + break; + + case MOD_SINGLE: { + modsingle *single = mod_callabletosingle(this); + + DEBUG("%.*s%s", depth, modcall_spaces, + single->modinst->name); + } + break; + +#ifdef WITH_UNLANG + case MOD_UPDATE: + g = mod_callabletogroup(this); + DEBUG("%.*s%s {", depth, modcall_spaces, + unlang_keyword[this->type]); + + for (map = g->map; map != NULL; map = map->next) { + map_prints(buffer, sizeof(buffer), map); + DEBUG("%.*s%s", depth + 1, modcall_spaces, buffer); + } + + DEBUG("%.*s}", depth, modcall_spaces); + break; + + case MOD_ELSE: + g = mod_callabletogroup(this); + DEBUG("%.*s%s {", depth, modcall_spaces, + unlang_keyword[this->type]); + modcall_debug(g->children, depth + 1); + DEBUG("%.*s}", depth, modcall_spaces); + break; + + case MOD_IF: + case MOD_ELSIF: + g = mod_callabletogroup(this); + fr_cond_sprint(buffer, sizeof(buffer), g->cond); + DEBUG("%.*s%s (%s) {", depth, modcall_spaces, + unlang_keyword[this->type], buffer); + modcall_debug(g->children, depth + 1); + DEBUG("%.*s}", depth, modcall_spaces); + break; + + case MOD_SWITCH: + case MOD_CASE: + g = mod_callabletogroup(this); + tmpl_prints(buffer, sizeof(buffer), g->vpt, NULL); + DEBUG("%.*s%s %s {", depth, modcall_spaces, + unlang_keyword[this->type], buffer); + modcall_debug(g->children, depth + 1); + DEBUG("%.*s}", depth, modcall_spaces); + break; + + case MOD_POLICY: + case MOD_FOREACH: + g = mod_callabletogroup(this); + DEBUG("%.*s%s %s {", depth, modcall_spaces, + unlang_keyword[this->type], this->name); + modcall_debug(g->children, depth + 1); + DEBUG("%.*s}", depth, modcall_spaces); + break; + + case MOD_BREAK: + DEBUG("%.*sbreak", depth, modcall_spaces); + break; + +#endif + case MOD_GROUP: + g = mod_callabletogroup(this); + DEBUG("%.*s%s {", depth, modcall_spaces, + unlang_keyword[this->type]); + modcall_debug(g->children, depth + 1); + DEBUG("%.*s}", depth, modcall_spaces); + break; + + + case MOD_LOAD_BALANCE: + case MOD_REDUNDANT_LOAD_BALANCE: + g = mod_callabletogroup(this); + DEBUG("%.*s%s {", depth, modcall_spaces, + unlang_keyword[this->type]); + modcall_debug(g->children, depth + 1); + DEBUG("%.*s}", depth, modcall_spaces); + break; + } + } +} + +int modcall_pass2_condition(fr_cond_t *c) +{ + if (!fr_condition_walk(c, pass2_callback, NULL)) return -1; + + return 0; +} -- cgit v1.2.3