* @brief Defines functions for rlm module (re-)initialisation.
*
* @copyright 2003,2006,2016 The FreeRADIUS server project
- * @copyright 2016 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ * @copyright 2016,2024 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
* @copyright 2000 Alan DeKok (aland@freeradius.org)
* @copyright 2000 Alan Curry (pacman@world.std.com)
*/
+
RCSID("$Id$")
#include <freeradius-devel/server/cf_file.h>
#include <freeradius-devel/server/module.h>
#include <freeradius-devel/server/module_rlm.h>
#include <freeradius-devel/server/pair.h>
+#include <freeradius-devel/server/section.h>
+#include <freeradius-devel/server/tmpl.h>
#include <freeradius-devel/server/virtual_servers.h>
#include <freeradius-devel/util/atexit.h>
#include <freeradius-devel/util/debug.h>
#include <freeradius-devel/util/dlist.h>
+#include <freeradius-devel/util/rb.h>
+#include <freeradius-devel/util/sbuff.h>
+#include <freeradius-devel/util/strerror.h>
+#include <freeradius-devel/util/talloc.h>
+#include <freeradius-devel/util/token.h>
+#include <freeradius-devel/util/value.h>
#include <freeradius-devel/unlang/compile.h>
+
#include <freeradius-devel/unlang/xlat_func.h>
#include <freeradius-devel/unlang/xlat_redundant.h>
+#include <pthread.h>
+
/** Lookup virtual module by name
*/
static fr_rb_tree_t *module_rlm_virtual_name_tree;
return 1;
}
+xlat_t *module_rlm_xlat_register(TALLOC_CTX *ctx, module_inst_ctx_t const *mctx,
+ char const *name, xlat_func_t func, fr_type_t return_type)
+{
+ module_instance_t *mi = mctx->mi;
+ module_rlm_instance_t *mri = talloc_get_type_abort(mi->uctx, module_rlm_instance_t);
+ module_rlm_xlat_t *mrx;
+ xlat_t *x;
+ char inst_name[256];
+
+ fr_assert_msg(name != mctx->mi->name, "`name` must not be the same as the module "
+ "instance name. Pass a NULL `name` arg if this is required");
+
+ if (!name) {
+ name = mctx->mi->name;
+ } else {
+ if ((size_t)snprintf(inst_name, sizeof(inst_name), "%s.%s", mctx->mi->name, name) >= sizeof(inst_name)) {
+ ERROR("%s: Instance name too long", __FUNCTION__);
+ return NULL;
+ }
+ name = inst_name;
+ }
+
+ x = xlat_func_register(ctx, name, func, return_type);
+ if (unlikely(x == NULL)) return NULL;
+
+ xlat_mctx_set(x, mctx);
+
+ MEM(mrx = talloc(mi, module_rlm_xlat_t));
+ mrx->xlat = x;
+
+ fr_rb_insert(&mri->xlats, mrx);
+
+ return x;
+}
+
/** Initialise a module specific connection pool
*
* @see fr_pool_init
}
}
-/** Find an existing module instance and verify it implements the specified method
- *
- * Extracts the method from the module name where the format is @verbatim <module>.<method> @endverbatim
- * and ensures the module implements the specified method.
+/** Iterate over an array of named module methods, looking for matches
*
- * @param[out] method the method function we will call
- * @param[out] method_env the module_call_env to evaluate when compiling the method.
- * @param[out] name1 name1 of the method being called
- * @param[out] name2 name2 of the method being called
- * @param[in] name The name of the module we're attempting to find, possibly concatenated with the method
+ * @param[in] bindings A terminated array of module method bindings.
+ * pre-sorted using #section_name_cmp with name2
+ * sublists populated.
+ * @param[in] section name1 of the method being called can be one of the following:
+ * - An itenfier.
+ * - CF_IDENT_ANY if the method is a wildcard.
+ * name2 of the method being called can be one of the following:
+ * - An itenfier.
+ * - NULL to match section names with only a name1.
+ * - CF_IDENT_ANY if the method is a wildcard.
* @return
- * - The module instance on success.
- * - NULL on not found
- *
- * If the module exists but the method doesn't exist, then `method` is set to NULL.
+ * - The module_method_name_t on success.
+ * - NULL on not found.
*/
-module_instance_t *module_rlm_by_name_and_method(module_method_t *method, call_env_method_t const **method_env,
- char const **name1, char const **name2,
- virtual_server_t const *vs, char const *name)
+static CC_HINT(nonnull)
+module_method_binding_t const *module_binding_find(module_method_binding_t const *bindings, section_name_t const *section)
{
- char *p, *q, *inst_name;
- size_t len;
- int j;
- module_instance_t *mi;
- module_method_binding_t const *methods;
- char const *method_name1, *method_name2;
- module_rlm_t const *mrlm;
-
- if (method) *method = NULL;
-
- method_name1 = method_name2 = NULL;
- if (name1) {
- method_name1 = *name1;
- *name1 = NULL;
- }
- if (name2) {
- method_name2 = *name2;
- *name2 = NULL;
- }
+ module_method_binding_t const *p;
/*
- * Module names are allowed to contain '.'
- * so we search for the bare module name first.
+ * This could potentially be improved by using a binary search
+ * but given the small number of items, reduced branches and
+ * sequential access just scanning the list, it's probably not
+ * worth it.
*/
- mi = module_rlm_static_by_name(NULL, name);
- if (mi) {
- section_name_t const **allowed_list;
-
- if (!method) return mi;
+ for (p = bindings; p->section; p++) {
+ switch (section_name_match(p->section, section)) {
+ case 1: /* match */
+ return p;
- mrlm = module_rlm_from_module(mi->exported);
-
- /*
- * We're not searching for a named method, OR the
- * module has no named methods. Try to return a
- * method based on the component.
- */
- if (!method_name1 || !mrlm->bindings) goto return_component;
+ case -1: /* name1 didn't match, skip to the end of the sub-list */
+ p = fr_dlist_tail(&p->same_name1);
+ break;
- /*
- * Walk through the module, finding a matching
- * method.
- */
- for (j = 0; mrlm->bindings[j].section; j++) {
- methods = &mrlm->bindings[j];
+ case 0: /* name1 did match - see if we can find a matching name2 */
+ {
+ fr_dlist_head_t const *same_name1 = &p->same_name1;
- /*
- * Wildcard match name1, we're
- * done.
- */
- if (methods->section->name1 == CF_IDENT_ANY) {
- found:
- *method = methods->method;
- if (method_env) *method_env = methods->method_env;
- if (name1) *name1 = method_name1;
- if (name2) *name2 = method_name2;
- return mi;
+ while ((p = fr_dlist_next(same_name1, p))) {
+ if (section_name2_match(p->section, section)) return p;
}
-
- /*
- * If name1 doesn't match, skip it.
- */
- if (strcasecmp(methods->section->name1, method_name1) != 0) continue;
-
- /*
- * The module can declare a
- * wildcard for name2, in which
- * case it's a match.
- */
- if (methods->section->name2 == CF_IDENT_ANY) goto found;
-
- /*
- * No name2 is also a match to no name2.
- */
- if (!methods->section->name2 && !method_name2) goto found;
-
- /*
- * Don't do strcmp on NULLs
- */
- if (!methods->section->name2 || !method_name2) continue;
-
- if (strcasecmp(methods->section->name2, method_name2) == 0) goto found;
+ p = fr_dlist_tail(same_name1);
}
-
- if (!vs) goto skip_section_method;
-
- /*
- * No match for "recv Access-Request", or
- * whatever else the section is. Let's see if
- * the section has a list of allowed methods.
- */
- allowed_list = virtual_server_section_methods(vs, method_name1, method_name2);
- if (!allowed_list) goto return_component;
-
- /*
- * Walk over allowed methods for this section,
- * (implicitly ordered by priority), and see if
- * the allowed method matches any of the module
- * methods. This process lets us reference a
- * module as "foo" in the configuration. If the
- * module exports a "recv bar" method, and the
- * virtual server has a "recv bar" processing
- * section, then they should match.
- *
- * Unfortunately, this process is O(N*M).
- * Luckily, we only do it if all else fails, so
- * it's mostly OK.
- *
- * Note that the "allowed" list CANNOT include
- * CF_IDENT_ANY. Only the module can do that.
- * If the "allowed" list exported CF_IDENT_ANY,
- * then any module method would match, which is
- * bad.
- */
- for (j = 0; allowed_list[j]; j++) {
- int k;
- section_name_t const *allowed = allowed_list[j];
-
- for (k = 0; mrlm->bindings[k].section; k++) {
- methods = &mrlm->bindings[k];
-
- fr_assert(methods->section->name1 != CF_IDENT_ANY); /* should have been caught above */
-
- if (strcasecmp(methods->section->name1, allowed->name1) != 0) continue;
-
- /*
- * The module matches "recv *",
- * call this method.
- */
- if (methods->section->name2 == CF_IDENT_ANY) {
- found_allowed:
- *method = methods->method;
- return mi;
- }
-
- /*
- * No name2 is also a match to no name2.
- */
- if (!methods->section->name2 && !allowed->name2) goto found_allowed;
-
- /*
- * Don't do strcasecmp on NULLs
- */
- if (!methods->section->name2 || !allowed->name2) continue;
-
- if (strcasecmp(methods->section->name2, allowed->name2) == 0) goto found_allowed;
- }
+ break;
}
-
- return_component:
- /*
- * Didn't find a matching method. Just return
- * the module.
- */
- return mi;
+#ifdef __clang_analyzer__
+ /* Will never be NULL, worse case, p doesn't change*/
+ if (!p) break;
+#endif
}
-skip_section_method:
+ return NULL;
+}
- /*
- * Find out if the instance name contains
- * a method, if it doesn't, then the module
- * doesn't exist.
- */
- p = strchr(name, '.');
- if (!p) {
- fr_strerror_printf("No such module '%s'", name);
- return NULL;
+/** Dump the available bindings for the module into the strerror stack
+ *
+ * @param[in] mmb bindings to push onto the strerror stack.
+ */
+static void module_rlm_methods_to_strerror(module_method_binding_t const *mmb)
+{
+ module_method_binding_t const *mmb_p;
+
+ if (!mmb || !mmb[0].section) {
+ fr_strerror_const_push("Module provides no methods");
+ return;
}
- /*
- * The module name may have a '.' in it, AND it may have
- * a method <sigh> So we try to find out which is which.
- */
- inst_name = talloc_strdup(NULL, name);
- p = inst_name + (p - name);
+ fr_strerror_const_push("Available methods are:");
- /*
- * Loop over the '.' portions, gradually looking up a
- * longer string, in order to find the full module name.
- */
- do {
- *p = '\0';
+ for (mmb_p = mmb; mmb_p->section; mmb_p++) {
+ char const *name1 = section_name_str(mmb_p->section->name1);
+ char const *name2 = section_name_str(mmb_p->section->name2);
- mi = module_instance_by_name(rlm_modules_static, NULL, inst_name);
- if (mi) break;
+ fr_strerror_printf_push(" %s%s%s",
+ name1, name2 ? "." : "", name2 ? name2 : "");
+ }
+}
- /*
- * Find the next '.'
- */
- *p = '.';
- p = strchr(p + 1, '.');
- } while (p);
+/** Find an existing module instance and verify it implements the specified method
+ *
+ * Extracts the method from the module name where the format is @verbatim <module>[.<method1>[.<method2>]] @endverbatim
+ * and ensures the module implements the specified method.
+ *
+ * @param[in] ctx to allocate the dynamic module key tmpl from.
+ * @param[out] mmc_out the result from resolving the module method,
+ * plus the key tmpl for dynamic modules.
+ * This is not allocated from the ctx to save the runtime
+ * dereference.
+ * @param[in] vs Virtual server to search for alternative module names in.
+ * @param[in] section Section name containing the module call.
+ * @param[in] name The module method call i.e. module[<key>][.<method>]
+ * @param[in] t_rules for resolving the dynamic module key.
+ * @return
+ * - The module instance on success.
+ * - NULL on not found
+ *
+ * If the module exists but the method doesn't exist, then `method` is set to NULL.
+ */
+fr_slen_t module_rlm_by_name_and_method(TALLOC_CTX *ctx, module_method_call_t *mmc_out,
+ virtual_server_t const *vs, section_name_t const *section, fr_sbuff_t *name,
+ tmpl_rules_t const *t_rules)
+{
+ fr_sbuff_term_t const *dyn_tt = &FR_SBUFF_TERMS(
+ L(""),
+ L("\t"),
+ L("\n"),
+ L(" "),
+ L("[")
+ );
+
+ fr_sbuff_term_t const *elem_tt = &FR_SBUFF_TERMS(
+ L(""),
+ L("\t"),
+ L("\n"),
+ L(" "),
+ L(".")
+ );
+
+ fr_sbuff_t *elem1;
+ module_method_call_t *mmc;
+ module_method_call_t mmc_tmp;
+ module_method_binding_t const *mmb;
+
+ fr_sbuff_marker_t meth_start;
+
+ fr_slen_t slen;
+ fr_sbuff_t our_name = FR_SBUFF(name);
+
+ mmc = mmc_out ? mmc_out : &mmc_tmp;
+ if (mmc_out) memset(mmc_out, 0, sizeof(*mmc_out));
/*
- * No such module, we're done.
+ * Advance until the start of the dynamic selector
+ * (if it exists).
*/
- if (!mi) {
- fr_strerror_printf("Failed to find module '%s'", inst_name);
- talloc_free(inst_name);
- return NULL;
+ if (fr_sbuff_adv_until(&our_name, SIZE_MAX, dyn_tt, '\0') == 0) {
+ fr_strerror_printf("Invalid module method name");
+ return fr_sbuff_error(&our_name);
}
- mrlm = module_rlm_from_module(mi->exported);
+ FR_SBUFF_TALLOC_THREAD_LOCAL(&elem1, MODULE_INSTANCE_LEN_MAX, (MODULE_INSTANCE_LEN_MAX + 1) * 10);
/*
- * We have a module, but the caller doesn't care about
- * method or names, so just return the module.
+ * If the method string contains a '['
+ *
+ * Search for a dynamic module method, e.g. `elem1[<key>]`.
*/
- if (!method || !method_name1 || !method_name2) goto finish;
+ if (fr_sbuff_is_char(&our_name, '[')) {
+ fr_sbuff_marker_t end, s_end;
+ fr_sbuff_marker(&end, &our_name);
+
+ slen = tmpl_afrom_substr(ctx, &mmc->key, &our_name, T_BARE_WORD, NULL, t_rules);
+ if (slen < 0) {
+ fr_strerror_const_push("Invalid dynamic module selector expression");
+ talloc_free(mmc);
+ return slen;
+ }
- /*
- * We MAY have two names.
- */
- p++;
- q = strchr(p, '.');
- /*
- * We've found the module, but it has no named methods.
- */
- if (!mrlm->bindings) {
- *name1 = name + (p - inst_name);
- *name2 = NULL;
- goto finish;
- }
+ if (!fr_sbuff_is_char(&our_name, ']')) {
+ fr_strerror_const_push("Missing terminating ']' for dynamic module selector");
+ error:
+ talloc_free(mmc);
+ return fr_sbuff_error(&our_name);
+ }
+ fr_sbuff_marker(&s_end, &our_name);
+ fr_sbuff_set_to_start(&our_name);
+ slen = fr_sbuff_out_bstrncpy(elem1, &our_name, fr_sbuff_ahead(&end));
+ if (slen < 0) {
+ fr_strerror_const("Module method string too long");
+ goto error;
+ }
+ mmc->mi = module_instance_by_name(rlm_modules_dynamic, NULL, elem1->start);
+ if (!mmc->mi) {
+ fr_strerror_printf("No such dynamic module '%s'", elem1->start);
+ goto error;
+ }
+ mmc->rlm = module_rlm_from_module(mmc->mi->exported);
+
+ fr_sbuff_set(&our_name, &s_end);
+ fr_sbuff_advance(&our_name, 1); /* Skip the ']' */
/*
- * We have "module.METHOD", but METHOD doesn't match
- * "authorize", "authenticate", etc. Let's see if it
- * matches anything else.
+ * With elem1.elem2.elem3
+ *
+ * Search for a static module matching one of the following:
+ *
+ * - elem1.elem2.elem3
+ * - elem1.elem2
+ * - elem1
*/
- if (!q) {
- for (j = 0; mrlm->bindings[j].section; j++) {
- methods = &mrlm->bindings[j];
-
- /*
- * If we do not have the second $method, then ignore it!
- */
- if (methods->section->name2 && (methods->section->name2 != CF_IDENT_ANY)) continue;
-
- /*
- * Wildcard match name1, we're
- * done.
- */
- if (!methods->section->name1 || (methods->section->name1 == CF_IDENT_ANY)) goto found_name1;
+ } else {
+ char *p;
- /*
- * If name1 doesn't match, skip it.
- */
- if (strcasecmp(methods->section->name1, p) != 0) continue;
+ fr_sbuff_set_to_start(&our_name);
- found_name1:
- /*
- * We've matched "*", or "name1" or
- * "name1 *". Return that.
- */
- *name1 = name + (p - inst_name);
- *name2 = NULL;
- *method = methods->method;
- if (method_env) *method_env = methods->method_env;
- break;
+ slen = fr_sbuff_out_bstrncpy_until(elem1, &our_name, SIZE_MAX, dyn_tt, NULL);
+ if (slen == 0) {
+ fr_strerror_const("Invalid module name");
+ goto error;
+ }
+ if (slen < 0) {
+ fr_strerror_const("Module method string too long");
+ goto error;
}
/*
- * Return the found module.
+ * Now we have a mutable buffer, we can start chopping
+ * it up to find the module.
*/
- goto finish;
- }
+ for (;;) {
+ mmc->mi = (module_instance_t *)module_rlm_static_by_name(NULL, elem1->start);
+ if (mmc->mi) {
+ mmc->rlm = module_rlm_from_module(mmc->mi->exported);
+ break; /* Done */
+ }
- /*
- * We CANNOT have '.' in method names.
- */
- if (strchr(q + 1, '.') != 0) goto finish;
+ p = strrchr(elem1->start, '.');
+ if (!p) break; /* No more '.' */
+ *p = '\0'; /* Chop off the last '.' */
+ }
+
+ if (!mmc->mi) {
+ fr_strerror_printf("No such module '%pV'", fr_box_strvalue_len(our_name.start, slen));
+ return -1;
+ }
- len = q - p;
+ fr_sbuff_set_to_start(&our_name);
+ fr_sbuff_advance(&our_name, strlen(elem1->start)); /* Advance past the module name */
+ if (fr_sbuff_is_char(&our_name, '.')) {
+ fr_sbuff_advance(&our_name, 1); /* Static module method, search directly */
+ } else {
+ fr_sbuff_marker(&meth_start, &our_name); /* for the errors... */
+ goto by_section; /* Get the method dynamically from the section*/
+ }
+ }
/*
- * Trim the '.'.
+ * For both cases, the buffer should be pointing
+ * at the start of the method string.
*/
- if (*q == '.' && *(q + 1)) q++;
+ fr_sbuff_marker(&meth_start, &our_name);
/*
- * We have "module.METHOD1.METHOD2".
+ * If a module method was provided, search for it in the named
+ * methods provided by the module.
+ *
+ * The method name should be either:
*
- * Loop over the method names, seeing if we have a match.
+ * - name1
+ * - name1.name2
*/
- for (j = 0; mrlm->bindings[j].section; j++) {
- methods = &mrlm->bindings[j];
+ {
+ section_name_t method;
+ fr_sbuff_t *elem2;
- /*
- * If name1 doesn't match, skip it.
- */
- if (strncasecmp(methods->section->name1, p, len) != 0) continue;
+ fr_sbuff_set_to_start(elem1); /* May have used this already for module lookups */
- /*
- * It may have been a partial match, like "rec",
- * instead of "recv". In which case check if it
- * was a FULL match.
- */
- if (strlen(methods->section->name1) != len) continue;
+ slen = fr_sbuff_out_bstrncpy_until(elem1, &our_name, SIZE_MAX, elem_tt, NULL);
+ if (slen < 0) {
+ fr_strerror_const("Module method string too long");
+ return fr_sbuff_error(&our_name);
+ }
+ if (slen == 0) goto by_section; /* This works for both dynamic and static modules */
- /*
- * The module can declare a
- * wildcard for name2, in which
- * case it's a match.
- */
- if (!methods->section->name2 || (methods->section->name2 == CF_IDENT_ANY)) goto found_name2;
+ FR_SBUFF_TALLOC_THREAD_LOCAL(&elem2, MODULE_INSTANCE_LEN_MAX, MODULE_INSTANCE_LEN_MAX);
- /*
- * Don't do strcmp on NULLs
- */
- if (!methods->section->name2) continue;
+ if (fr_sbuff_is_char(&our_name, '.')) {
+ fr_sbuff_advance(&our_name, 1);
+ if (fr_sbuff_out_bstrncpy_until(elem2, &our_name, SIZE_MAX, elem_tt, NULL) < 0) {
+ fr_strerror_const("Module method string too long");
+ goto error;
+ }
+ }
- if (strcasecmp(methods->section->name2, q) != 0) continue;
+ method = (section_name_t) {
+ .name1 = elem1->start,
+ .name2 = fr_sbuff_used(elem2) ? elem2->start : NULL
+ };
+
+ mmb = module_binding_find(mmc->rlm->bindings, &method);
+ if (!mmb) {
+ fr_strerror_printf("Module \"%s\" does not have method %s%s%s",
+ mmc->mi->name,
+ method.name1,
+ method.name2 ? "." : "",
+ method.name2 ? method.name2 : ""
+ );
+
+ module_rlm_methods_to_strerror(mmc->rlm->bindings);
+ return fr_sbuff_error(&meth_start);
+ }
+ mmc->mmb = *mmb; /* For locality of reference and fewer derefs */
+ if (mmc_out) section_name_dup(ctx, &mmc->asked, &method);
- found_name2:
- /*
- * Update name1/name2 with the methods
- * that were found.
- */
- *name1 = methods->section->name1;
- *name2 = name + (q - inst_name);
- *method = methods->method;
- if (method_env) *method_env = methods->method_env;
- goto finish;
+ return fr_sbuff_set(name, &our_name);
}
- *name1 = name + (p - inst_name);
- *name2 = NULL;
+by_section:
+ /*
+ * First look for the section name in the module's
+ * bindings. If that fails, look for the alt
+ * section names from the virtual server section.
+ *
+ * If that fails, we're done.
+ */
+ mmb = module_binding_find(mmc->rlm->bindings, section);
+ if (!mmb) {
+ section_name_t const **alt_p = virtual_server_section_methods(vs, section);
+ if (alt_p) {
+ for (; *alt_p; alt_p++) {
+ mmb = module_binding_find(mmc->rlm->bindings, *alt_p);
+ if (mmb) {
+ if (mmc_out) section_name_dup(ctx, &mmc->asked, *alt_p);
+ break;
+ }
+ }
+ }
+ } else {
+ if (mmc_out) section_name_dup(ctx, &mmc->asked, section);
+ }
+ if (!mmb) {
+ fr_strerror_printf("Module \"%s\" has no method for section %s %s { ... }, i.e. %s%s%s",
+ mmc->mi->name,
+ section->name1,
+ section->name2 ? section->name2 : "",
+ section->name1,
+ section->name2 ? "." : "",
+ section->name2 ? section->name2 : ""
+ );
+ module_rlm_methods_to_strerror(mmc->rlm->bindings);
+
+ return fr_sbuff_error(&meth_start);
+ }
+ mmc->mmb = *mmb; /* For locality of reference and fewer derefs */
-finish:
- talloc_free(inst_name);
- return mi;
+ return fr_sbuff_set(name, &our_name);
}
-CONF_SECTION *module_rlm_by_name_virtual(char const *asked_name)
+CONF_SECTION *module_rlm_virtual_by_name(char const *asked_name)
{
module_rlm_virtual_t *inst;
return inst->cs;
}
+module_instance_t *module_rlm_dynamic_by_name(module_instance_t const *parent, char const *asked_name)
+{
+ return module_instance_by_name(rlm_modules_dynamic, parent, asked_name);
+}
+
module_instance_t *module_rlm_static_by_name(module_instance_t const *parent, char const *asked_name)
{
return module_instance_by_name(rlm_modules_static, parent, asked_name);
{
char const *name;
bool all_same;
- module_t const *last = NULL;
CONF_ITEM *sub_ci = NULL;
CONF_PAIR *cp;
module_instance_t *mi;
*/
all_same = (strcmp(cf_section_name1(cs), "group") != 0);
- /*
- * Ensure that the modules we reference here exist.
- */
- while ((sub_ci = cf_item_next(cs, sub_ci))) {
- if (cf_item_is_pair(sub_ci)) {
- cp = cf_item_to_pair(sub_ci);
- if (cf_pair_value(cp)) {
- cf_log_err(sub_ci, "Cannot set return codes in a %s block", cf_section_name1(cs));
- return -1;
- }
+ {
+ module_t const *last = NULL;
- /*
- * Allow "foo.authorize" in subsections.
- *
- * Note that we don't care what the method is, just that it exists.
- *
- * This check is needed only because we
- * want to know if we need to register a
- * redundant xlat for the virtual module.
- */
- mi = module_rlm_by_name_and_method(NULL, NULL, NULL, NULL, NULL, cf_pair_attr(cp));
- if (!mi) {
- cf_log_err(sub_ci, "Module instance \"%s\" referenced in %s block, does not exist",
- cf_pair_attr(cp), cf_section_name1(cs));
- return -1;
- }
+ /*
+ * Ensure that the modules we reference here exist.
+ */
+ while ((sub_ci = cf_item_next(cs, sub_ci))) {
+ if (cf_item_is_pair(sub_ci)) {
+ cp = cf_item_to_pair(sub_ci);
+ if (cf_pair_value(cp)) {
+ cf_log_err(sub_ci, "Cannot set return codes in a %s block", cf_section_name1(cs));
+ return -1;
+ }
+
+ mi = module_rlm_static_by_name(NULL, cf_pair_attr(cp));
+ if (!mi) {
+ cf_log_perr(sub_ci, "Failed resolving module reference '%s' in %s block",
+ cf_pair_attr(cp), cf_section_name1(cs));
+ return -1;
+ }
- if (all_same) {
- if (!last) {
- last = mi->exported;
- } else if (last != mi->exported) {
- last = NULL;
- all_same = false;
+ if (all_same) {
+ if (!last) {
+ last = mi->exported;
+ } else if (last != mi->exported) {
+ last = NULL;
+ all_same = false;
+ }
}
+ } else {
+ all_same = false;
}
- } else {
- all_same = false;
- }
- /*
- * Don't check subsections for now. That check
- * happens later in the unlang compiler.
- */
- } /* loop over things in a virtual module section */
+ /*
+ * Don't check subsections for now. That check
+ * happens later in the unlang compiler.
+ */
+ } /* loop over things in a virtual module section */
+ }
inst = talloc_zero(cs, module_rlm_virtual_t);
if (!inst) return -1;
return 0;
}
+/** Compare xlat functions registered to a module
+ */
+static int8_t module_rlm_xlat_cmp(void const *one, void const *two)
+{
+ module_rlm_xlat_t const *a = one;
+ module_rlm_xlat_t const *b = two;
+
+ return xlat_func_cmp(a->xlat, b->xlat);
+}
+
/** Allocate a rlm module instance
*
* These have extra space allocated to hold the dlist of associated xlats.
MEM(mri = talloc(mi, module_rlm_instance_t));
module_instance_uctx_set(mi, mri);
- fr_rb_inline_init(&mri->xlats, module_rlm_xlat_t, node, xlat_func_cmp, NULL);
+ fr_rb_inline_init(&mri->xlats, module_rlm_xlat_t, node, module_rlm_xlat_cmp, NULL);
return mi;
}
///< onto the stack for execution. So we need
///< to use the common type here.
module_rlm_t const *rlm; //!< Cached module_rlm_t.
+ section_name_t asked; //!< The actual <name1>.<name2> used for the module call.
+ ///< This was either the override the user specified,
+ ///< or the name of the section.
module_method_binding_t mmb; //!< Method we're calling.
tmpl_t *key; //!< Dynamic key, only set for dynamic modules.
} module_method_call_t;
*
* @{
*/
-module_instance_t *module_rlm_by_name_and_method(module_method_t *method, call_env_method_t const ** method_env,
- char const **name1, char const **name2,
- virtual_server_t const *vs, char const *asked_name);
+fr_slen_t module_rlm_by_name_and_method(TALLOC_CTX *ctx, module_method_call_t *mmc_out,
+ virtual_server_t const *vs, section_name_t const *section, fr_sbuff_t *name,
+ tmpl_rules_t const *t_rules) CC_HINT(nonnull(5));
module_instance_t *module_rlm_dynamic_by_name(module_instance_t const *parent, char const *name);
-CONF_SECTION *module_rlm_by_name_virtual(char const *asked_name);
module_instance_t *module_rlm_static_by_name(module_instance_t const *parent, char const *name);
CONF_SECTION *module_rlm_virtual_by_name(char const *name);
return section_name2_match(a, b);
}
+/** Return a printable string for the section name
+ *
+ * @param[in] name Section name.
+ */
+static inline char const *section_name_str(char const *name)
+{
+ if (name == NULL) return "NULL";
+ if (name == CF_IDENT_ANY) return "*";
+ return name;
+}
+
+static inline void section_name_dup(TALLOC_CTX *ctx, section_name_t *dst, section_name_t const *src)
+{
+ dst->name1 = src->name1;
+ dst->name2 = src->name2;
+
+ if (dst->name1 && (dst->name1 != CF_IDENT_ANY)) dst->name1 = talloc_typed_strdup(ctx, src->name1);
+ if (dst->name2 && (dst->name2 != CF_IDENT_ANY)) dst->name2 = talloc_typed_strdup(ctx, src->name2);
+}
+
int8_t section_name_cmp(void const *one, void const *two);
#ifdef __cplusplus
* @copyright 2000 Alan DeKok (aland@freeradius.org)
* @copyright 2000 Alan Curry (pacman@world.std.com)
*/
-
-
RCSID("$Id$")
#include <freeradius-devel/protocol/freeradius/freeradius.internal.h>
#include <freeradius-devel/server/modpriv.h>
#include <freeradius-devel/server/process.h>
#include <freeradius-devel/server/protocol.h>
+#include <freeradius-devel/server/section.h>
#include <freeradius-devel/server/virtual_servers.h>
#include <freeradius-devel/unlang/compile.h>
static fr_rb_tree_t *listen_addr_root = NULL;
-static int8_t server_section_name_cmp(void const *one, void const *two);
-
static int namespace_on_read(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
static int server_on_read(TALLOC_CTX *ctx, void *out, UNUSED void *parent, CONF_ITEM *ci, UNUSED conf_parser_t const *rule);
return 0;
}
+static int8_t virtual_server_compile_name_cmp(void const *a, void const *b)
+{
+ virtual_server_compile_t const *sa = a;
+ virtual_server_compile_t const *sb = b;
+
+ return section_name_cmp(sa->section, sb->section);
+}
+
/** Callback to validate the server section
*
* @param[in] ctx to allocate data in.
return -1;
}
- MEM(server->sections = fr_rb_alloc(server, server_section_name_cmp, NULL));
+ MEM(server->sections = fr_rb_alloc(server, virtual_server_compile_name_cmp, NULL));
server->server_cs = server_cs;
/*
return found;
}
-static int8_t server_section_name_cmp(void const *one, void const *two)
-{
- virtual_server_compile_t const *a = one;
- virtual_server_compile_t const *b = two;
- int ret;
-
- ret = strcmp(a->section->name1, b->section->name1);
- ret = CMP(ret, 0);
- if (ret != 0) return ret;
-
- if (a->section->name2 == b->section->name2) return 0;
- if ((a->section->name2 == CF_IDENT_ANY) && (b->section->name2 != CF_IDENT_ANY)) return -1;
- if ((a->section->name2 != CF_IDENT_ANY) && (b->section->name2 == CF_IDENT_ANY)) return +1;
-
- ret = strcmp(a->section->name2, b->section->name2);
- return CMP(ret, 0);
-}
-
/** Register name1 / name2 as allowed processing sections
*
* This function is called from the virtual server bootstrap routine,
/** Find the component for a section
*
*/
-section_name_t const **virtual_server_section_methods(virtual_server_t const *vs, char const *name1, char const *name2)
+section_name_t const **virtual_server_section_methods(virtual_server_t const *vs, section_name_t const *section)
{
virtual_server_compile_t *entry;
* Look up the specific name first. That way we can
* define both "accounting on", and "accounting *".
*/
- if (name2 != CF_IDENT_ANY) {
+ if (section->name2 != CF_IDENT_ANY) {
entry = fr_rb_find(vs->sections,
&(virtual_server_compile_t) {
- .section = SECTION_NAME(name1, name2)
+ .section = section
});
if (entry) return entry->methods;
}
*/
entry = fr_rb_find(vs->sections,
&(virtual_server_compile_t) {
- .section = SECTION_NAME(name1, CF_IDENT_ANY)
+ .section = SECTION_NAME(section->name1, CF_IDENT_ANY)
});
if (!entry) return NULL;
int virtual_server_section_register(virtual_server_t *vs, virtual_server_compile_t const *entry) CC_HINT(nonnull);
-int virtual_server_compile_sections(virtual_server_t const *vs, tmpl_rules_t const *rules) CC_HINT(nonnull);
+section_name_t const **virtual_server_section_methods(virtual_server_t const *vs, section_name_t const *section) CC_HINT(nonnull);
-section_name_t const **virtual_server_section_methods(virtual_server_t const *vs, char const *name1, char const *name2) CC_HINT(nonnull(1));
+int virtual_server_compile_sections(virtual_server_t const *vs, tmpl_rules_t const *rules) CC_HINT(nonnull);
unlang_action_t virtual_server_push(request_t *request, CONF_SECTION *server_cs, bool top_frame) CC_HINT(nonnull);
#include <freeradius-devel/server/cf_util.h>
#include <freeradius-devel/server/tmpl.h>
#include <freeradius-devel/server/request.h>
+#include <freeradius-devel/server/section.h>
#include <freeradius-devel/unlang/tmpl.h>
#include <freeradius-devel/unlang/function.h>
#include <freeradius-devel/unlang/interpret.h>
};
FR_DLIST_FUNCS(call_env_parsed, call_env_parsed_t, entry)
+#if defined(DEBUG_CALL_ENV)
+# define CALL_ENV_DEBUG(_ci, fmt, ...) cf_log_debug(_ci, fmt, ##__VA_ARGS__)
+#else
+# define CALL_ENV_DEBUG(_ci, ...)
+#endif
+
/** Parse the result of call_env tmpl expansion
*/
static inline CC_HINT(always_inline)
* @param[out] out Where to write the result of parsing.
* @param[in] t_rules we're parsing attributes with. Contains the default dictionary and nested 'caller' tmpl_rules_t.
* @param[in] ci The #CONF_SECTION or #CONF_PAIR to parse.
- * @param[in] data module / xlat instance data of the module / xlat allocating this call_env
+ * @param[in] cec information about the call.
* @param[in] rule Parse rules - How the #CONF_PAIR or #CONF_SECTION should be converted.
* @return
* - 0 on success.
* - -1 on failure.
*/
int call_env_parse_pair(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci,
- UNUSED char const *section_name1, UNUSED char const *section_name2,
- UNUSED void const *data, UNUSED call_env_parser_t const *rule)
+ UNUSED call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule)
{
CONF_PAIR const *to_parse = cf_item_to_pair(ci);
tmpl_t *parsed_tmpl;
* @param[in] name Module name for error messages.
* @param[in] t_rules controlling how the call env is parsed.
* @param[in] cs Module config.
- * @param[in] section_name1 Name 1 from calling section for module calls
- * @param[in] section_name2 Name 2 from calling section for module calls
- * @param[in] data module / xlat instance data of the module / xlat allocating this call_env
+ * @param[in] cec information about the call.
* @param[in] rule to parse.
* @return
* - 0 on success;
* - <0 on failure;
*/
static int call_env_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *parsed, char const *name, tmpl_rules_t const *t_rules,
- CONF_SECTION const *cs, char const *section_name1, char const *section_name2,
- void const *data, call_env_parser_t const *rule) {
+ CONF_SECTION const *cs,
+ call_env_ctx_t const *cec, call_env_parser_t const *rule) {
CONF_PAIR const *cp, *next;
call_env_parsed_t *call_env_parsed = NULL;
ssize_t count, multi_index;
+ call_env_parser_t const *rule_p = rule;
+
+ while (rule_p->name) {
+ CALL_ENV_DEBUG(cs, "%s: Parsing call env data for %s", name, section_name_str(rule_p->name));
- while (rule->name) {
- if (call_env_is_subsection(rule->flags)) {
+ if (call_env_is_subsection(rule_p->flags)) {
CONF_SECTION const *subcs;
- subcs = cf_section_find(cs, rule->name, rule->section.ident2);
- if (!subcs && !call_env_parse_missing(rule->flags)) {
- if (!call_env_required(rule->flags)) goto next;
- cf_log_err(cs, "Module %s missing required section \"%s\"", name, rule->name);
+ subcs = cf_section_find(cs, rule_p->name, rule_p->section.name2);
+ if (!subcs && !call_env_parse_missing(rule_p->flags)) {
+ if (!call_env_required(rule_p->flags)) goto next;
+ cf_log_err(cs, "Module %s missing required section \"%s\"", name, rule_p->name);
return -1;
}
/*
* Hand off to custom parsing function if there is one...
*/
- if (rule->section.func) {
+ if (rule_p->section.func) {
/*
* Record our position so we can process any new entries
* after the callback returns.
*/
call_env_parsed_t *last = call_env_parsed_tail(parsed);
- if (rule->section.func(ctx, parsed, t_rules, cf_section_to_item(subcs), section_name1,
- section_name2, data, rule) < 0) {
- cf_log_perr(cs, "Failed parsing configuration section %s", rule->name);
+ CALL_ENV_DEBUG(cs, "%s: Calling subsection callback %p", name, rule_p->section.func);
+
+ if (rule_p->section.func(ctx, parsed, t_rules, cf_section_to_item(subcs), cec, rule_p) < 0) {
+ cf_log_perr(cs, "Failed parsing configuration section %s", rule_p->name);
talloc_free(call_env_parsed);
return -1;
}
+ CALL_ENV_DEBUG(subcs, "%s: Callback returned %u parsed call envs", name,
+ call_env_parsed_num_elements(parsed));
+
/*
* We _could_ fix up count and multi_index on behalf of
* the callback, but there's no guarantee that all call_env_parsed_t
*/
call_env_parsed = last;
while ((call_env_parsed = call_env_parsed_next(parsed, call_env_parsed))) {
- if (call_env_parsed_valid(call_env_parsed, cf_section_to_item(subcs), rule) < 0) {
- cf_log_err(cf_section_to_item(subcs), "Invalid data produced by %s", rule->name);
+ CALL_ENV_DEBUG(subcs, "%s: Checking parsed env", name, rule_p->section.func);
+ if (call_env_parsed_valid(call_env_parsed, cf_section_to_item(subcs), rule_p) < 0) {
+ cf_log_err(cf_section_to_item(subcs), "Invalid data produced by %s", rule_p->name);
return -1;
}
}
goto next;
}
- if (call_env_parse(ctx, parsed, name, t_rules, subcs, section_name1, section_name2,
- data, rule->section.subcs) < 0) return -1;
+ if (call_env_parse(ctx, parsed, name, t_rules, subcs, cec, rule_p->section.subcs) < 0) {
+ CALL_ENV_DEBUG(cs, "%s: Recursive call failed", name, rule_p->name);
+ return -1;
+ }
goto next;
}
- cp = cf_pair_find(cs, rule->name);
+ cp = cf_pair_find(cs, rule_p->name);
- if (!cp && !rule->pair.dflt) {
- if (!call_env_required(rule->flags)) goto next;
+ if (!cp && !rule_p->pair.dflt) {
+ if (!call_env_required(rule_p->flags)) goto next;
- cf_log_err(cs, "Missing required config item '%s'", rule->name);
+ cf_log_err(cs, "Missing required config item '%s'", rule_p->name);
return -1;
}
* Check for additional conf pairs and error
* if there is one and multi is not allowed.
*/
- if (!call_env_multi(rule->flags) && ((next = cf_pair_find_next(cs, cp, rule->name)))) {
- cf_log_err(cf_pair_to_item(next), "Invalid duplicate configuration item '%s'", rule->name);
+ if (!call_env_multi(rule_p->flags) && ((next = cf_pair_find_next(cs, cp, rule_p->name)))) {
+ cf_log_err(cf_pair_to_item(next), "Invalid duplicate configuration item '%s'", rule_p->name);
return -1;
}
- count = cf_pair_count(cs, rule->name);
+ count = cf_pair_count(cs, rule_p->name);
if (count == 0) count = 1;
for (multi_index = 0; multi_index < count; multi_index++) {
CONF_PAIR *tmp_cp = NULL;
CONF_PAIR const *to_parse;
tmpl_rules_t our_rules = {};
- fr_type_t type = rule->pair.cast_type;
- call_env_parse_pair_t func = rule->pair.func ? rule->pair.func : call_env_parse_pair;
+ fr_type_t type = rule_p->pair.cast_type;
+ call_env_parse_pair_t func = rule_p->pair.func ? rule_p->pair.func : call_env_parse_pair;
if (t_rules) {
our_rules.parent = t_rules->parent;
our_rules.attr.dict_def = t_rules->attr.dict_def;
- our_rules.escape = rule->pair.escape; /* Escape rules will now get embedded in the tmpl_t and used at evaluation */
+ our_rules.escape = rule_p->pair.escape; /* Escape rules will now get embedded in the tmpl_t and used at evaluation */
}
our_rules.attr.list_def = request_attr_request;
our_rules.cast = ((type == FR_TYPE_VOID) ? FR_TYPE_NULL : type);
- our_rules.literals_safe_for = rule->pair.literals_safe_for;
+ our_rules.literals_safe_for = rule_p->pair.literals_safe_for;
- call_env_parsed = call_env_parsed_alloc(ctx, rule);
+ call_env_parsed = call_env_parsed_alloc(ctx, rule_p);
call_env_parsed->count = count;
call_env_parsed->multi_index = multi_index;
* we can't do that here.
*/
if (cp) {
- if (call_env_force_quote(rule->flags)) {
+ if (call_env_force_quote(rule_p->flags)) {
to_parse = tmp_cp = cf_pair_alloc(NULL,
cf_pair_attr(cp), cf_pair_value(cp), cf_pair_operator(cp),
cf_pair_attr_quote(cp),
- call_env_force_quote(rule->flags) ? rule->pair.dflt_quote : cf_pair_value_quote(cp));
+ call_env_force_quote(rule_p->flags) ? rule_p->pair.dflt_quote : cf_pair_value_quote(cp));
} else {
to_parse = cp;
}
} else {
to_parse = tmp_cp = cf_pair_alloc(NULL,
- rule->name, rule->pair.dflt, T_OP_EQ,
- T_BARE_WORD, rule->pair.dflt_quote);
+ rule_p->name, rule_p->pair.dflt, T_OP_EQ,
+ T_BARE_WORD, rule_p->pair.dflt_quote);
}
/*
* would, or produce a custom structure, which will be copied into the
* result structure.
*/
- if (unlikely(func(ctx, &call_env_parsed->data, &our_rules, cf_pair_to_item(to_parse), section_name1, section_name2, data, rule) < 0)) {
+ if (unlikely(func(ctx, &call_env_parsed->data, &our_rules, cf_pair_to_item(to_parse), cec, rule_p) < 0)) {
error:
- cf_log_perr(to_parse, "Failed to parse configuration item '%s = %s'", rule->name, cf_pair_value(to_parse));
+ cf_log_perr(to_parse, "Failed to parse configuration item '%s = %s'", rule_p->name, cf_pair_value(to_parse));
talloc_free(call_env_parsed);
talloc_free(tmp_cp);
return -1;
/*
* Ensure only valid data is produced.
*/
- if (call_env_parsed_valid(call_env_parsed, cf_pair_to_item(to_parse), rule) < 0) goto error;
+ if (call_env_parsed_valid(call_env_parsed, cf_pair_to_item(to_parse), rule_p) < 0) goto error;
call_env_parsed_insert_tail(parsed, call_env_parsed);
next_pair:
talloc_free(tmp_cp);
- cp = cf_pair_find_next(cs, cp, rule->name);
+ cp = cf_pair_find_next(cs, cp, rule_p->name);
}
next:
- rule++;
+ rule_p++;
}
+ CALL_ENV_DEBUG(cs, "Returning afer processing %u rules", (unsigned int)(rule_p - rule));
+
return 0;
}
while (call_env->name) {
if (call_env_is_subsection(call_env->flags)) {
CONF_SECTION const *subcs;
- subcs = cf_section_find(cs, call_env->name, call_env->section.ident2);
+ subcs = cf_section_find(cs, call_env->name, call_env->section.name2);
if (!subcs) goto next;
/*
* @param[in] call_env_method containing the call_env_pair_t to evaluate against the specified CONF_SECTION.
* @param[in] t_rules that control how call_env_pair_t are parsed.
* @param[in] cs to parse in the context of the call.
- * @param[in] data module / xlat instance data of the module / xlat allocating this call_env
+ * @param[in] cec information about how the call is being made.
* @return
* - A new call_env_t on success.
* - NULL on failure.
*/
call_env_t *call_env_alloc(TALLOC_CTX *ctx, char const *name, call_env_method_t const *call_env_method,
- tmpl_rules_t const *t_rules, CONF_SECTION *cs, char const *section_name1,
- char const *section_name2, void const *data)
+ tmpl_rules_t const *t_rules, CONF_SECTION *cs, call_env_ctx_t const *cec)
{
unsigned int count;
size_t names_len;
MEM(call_env = talloc_pooled_object(ctx, call_env_t, count * 4, (sizeof(call_env_parser_t) + sizeof(tmpl_t)) * count + names_len * 2));
call_env->method = call_env_method;
call_env_parsed_init(&call_env->parsed);
- if (call_env_parse(call_env, &call_env->parsed, name, t_rules, cs, section_name1, section_name2,
- data, call_env_method->env) < 0) {
+ if (call_env_parse(call_env, &call_env->parsed, name, t_rules, cs, cec, call_env_method->env) < 0) {
talloc_free(call_env);
return NULL;
}
typedef struct call_env_parser_s call_env_parser_t;
typedef struct call_env_parsed_s call_env_parsed_t;
typedef struct call_env_method_s call_env_method_t;
+typedef struct call_env_ctx_s call_env_ctx_t;
typedef struct call_env_s call_env_t;
FR_DLIST_TYPES(call_env_parsed)
#include <freeradius-devel/unlang/action.h>
#include <freeradius-devel/server/cf_parse.h>
#include <freeradius-devel/server/dl_module.h>
+#include <freeradius-devel/server/module.h>
#include <freeradius-devel/server/request.h>
#include <freeradius-devel/server/tmpl.h>
* @param[out] out Where to write the result of parsing.
* @param[in] t_rules we're parsing attributes with. Contains the default dictionary and nested 'caller' tmpl_rules_t.
* @param[in] ci The #CONF_SECTION or #CONF_PAIR to parse.
- * @param[in] data module / xlat instance data of the module / xlat allocating this call_env
+ * @param[in] cec information about how the call env is being used.
* @param[in] rule Parse rules - How the #CONF_PAIR or #CONF_SECTION should be converted.
* @return
* - 0 on success.
* - -1 on failure.
*/
-typedef int (*call_env_parse_pair_t)(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, char const *section_name1, char const *section_name2, void const *data, call_env_parser_t const *rule);
+typedef int (*call_env_parse_pair_t)(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, call_env_ctx_t const *cec, call_env_parser_t const *rule);
/** Callback for performing custom parsing of a #CONF_SECTION
*
* @param[out] out Where to write the result of parsing.
* @param[in] t_rules we're parsing attributes with. Contains the default dictionary and nested 'caller' tmpl_rules_t.
* @param[in] ci The #CONF_SECTION or #CONF_PAIR to parse.
+ * @param[in] cec information about how the call env is being used.
* @param[in] rule Parse rules - How the #CONF_PAIR or #CONF_SECTION should be converted.
* @return
* - 0 on success.
* - -1 on failure.
*/
-typedef int (*call_env_parse_section_t)(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, char const *section_name1, char const *section_name2, void const *data, call_env_parser_t const *rule);
+typedef int (*call_env_parse_section_t)(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci,
+ call_env_ctx_t const *cec, call_env_parser_t const *rule);
/** Per method call config
*
} pair;
struct {
- char const *ident2; //!< Second identifier for a section
+ char const *name2; //!< Second identifier for a section
call_env_parser_t const *subcs; //!< Nested definitions for subsection.
call_env_parse_section_t func; //!< Callback for parsing CONF_SECTION.
void const *uctx; //!< User context for callback functions.
};
+typedef enum {
+ CALL_ENV_CTX_TYPE_MODULE = 1, //!< The callenv is registered to a module method.
+ CALL_ENV_CTX_TYPE_XLAT //!< The callenv is registered to an xlat.
+} call_env_ctx_type_t;
+
+struct call_env_ctx_s {
+ call_env_ctx_type_t type; //!< Type of callenv ctx.
+
+ module_instance_t const *mi; //!< Module instance that the callenv is registered to.
+ ///< Available for both module methods, and xlats.
+
+ section_name_t const *asked; //!< The actual name1/name2 that resolved to a
+ ///< module_method_binding_t.
+};
+
#define CALL_ENV_TERMINATOR { NULL }
/** Helper macro for populating the size/type fields of a #call_env_method_t from the output structure type
/** Specify a call_env_parser_t which defines a nested subsection
*/
-#define FR_CALL_ENV_SUBSECTION(_name, _ident2, _flags, _subcs ) \
+#define FR_CALL_ENV_SUBSECTION(_name, _name2, _flags, _subcs ) \
.name = _name, \
.flags = CALL_ENV_FLAG_SUBSECTION | (_flags), \
.section = { \
- .ident2 = _ident2, \
+ .name2 = _name2, \
.subcs = _subcs, \
}
/** Specify a call_env_parser_t which parses a subsection using a callback function
*/
-#define FR_CALL_ENV_SUBSECTION_FUNC(_name, _ident2, _flags, _func) \
+#define FR_CALL_ENV_SUBSECTION_FUNC(_name, _name2, _flags, _func) \
.name = _name, \
.flags = CALL_ENV_FLAG_SUBSECTION | (_flags), \
.section = { \
- .ident2 = _ident2, \
+ .name2 = _name2, \
.func = _func \
}
/** @name Functions that implement standard parsing behaviour which can be called by callbacks
* @{
*/
-int call_env_parse_pair(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, char const *section_name1,
- char const *section_name2, void const *data, call_env_parser_t const *rule);
+int call_env_parse_pair(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci,
+ call_env_ctx_t const *cec, call_env_parser_t const *rule);
/** @} */
/** @name Functions to be used by the section callbacks to add parsed data.
* @{
*/
call_env_t *call_env_alloc(TALLOC_CTX *ctx, char const *name, call_env_method_t const *call_env_method,
- tmpl_rules_t const *rules, CONF_SECTION *cs, char const *section_name1,
- char const *section_name2, void const *data) CC_HINT(nonnull(3,4,5));
+ tmpl_rules_t const *rules, CONF_SECTION *cs, call_env_ctx_t const *cec) CC_HINT(nonnull(3,4,5));
/** @} */
#ifdef __cplusplus
case UNLANG_TYPE_MODULE:
{
- unlang_module_t *single = unlang_generic_to_module(c);
+ unlang_module_t *m = unlang_generic_to_module(c);
- DEBUG("%.*s%s", depth, unlang_spaces, single->mi->name);
+ DEBUG("%.*s%s", depth, unlang_spaces, m->mmc.mi->name);
}
break;
*
* Return it to the caller, with the updated method.
*/
- subcs = module_rlm_by_name_virtual(virtual_name);
+ subcs = module_rlm_virtual_by_name(virtual_name);
if (subcs) goto check_for_loop;
/*
return subcs;
}
-static unlang_t *compile_module(unlang_t *parent, unlang_compile_t *unlang_ctx,
- CONF_ITEM *ci, module_instance_t *inst, module_method_t method,
- call_env_method_t const *method_env, char const *realname)
+static unlang_t *compile_module(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM *ci, char const *name)
{
- unlang_t *c;
- unlang_module_t *single;
-
- /*
- * Check if the module in question has the necessary
- * component.
- */
- if (!method) {
- cf_log_err(ci, "Failed compiling %s - no method matching calling section found", inst->name);
+ unlang_t *c;
+ unlang_module_t *m;
+ fr_slen_t slen;
+
+ MEM(m = talloc_zero(parent, unlang_module_t));
+ slen = module_rlm_by_name_and_method(m, &m->mmc,
+ unlang_ctx->vs,
+ &(section_name_t){ .name1 = unlang_ctx->section_name1, .name2 = unlang_ctx->section_name2 },
+ &FR_SBUFF_IN(name, strlen(name)),
+ unlang_ctx->rules);
+ if (slen < 0) {
+ cf_log_perr(ci, "Failed compiling module call");
+ talloc_free(m);
return NULL;
}
- MEM(single = talloc_zero(parent, unlang_module_t));
- single->mi = inst;
- single->method = method;
- c = unlang_module_to_generic(single);
+ c = unlang_module_to_generic(m);
c->parent = parent;
c->next = NULL;
- c->name = talloc_typed_strdup(c, realname);
+ c->name = talloc_typed_strdup(c, name);
c->debug_name = c->name;
c->type = UNLANG_TYPE_MODULE;
c->ci = ci;
/*
* Set the default actions for this module.
*/
- c->actions = inst->actions;
+ c->actions = m->mmc.mi->actions;
/*
* Add in the default actions for this section.
/*
* Parse the method environment for this module / method
*/
- if (method_env) {
+ if (m->mmc.mmb.method_env) {
+ call_env_method_t const *method_env = m->mmc.mmb.method_env;
+
fr_assert_msg(method_env->inst_size, "Method environment for module %s, method %s %s declared, "
"but no inst_size set",
- inst->name, unlang_ctx->section_name1, unlang_ctx->section_name2);
+ m->mmc.mi->name, unlang_ctx->section_name1, unlang_ctx->section_name2);
if (!unlang_ctx->rules) {
- cf_log_err(ci, "Failed compiling %s - no rules", inst->name);
+ cf_log_err(ci, "Failed compiling %s - no rules", m->mmc.mi->name);
goto error;
}
- single->call_env = call_env_alloc(single, single->self.name, method_env,
- unlang_ctx->rules, inst->conf,
- unlang_ctx->section_name1, unlang_ctx->section_name2,
- single->mi->data);
- if (!single->call_env) {
+ m->call_env = call_env_alloc(m, m->self.name, method_env,
+ unlang_ctx->rules, m->mmc.mi->conf,
+ &(call_env_ctx_t){
+ .type = CALL_ENV_CTX_TYPE_MODULE,
+ .mi = m->mmc.mi,
+ .asked = &m->mmc.asked
+ });
+ if (!m->call_env) {
error:
- talloc_free(c);
+ talloc_free(m);
return NULL;
}
}
*/
if (cf_item_is_section(ci) &&
!unlang_compile_actions(&c->actions, cf_item_to_section(ci),
- (inst->exported->flags & MODULE_TYPE_RETRY) != 0)) goto error;
+ (m->mmc.mi->exported->flags & MODULE_TYPE_RETRY) != 0)) goto error;
return c;
}
static unlang_t *compile_item(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM *ci)
{
char const *name, *p;
- module_instance_t *inst;
CONF_SECTION *cs, *subcs, *modules;
char const *realname;
unlang_compile_t unlang_ctx2;
- module_method_t method;
bool policy;
unlang_op_compile_t compile;
unlang_t *c;
- call_env_method_t const *method_env = NULL;
+ bool ignore_notfound = false;
if (cf_item_is_section(ci)) {
cs = cf_item_to_section(ci);
* Not a function. It must be a real module.
*/
modules = cf_section_find(cf_root(ci), "modules", NULL);
- if (!modules) goto fail;
+ if (!modules) {
+ cf_log_err(ci, "Failed compiling \"%s\" as a module or policy as no modules are enabled", name);
+ cf_log_err(ci, "Please verify that modules { ... } section is present in the server configuration", name);
+ return NULL;
+ }
realname = name;
/*
* Try to load the optional module.
*/
- if (realname[0] == '-') realname++;
+ if (*realname == '-') {
+ ignore_notfound = true;
+ realname++;
+ }
/*
* Set the child compilation context BEFORE parsing the
* name2, etc.
*/
UPDATE_CTX2;
- inst = module_rlm_by_name_and_method(&method, &method_env,
- &unlang_ctx2.section_name1, &unlang_ctx2.section_name2,
- unlang_ctx2.vs, realname);
- if (inst) {
- c = compile_module(parent, &unlang_ctx2, ci, inst, method, method_env, realname);
- goto allocate_number;
- }
+ c = compile_module(parent, &unlang_ctx2, ci, realname);
+ if (c) goto allocate_number;
- /*
- * We were asked to MAYBE load it and it
- * doesn't exist. Return a soft error.
- */
- if (realname != name) {
- cf_log_warn(ci, "Ignoring \"%s\" as the \"%s\" module is not enabled.", name, realname);
+ if (ignore_notfound) {
+ cf_log_warn(ci, "Ignoring \"%s\" as the \"%s\" module is not enabled, "
+ "or the method does not exist", name, realname);
return UNLANG_IGNORE;
}
- /*
- * The module exists, but it does not have the
- * named method.
- */
- if (!unlang_ctx2.section_name1) {
- cf_log_err(ci, "Failed compiling %s - %s", name, fr_strerror());
- return NULL;
- }
-
- /*
- * Can't de-reference it to anything. Ugh.
- */
-fail:
- cf_log_err(ci, "Failed to find \"%s\" as a module or policy.", name);
- cf_log_err(ci, "Please verify that the configuration exists in mods-enabled/%s.", name);
return NULL;
}
unlang_module_fd_event_t fd_read; //!< Function to call when FD is readable.
unlang_module_fd_event_t fd_write; //!< Function to call when FD is writable.
unlang_module_fd_event_t fd_error; //!< Function to call when FD has errored.
- module_instance_t *mi; //!< Module instance to pass to callbacks.
+ module_instance_t const *mi; //!< Module instance to pass to callbacks.
///< Use mi->data to get instance data.
void *thread; //!< Thread specific module instance.
void *env_data; //!< Per call environment data.
unlang_stack_t *stack = request->stack;
unlang_stack_frame_t *frame = &stack->frame[stack->depth];
unlang_module_event_t *ev;
- unlang_module_t *mc;
+ unlang_module_t *m;
unlang_frame_state_module_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_module_t);
fr_assert(stack->depth > 0);
fr_assert(frame->instruction->type == UNLANG_TYPE_MODULE);
- mc = unlang_generic_to_module(frame->instruction);
+ m = unlang_generic_to_module(frame->instruction);
ev = talloc(request, unlang_module_event_t);
if (!ev) return -1;
.request = request,
.fd = -1,
.timeout = callback,
- .mi = mc->mi,
+ .mi = m->mmc.mi,
.thread = state->thread,
.env_data = state->env_data,
.rctx = rctx
unlang_stack_t *stack = request->stack;
unlang_stack_frame_t *frame = &stack->frame[stack->depth];
unlang_module_event_t *ev;
- unlang_module_t *mc;
+ unlang_module_t *m;
unlang_frame_state_module_t *state = talloc_get_type_abort(frame->state,
unlang_frame_state_module_t);
fr_assert(stack->depth > 0);
fr_assert(frame->instruction->type == UNLANG_TYPE_MODULE);
- mc = unlang_generic_to_module(frame->instruction);
+ m = unlang_generic_to_module(frame->instruction);
ev = talloc_zero(request, unlang_module_event_t);
if (!ev) return -1;
ev->fd_read = read;
ev->fd_write = write;
ev->fd_error = error;
- ev->mi = mc->mi;
+ ev->mi = m->mmc.mi;
ev->thread = state->thread;
ev->env_data = state->env_data;
ev->rctx = rctx;
.retry = RETRY_INIT,
},
},
- .mi = mi,
- .method = method
+ .mmc = {
+ .mi = mi,
+ .mmb = {
+ .method = method
+ }
+ }
};
/*
unlang_module_signal_t signal, fr_signal_t sigmask, void *rctx)
{
if (!subcs) {
- unlang_stack_t *stack = request->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_module_t *mc;
+ unlang_stack_t *stack = request->stack;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_module_t *m;
unlang_frame_state_module_t *state;
fr_assert(frame->instruction->type == UNLANG_TYPE_MODULE);
- mc = unlang_generic_to_module(frame->instruction);
+ m = unlang_generic_to_module(frame->instruction);
/*
* Be transparent to the resume function.
state = talloc_get_type_abort(frame->state, unlang_frame_state_module_t);
return resume(p_result,
- MODULE_CTX(mc->mi, module_thread(mc->mi)->data,
+ MODULE_CTX(m->mmc.mi, module_thread(m->mmc.mi)->data,
state->env_data, rctx),
request);
}
static void unlang_module_signal(request_t *request, unlang_stack_frame_t *frame, fr_signal_t action)
{
unlang_frame_state_module_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_module_t);
- unlang_module_t *mc = unlang_generic_to_module(frame->instruction);
+ unlang_module_t *m = unlang_generic_to_module(frame->instruction);
char const *caller;
if (!state->signal) return;
* Async calls can't push anything onto the unlang stack, so we just use a local "caller" here.
*/
caller = request->module;
- request->module = mc->mi->name;
- safe_lock(mc->mi);
- if (!(action & state->sigmask)) state->signal(MODULE_CTX(mc->mi, state->thread->data, state->env_data, state->rctx), request, action);
- safe_unlock(mc->mi);
+ request->module = m->mmc.mi->name;
+ safe_lock(m->mmc.mi);
+ if (!(action & state->sigmask)) state->signal(MODULE_CTX(m->mmc.mi, state->thread->data, state->env_data, state->rctx), request, action);
+ safe_unlock(m->mmc.mi);
request->module = caller;
/*
static unlang_action_t unlang_module_resume(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
{
unlang_frame_state_module_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_module_t);
- unlang_module_t *mc = unlang_generic_to_module(frame->instruction);
- module_method_t resume;
+ unlang_module_t *m = unlang_generic_to_module(frame->instruction);
+ module_method_t resume;
unlang_action_t ua;
/*
/*
* Lock is noop unless instance->mutex is set.
*/
- safe_lock(mc->mi);
- ua = resume(&state->rcode, MODULE_CTX(mc->mi, state->thread->data,
+ safe_lock(m->mmc.mi);
+ ua = resume(&state->rcode, MODULE_CTX(m->mmc.mi, state->thread->data,
state->env_data, state->rctx), request);
- safe_unlock(mc->mi);
+ safe_unlock(m->mmc.mi);
if (request->master_state == REQUEST_STOP_PROCESSING) ua = UNLANG_ACTION_STOP_PROCESSING;
switch (ua) {
case UNLANG_ACTION_STOP_PROCESSING:
- RWARN("Module %s or worker signalled to stop processing request", mc->mi->module->exported->name);
+ RWARN("Module %s or worker signalled to stop processing request", m->mmc.mi->name);
if (state->p_result) *state->p_result = state->rcode;
state->thread->active_callers--;
*p_result = state->rcode;
static unlang_action_t unlang_module(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
{
- unlang_module_t *mc;
+ unlang_module_t *m;
unlang_frame_state_module_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_module_t);
unlang_action_t ua;
fr_time_t now = fr_time_wrap(0);
* Process a stand-alone child, and fall through
* to dealing with it's parent.
*/
- mc = unlang_generic_to_module(frame->instruction);
- fr_assert(mc);
+ m = unlang_generic_to_module(frame->instruction);
+ fr_assert(m);
RDEBUG4("[%i] %s - %s (%s)", stack_depth_current(request), __FUNCTION__,
- mc->mi->module->exported->name, mc->mi->name);
+ m->mmc.mi->module->exported->name, m->mmc.mi->name);
state->p_result = NULL;
/*
* Return administratively configured return code
*/
- if (mc->mi->force) {
- state->rcode = mc->mi->code;
+ if (m->mmc.mi->force) {
+ state->rcode = m->mmc.mi->code;
ua = UNLANG_ACTION_CALCULATE_RESULT;
goto done;
}
- if (mc->call_env) {
+ if (m->mmc.mmb.method_env) {
if (!state->env_data) {
- ua = call_env_expand(state, request, &state->env_result, &state->env_data, mc->call_env);
+ ua = call_env_expand(state, request, &state->env_result, &state->env_data, m->call_env);
switch (ua) {
case UNLANG_ACTION_FAIL:
goto fail;
/*
* Grab the thread/module specific data if any exists.
*/
- state->thread = module_thread(mc->mi);
+ state->thread = module_thread(m->mmc.mi);
fr_assert(state->thread != NULL);
/*
*/
if (fr_time_delta_ispos(frame->instruction->actions.retry.irt)) now = fr_time();
- request->module = mc->mi->name;
- safe_lock(mc->mi); /* Noop unless instance->mutex set */
- ua = mc->method(&state->rcode,
- MODULE_CTX(mc->mi, state->thread->data, state->env_data, NULL),
- request);
- safe_unlock(mc->mi);
+ request->module = m->mmc.mi->name;
+ safe_lock(m->mmc.mi); /* Noop unless instance->mutex set */
+ ua = m->mmc.mmb.method(&state->rcode,
+ MODULE_CTX(m->mmc.mi, state->thread->data, state->env_data, NULL),
+ request);
+ safe_unlock(m->mmc.mi);
if (request->master_state == REQUEST_STOP_PROCESSING) ua = UNLANG_ACTION_STOP_PROCESSING;
* must have been blocked.
*/
case UNLANG_ACTION_STOP_PROCESSING:
- RWARN("Module %s became unblocked", mc->mi->name);
+ RWARN("Module %s became unblocked", m->mmc.mi->name);
if (state->p_result) *state->p_result = state->rcode;
*p_result = state->rcode;
request->module = state->previous_module;
* @copyright 2018-2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
* @copyright 2018 The FreeRADIUS server project
*/
+#include "lib/unlang/call_env.h"
RCSID("$Id$")
#include <freeradius-devel/io/schedule.h>
.dict_def = call->dict,
.list_def = request_attr_request
}
- }, call->func->mctx->mi->conf, NULL, NULL, call->func->mctx->mi->data);
+ }, call->func->mctx->mi->conf,
+ &(call_env_ctx_t){
+ .type = CALL_ENV_CTX_TYPE_XLAT,
+ .mi = call->func->mctx->mi
+ });
if (!xi->call_env) {
talloc_free(xi);
return NULL;
*
* @copyright 2023 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
*/
-
RCSID("$Id$")
+#include <talloc.h>
+
#include <freeradius-devel/unlang/interpret.h>
#include <freeradius-devel/unlang/xlat_redundant.h>
#include <freeradius-devel/unlang/xlat_func.h>
#include <freeradius-devel/unlang/xlat_priv.h>
+
+#include <freeradius-devel/server/cf_util.h>
+#include <freeradius-devel/server/module.h>
+#include <freeradius-devel/server/module_rlm.h>
+#include <freeradius-devel/unlang/xlat.h>
+
+#include <freeradius-devel/util/dlist.h>
#include <freeradius-devel/util/rand.h>
+#include <freeradius-devel/util/rb.h>
+#include <freeradius-devel/util/sbuff.h>
+
/*
* Internal redundant handler for xlats
typedef struct {
fr_dlist_t entry; //!< Entry in the redundant function list.
- xlat_t *func; //!< Resolved xlat function.
+ xlat_t const *func; //!< Resolved xlat function.
} xlat_redundant_func_t;
typedef struct {
* @param[in] args Arguments to the function. Will be copied,
* and freed when the new xlat node is freed.
*/
-static xlat_exp_t *xlat_exp_func_alloc(TALLOC_CTX *ctx, xlat_t *func, xlat_exp_head_t const *args)
+static xlat_exp_t *xlat_exp_func_alloc(TALLOC_CTX *ctx, xlat_t const *func, xlat_exp_head_t const *args)
{
xlat_exp_t *node;
XLAT_ARG_PARSER_TERMINATOR
};
+static inline CC_HINT(always_inline)
+void xlat_redundant_add_xlat(xlat_redundant_t *xr, xlat_t const *x)
+{
+ xlat_redundant_func_t *xrf;
+
+ MEM(xrf = talloc_zero(xr, xlat_redundant_func_t));
+ xrf->func = x;
+ fr_dlist_insert_tail(&xr->funcs, xrf);
+}
+
/** Registers a redundant xlat
*
* These xlats wrap the xlat methods of the modules in a redundant section,
};
static size_t xlat_redundant_type_table_len = NUM_ELEMENTS(xlat_redundant_type_table);
- char const *name1, *name2;
+ char const *name1;
xlat_redundant_type_t xr_type;
xlat_redundant_t *xr;
- xlat_func_flags_t flags = XLAT_FUNC_FLAG_NONE;
- bool can_be_pure = false;
+ xlat_func_flags_t default_flags;
xlat_arg_parser_t const *args = NULL;
fr_type_t return_type = FR_TYPE_NULL;
- bool first = true;
- xlat_t *xlat;
CONF_ITEM *ci = NULL;
+ int children = 0, i;
+ module_instance_t **mri_arr; /* Temporary array of module instances */
+ module_instance_t **mri_p;
name1 = cf_section_name1(cs);
xr_type = fr_table_value_by_str(xlat_redundant_type_table, name1, XLAT_REDUNDANT_INVALID);
return -1;
case XLAT_REDUNDANT:
- can_be_pure = true; /* Can be pure */
+ default_flags = XLAT_FUNC_FLAG_PURE; /* Can be pure */
break;
case XLAT_LOAD_BALANCE:
- can_be_pure = false; /* Can never be pure because of random selection */
+ default_flags = XLAT_FUNC_FLAG_NONE; /* Can never be pure because of random selection */
break;
case XLAT_REDUNDANT_LOAD_BALANCE:
- can_be_pure = false; /* Can never be pure because of random selection */
+ default_flags = XLAT_FUNC_FLAG_NONE; /* Can never be pure because of random selection */
break;
}
- name2 = cf_section_name2(cs);
- if (xlat_func_find(name2, talloc_array_length(name2) - 1)) {
- cf_log_err(cs, "An expansion is already registered for this name");
- return -1;
- }
+ /*
+ * Count the children
+ */
+ while ((ci = cf_item_next(cs, ci))) {
+ if (!cf_item_is_pair(ci)) continue;
- MEM(xr = talloc_zero(cs, xlat_redundant_t));
- xr->type = xr_type;
- xr->cs = cs;
- fr_dlist_talloc_init(&xr->funcs, xlat_redundant_func_t, entry);
+ children++;
+ }
/*
- * Count the number of children for load-balance, and
- * also find out a little bit more about the old xlats.
+ * There must be at least one child.
*
- * These are just preemptive checks, the majority of
- * the work is done when a redundant xlat is
- * instantiated. There we create an xlat node for
- * each of the children of the section.
+ * It's useful to allow a redundant section with
+ * only one child, for debugging.
*/
- while ((ci = cf_item_next(cs, ci))) {
- xlat_redundant_func_t *xrf;
- char const *mod_func_name;
- xlat_t *mod_func;
+ if (children == 0) {
+ cf_log_err(cs, "%s %s { ... } section must contain at least one module",
+ cf_section_name1(cs), cf_section_name2(cs));
+ return -1;
+ }
+
+ /*
+ * Resolve all the modules in the redundant section.
+ */
+ MEM(mri_arr = talloc_array(NULL, module_instance_t *, children));
+ for (ci = cf_item_next(cs, NULL), i = 0;
+ ci;
+ ci = cf_item_next(cs, ci), i++) {
+ char const *name;
if (!cf_item_is_pair(ci)) continue;
- mod_func_name = cf_pair_attr(cf_item_to_pair(ci));
+ name = cf_pair_attr(cf_item_to_pair(ci));
- /*
- * This is ok, it just means the module
- * doesn't have an xlat method.
- *
- * If there are ordering issues we could
- * move this check to the instantiation
- * function.
- */
- mod_func = xlat_func_find(mod_func_name, talloc_array_length(mod_func_name) - 1);
- if (!mod_func) {
- talloc_free(xr);
- return 1;
+ mri_arr[i] = module_rlm_static_by_name(NULL, name);
+ if (!mri_arr[i]) {
+ cf_log_err(ci, "Module '%s' not found. Referenced in %s %s { ... } section",
+ name, cf_section_name1(cs), cf_section_name2(cs));
+ error:
+ talloc_free(mri_arr);
+ return -1;
}
+ }
- if (!args) {
- args = mod_func->args;
- } else {
- fr_assert(args == mod_func->args);
- }
+ /*
+ * Iterate over the xlats registered for the
+ * first module, verifying that the other
+ * module instances have all registered the
+ * similarly named xlat functions.
+ *
+ * We ignore any xlat functions that aren't
+ * available in all the modules.
+ */
+ {
+ fr_rb_iter_inorder_t iter;
+ fr_sbuff_t *name;
+ fr_sbuff_marker_t name_start;
+ xlat_t *xlat;
+ module_rlm_xlat_t const *mrx;
+ module_rlm_instance_t *mri;
+
+ FR_SBUFF_TALLOC_THREAD_LOCAL(&name, 128, SIZE_MAX);
/*
- * Degrade to a void return type if
- * we have mixed types in a redundant
- * section.
+ * Prepopulate the name buffer with <section_name2>.
+ * as every function wil be registered with this
+ * prefix.
*/
- if (!first) {
- if (mod_func->return_type != return_type) return_type = FR_TYPE_VOID;
- } else {
- return_type = mod_func->return_type;
- first = false;
+ if ((fr_sbuff_in_bstrcpy_buffer(name, cf_section_name2(cs)) <= 0) ||
+ (fr_sbuff_in_char(name, '.') <= 0)) {
+ cf_log_perr(cs, "Name too long");
+ return -1;
}
- MEM(xrf = talloc_zero(xr, xlat_redundant_func_t));
- xrf->func = mod_func;
- fr_dlist_insert_tail(&xr->funcs, xrf);
+ fr_sbuff_marker(&name_start, name);
+
+ mri = talloc_get_type_abort(mri_arr[0]->uctx, module_rlm_instance_t);
/*
- * Figure out pure status. If any of
- * the children are un-pure then the
- * whole redundant xlat is un-pure,
- * same with async.
+ * Iterate over the xlats registered to the first
+ * module instance, searching for them in all the
+ * other module instances.
*/
- if (can_be_pure && mod_func->flags.pure) flags |= XLAT_FUNC_FLAG_PURE;
- }
+ for (mrx = fr_rb_iter_init_inorder(&iter, &mri->xlats);
+ mrx;
+ mrx = fr_rb_iter_next_inorder(&iter)) {
+ xlat_func_flags_t flags = default_flags;
+ char const *name_p;
+
+ (void)talloc_get_type(mrx, module_rlm_xlat_t);
+
+ /*
+ * Allocate the redundant xlat structure
+ * (somehwat prematurely) with the expectation
+ * that the virtually every module of the same
+ * type will have registered the same xlat
+ * functions.
+ */
+ MEM(xr = talloc_zero(NULL, xlat_redundant_t));
+ xr->type = xr_type;
+ xr->cs = cs;
+ fr_dlist_talloc_init(&xr->funcs, xlat_redundant_func_t, entry);
+
+ xlat_redundant_add_xlat(xr, mrx->xlat);
+
+ /*
+ * We're only interested in xlat functions
+ * that are available in all the module
+ * instances.
+ */
+ for (mri_p = mri_arr + 1; mri_p < mri_arr + children; mri_p++) {
+ module_rlm_xlat_t *found;
+
+ found = talloc_get_type_abort(fr_rb_find(&mri->xlats, mrx), module_rlm_xlat_t);
+ if (!found) continue;
+
+ xlat_redundant_add_xlat(xr, mrx->xlat);
+
+ if (!found->xlat->flags.pure) flags &= ~XLAT_FUNC_FLAG_PURE;
+ }
- /*
- * At least one module xlat has to exist.
- */
- if (!fr_dlist_num_elements(&xr->funcs)) {
- talloc_free(xr);
- return 1;
- }
+ /*
+ * Warn, but allow about redundant/failover expansions
+ * that are neither redundant, no failover.
+ *
+ * It's sometime useful to comment out multiples during
+ * testing.
+ */
+ if (fr_dlist_num_elements(&xr->funcs) == 1) {
+ cf_log_debug(cs, "%s expansion has no alternates, only %s",
+ fr_table_str_by_value(xlat_redundant_type_table, xr->type, "<INVALID>"),
+ mrx->xlat->name);
- xlat = xlat_func_register(NULL, name2, xlat_redundant, return_type);
- if (unlikely(xlat == NULL)) {
- ERROR("Registering xlat for %s section failed",
- fr_table_str_by_value(xlat_redundant_type_table, xr->type, "<INVALID>"));
- talloc_free(xr);
- return -1;
+ }
+ /*
+ * Where the xlat name is in the format <mod>.<name2>
+ * then the redundant xlat will be <section_name2>.<xlat_name>.
+ *
+ * Where the xlat has no '.', it's likely just the module
+ * name, in which case we just use <section_name2>.
+ */
+ name_p = strchr(mrx->xlat->name, '.');
+ if (name_p) {
+ name_p++;
+ fr_sbuff_set(name, &name_start); /* Reset the aggregation buffer to the '.' */
+ if (fr_sbuff_in_bstrncpy(name, name_p, strlen(name_p)) < 0) {
+ cf_log_perr(cs, "Name too long");
+ goto error;
+ }
+ name_p = fr_sbuff_start(name);
+ } else {
+ name_p = cf_section_name2(cs);
+ }
+
+ /*
+ * Register the new redundant xlat, and hang it off of
+ * the first module instance in the section.
+ *
+ * This isn't great, but at least the xlat should
+ * get unregistered at about the right time.
+ */
+ xlat = xlat_func_register(mri_arr[0], name_p, xlat_redundant, return_type);
+ if (unlikely(xlat == NULL)) {
+ cf_log_err(cs, "Registering expansion for %s section failed",
+ fr_table_str_by_value(xlat_redundant_type_table, xr->type, "<INVALID>"));
+ talloc_free(xr);
+ return -1;
+ }
+ talloc_steal(xlat, xr); /* redundant xlat should own its own config */
+
+ cf_log_debug(cs, "Registered %s expansion \"%s\" with %u alternates",
+ fr_table_str_by_value(xlat_redundant_type_table, xr->type, "<INVALID>"),
+ xlat->name, fr_dlist_num_elements(&xr->funcs));
+
+ xlat_func_flags_set(xlat, flags);
+ xlat_func_instantiate_set(xlat, xlat_redundant_instantiate, xlat_redundant_inst_t, NULL, xr);
+ if (args) xlat_func_args_set(xlat, xlat_redundant_args);
+ }
}
- xlat_func_flags_set(xlat, flags);
- xlat_func_instantiate_set(xlat, xlat_redundant_instantiate, xlat_redundant_inst_t, NULL, xr);
- if (args) xlat_func_args_set(xlat, xlat_redundant_args);
+
+ talloc_free(mri_arr);
return 0;
}
extern module_rlm_t rlm_cache;
int submodule_parse(TALLOC_CTX *ctx, void *out, void *parent, CONF_ITEM *ci, conf_parser_t const *rule);
-static int cache_key_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, char const *section_name1, char const *section_name2, void const *data, call_env_parser_t const *rule);
-static int cache_update_section_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, char const *section_name1, char const *section_name2, void const *data, call_env_parser_t const *rule);
+static int cache_key_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, call_env_ctx_t const *cec, call_env_parser_t const *rule);
+static int cache_update_section_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, call_env_ctx_t const *cec, call_env_parser_t const *rule);
static const conf_parser_t module_config[] = {
{ FR_CONF_OFFSET_TYPE_FLAGS("driver", FR_TYPE_VOID, 0, rlm_cache_t, driver_submodule), .dflt = "rbtree",
}
static int cache_key_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci,
- char const *section_name1, char const *section_name2, void const *data,
+ call_env_ctx_t const *cec,
call_env_parser_t const *rule)
{
- rlm_cache_t const *inst = talloc_get_type_abort_const(data, rlm_cache_t);
+ rlm_cache_t const *inst = talloc_get_type_abort_const(cec->mi->data, rlm_cache_t);
call_env_parse_pair_t func = inst->driver->key_parse ? inst->driver->key_parse : call_env_parse_pair;
tmpl_t *key_tmpl;
fr_type_t cast;
* Call the custom key parse function, OR the standard call_env_parse_pair
* function, depending on whether the driver calls a custom parsing function.
*/
- if (unlikely((ret = func(ctx, &key_tmpl, t_rules, ci, section_name1, section_name2,
- inst->driver_submodule->data, rule)) < 0)) return ret;
+ if (unlikely((ret = func(ctx, &key_tmpl, t_rules, ci, inst->driver_submodule->data, rule)) < 0)) return ret;
*((tmpl_t **)out) = key_tmpl;
/*
}
static int cache_update_section_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules,
- CONF_ITEM *ci, UNUSED char const *section_name1, UNUSED char const *section_name2,
- UNUSED void const *data, UNUSED call_env_parser_t const *rule)
+ CONF_ITEM *ci,
+ UNUSED call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule)
{
CONF_SECTION *update = cf_item_to_section(ci);
call_env_parsed_t *parsed;
return detail_do(p_result, mctx, request, request->reply, &request->reply_pairs, false);
}
-static int call_env_filename_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci,
- UNUSED char const *section_name1, UNUSED char const *section_name2,
- void const *data, UNUSED call_env_parser_t const *rule)
+static int call_env_filename_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules,
+ CONF_ITEM *ci,
+ call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule)
{
- rlm_detail_t const *inst = talloc_get_type_abort_const(data, rlm_detail_t);
+ rlm_detail_t const *inst = talloc_get_type_abort_const(cec->mi->data, rlm_detail_t);
tmpl_t *parsed;
CONF_PAIR const *to_parse = cf_item_to_pair(ci);
tmpl_rules_t our_rules;
}
static int call_env_suppress_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules,
- CONF_ITEM *ci, UNUSED char const *section_name1, UNUSED char const *section_name2,
- UNUSED void const *data, UNUSED call_env_parser_t const *rule)
+ CONF_ITEM *ci,
+ UNUSED call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule)
{
CONF_SECTION const *cs = cf_item_to_section(ci);
CONF_SECTION const *parent = cf_item_to_section(cf_parent(ci));
*
*/
static int call_env_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci,
- UNUSED char const *section_name1, UNUSED char const *section_name2,
- void const *data, UNUSED call_env_parser_t const *rule)
+ call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule)
{
- rlm_files_t const *inst = talloc_get_type_abort_const(data, rlm_files_t);
- CONF_PAIR const *to_parse = cf_item_to_pair(ci);
- rlm_files_data_t *files_data;
- fr_type_t keytype;
+ rlm_files_t const *inst = talloc_get_type_abort_const(cec->mi->data, rlm_files_t);
+ CONF_PAIR const *to_parse = cf_item_to_pair(ci);
+ rlm_files_data_t *files_data;
+ fr_type_t keytype;
MEM(files_data = talloc_zero(ctx, rlm_files_data_t));
map_list_t *profile_map; //!< List of maps to apply to the profile.
} ldap_xlat_profile_call_env_t;
-static int ldap_update_section_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, char const *section_name1, char const *section_name2, void const *data, call_env_parser_t const *rule);
+static int ldap_update_section_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, call_env_ctx_t const *cec, call_env_parser_t const *rule);
-static int ldap_group_filter_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, UNUSED char const *section_name1, UNUSED char const *section_name2, void const *data, UNUSED call_env_parser_t const *rule);
+static int ldap_group_filter_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci, call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule);
static const call_env_parser_t sasl_call_env[] = {
{ FR_CALL_ENV_OFFSET("mech", FR_TYPE_STRING, CALL_ENV_FLAG_NONE, ldap_auth_call_env_t, user_sasl_mech) },
}
static int ldap_update_section_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules,
- CONF_ITEM *ci, UNUSED char const *section_name1, UNUSED char const *section_name2,
- UNUSED void const *data, call_env_parser_t const *rule)
+ CONF_ITEM *ci,
+ UNUSED call_env_ctx_t const *cec, call_env_parser_t const *rule)
{
map_list_t *maps;
CONF_SECTION *update = cf_item_to_section(ci);
}
static int ldap_group_filter_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, UNUSED CONF_ITEM *ci,
- UNUSED char const *section_name1, UNUSED char const *section_name2,
- void const *data, UNUSED call_env_parser_t const *rule)
+ call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule)
{
- rlm_ldap_t const *inst = talloc_get_type_abort_const(data, rlm_ldap_t);
- char const *filters[] = { inst->group.obj_filter, inst->group.obj_membership_filter };
- tmpl_t *parsed;
+ rlm_ldap_t const *inst = talloc_get_type_abort_const(cec->mi->data, rlm_ldap_t);
+ char const *filters[] = { inst->group.obj_filter, inst->group.obj_membership_filter };
+ tmpl_t *parsed;
if (fr_ldap_filter_to_tmpl(ctx, t_rules, filters, NUM_ELEMENTS(filters), &parsed) < 0) return -1;
static int linelog_escape_func(fr_value_box_t *vb, UNUSED void *uctx);
static int call_env_filename_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci,
- UNUSED char const *section_name1, UNUSED char const *section_name2,
- void const *data, UNUSED call_env_parser_t const *rule);
+ call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule);
typedef enum {
LINELOG_DST_INVALID = 0,
LINELOG_DST_FILE, //!< Log to a file.
* Custom call env parser for filenames - sets the correct escaping function
*/
static int call_env_filename_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci,
- UNUSED char const *section_name1, UNUSED char const *section_name2,
- void const *data, UNUSED call_env_parser_t const *rule)
+ call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule)
{
- rlm_linelog_t const *inst = talloc_get_type_abort_const(data, rlm_linelog_t);
- tmpl_t *parsed;
- CONF_PAIR const *to_parse = cf_item_to_pair(ci);
- tmpl_rules_t our_rules;
+ rlm_linelog_t const *inst = talloc_get_type_abort_const(cec->mi->data, rlm_linelog_t);
+ tmpl_t *parsed;
+ CONF_PAIR const *to_parse = cf_item_to_pair(ci);
+ tmpl_rules_t our_rules;
/*
* If we're not logging to a file destination, do nothing
*
*/
static int smtp_header_section_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules,
- CONF_ITEM *ci, UNUSED char const *section_name1, UNUSED char const *section_name2,
- UNUSED void const *data, UNUSED call_env_parser_t const *rule)
+ CONF_ITEM *ci,
+ UNUSED call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule)
{
CONF_SECTION const *cs = cf_item_to_section(ci);
CONF_ITEM const *item = NULL;
};
static int logfile_call_env_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules, CONF_ITEM *cc,
- char const *section_name1, char const *section_name2, void const *data, call_env_parser_t const *rule);
+ call_env_ctx_t const *cec, call_env_parser_t const *rule);
static int query_call_env_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules, CONF_ITEM *cc,
- char const *section_name1, char const *section_name2, void const *data, call_env_parser_t const *rule);
+ call_env_ctx_t const *cec, call_env_parser_t const *rule);
typedef struct {
fr_value_box_t filename;
}
static int logfile_call_env_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules,
- CONF_ITEM *ci, char const *section_name1, char const *section_name2,
- UNUSED void const *data, UNUSED call_env_parser_t const *rule)
+ CONF_ITEM *ci,
+ UNUSED call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule)
{
CONF_SECTION const *subcs = NULL, *subsubcs = NULL;
CONF_PAIR const *to_parse = NULL;
tmpl_rules_t our_rules;
char *section2, *p;
+ fr_assert(cec->type == CALL_ENV_CTX_TYPE_MODULE);
+
/*
* The call env subsection which calls this has CF_IDENT_ANY as its name
* which results in finding the first child section of the module config.
* falling back to
* <module> { logfile }
*/
- subcs = cf_section_find(cf_item_to_section(ci), section_name1, CF_IDENT_ANY);
+ subcs = cf_section_find(cf_item_to_section(ci), cec->asked->name1, CF_IDENT_ANY);
if (subcs) {
- if (section_name2) {
- section2 = talloc_strdup(NULL, section_name2);
+ if (cec->asked->name2) {
+ section2 = talloc_strdup(NULL, cec->asked->name2);
p = section2;
while (*p != '\0') {
*(p) = tolower((uint8_t)*p);
}
static int query_call_env_parse(TALLOC_CTX *ctx, call_env_parsed_head_t *out, tmpl_rules_t const *t_rules,
- CONF_ITEM *ci, UNUSED char const *section_name1, char const *section_name2,
- void const *data, UNUSED call_env_parser_t const *rule)
+ CONF_ITEM *ci,
+ call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule)
{
- rlm_sql_t const *inst = talloc_get_type_abort_const(data, rlm_sql_t);
+ rlm_sql_t const *inst = talloc_get_type_abort_const(cec->mi->data, rlm_sql_t);
CONF_SECTION const *subcs = NULL;
CONF_PAIR const *to_parse = NULL;
tmpl_t *parsed_tmpl;
char *section2, *p;
ssize_t count, slen, multi_index = 0;
- if (!section_name2) return -1;
+ fr_assert(cec->type == CALL_ENV_CTX_TYPE_MODULE);
/*
* Find the instance(s) of "query" to parse
* If the module call is from `accounting Start` then it should be
* <module> { accounting { start { query } } }
*/
- section2 = talloc_strdup(NULL, section_name2);
+ section2 = talloc_strdup(NULL, section_name_str(cec->asked->name2));
p = section2;
while (*p != '\0') {
*(p) = tolower((uint8_t)*p);
p++;
}
subcs = cf_section_find(cf_item_to_section(ci), section2, CF_IDENT_ANY);
+ if (!subcs) {
+ cf_log_debug(ci, "No query found for \"%s\", this query will be disabled...", section2);
+ talloc_free(section2);
+ return 0;
+ }
talloc_free(section2);
- if (!subcs) return 0;
/*
* Use module specific escape functions
while ((to_parse = cf_pair_find_next(subcs, to_parse, "query"))) {
MEM(parsed_env = call_env_parsed_add(ctx, out,
- &(call_env_parser_t){ FR_CALL_ENV_PARSE_ONLY_OFFSET("query", FR_TYPE_STRING, CALL_ENV_FLAG_CONCAT | CALL_ENV_FLAG_MULTI, sql_redundant_call_env_t, query)}));
+ &(call_env_parser_t){
+ FR_CALL_ENV_PARSE_ONLY_OFFSET("query", FR_TYPE_STRING, CALL_ENV_FLAG_CONCAT | CALL_ENV_FLAG_MULTI,
+ sql_redundant_call_env_t, query)
+ }));
slen = tmpl_afrom_substr(parsed_env, &parsed_tmpl,
- &FR_SBUFF_IN(cf_pair_value(to_parse), talloc_array_length(cf_pair_value(to_parse)) - 1),
- cf_pair_value_quote(to_parse), NULL, &our_rules);
+ &FR_SBUFF_IN(cf_pair_value(to_parse), talloc_array_length(cf_pair_value(to_parse)) - 1),
+ cf_pair_value_quote(to_parse), NULL, &our_rules);
if (slen <= 0) {
cf_canonicalize_error(to_parse, slen, "Failed parsing query", cf_pair_value(to_parse));
error:
* The xlat escape function needs access to inst - so
* argument parser details need to be defined here
*/
- sql_xlat_arg = talloc_zero_array(xlat, xlat_arg_parser_t, 2);
- uctx = talloc_zero(sql_xlat_arg, rlm_sql_escape_uctx_t);
+ MEM(sql_xlat_arg = talloc_zero_array(xlat, xlat_arg_parser_t, 2));
+ MEM(uctx = talloc_zero(sql_xlat_arg, rlm_sql_escape_uctx_t));
*uctx = (rlm_sql_escape_uctx_t){ .sql = inst, .handle = NULL };
sql_xlat_arg[0] = (xlat_arg_parser_t){
.type = FR_TYPE_STRING,
/** Custom call_env parser to tokenize the SQL query xlat used for counter retrieval
*/
static int call_env_query_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci,
- UNUSED char const *section_name1, UNUSED char const *section_name2,
- void const *data, UNUSED call_env_parser_t const *rule)
+ call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule)
{
- rlm_sqlcounter_t const *inst = talloc_get_type_abort_const(data, rlm_sqlcounter_t);
+ rlm_sqlcounter_t const *inst = talloc_get_type_abort_const(cec->mi->data, rlm_sqlcounter_t);
CONF_PAIR const *to_parse = cf_item_to_pair(ci);
char *query;
xlat_exp_head_t *ex;
* @copyright 2016 The FreeRADIUS server project
* @copyright 2016 Matthew Newton (matthew@newtoncomputing.co.uk)
*/
-
RCSID("$Id$")
#include <freeradius-devel/server/base.h>
#include <freeradius-devel/server/module_rlm.h>
+#include <freeradius-devel/unlang/call_env.h>
#include <freeradius-devel/unlang/xlat_func.h>
#include <freeradius-devel/util/debug.h>
};
static int domain_call_env_parse(TALLOC_CTX *ctx, void *out, tmpl_rules_t const *t_rules, CONF_ITEM *ci,
- UNUSED char const *section_name1, UNUSED char const *section_name2,
- UNUSED void const *data, UNUSED call_env_parser_t const *rule)
+ UNUSED call_env_ctx_t const *cec, UNUSED call_env_parser_t const *rule)
{
CONF_PAIR const *to_parse = cf_item_to_pair(ci);
tmpl_t *parsed_tmpl = NULL;
}
redundant redundant_test {
- test1.passthrough
- test2.passthrough
+ test1
+ test2
}
}
#
# The config has a "redundant" block for test1 and test2.
#
-if (!(%concat(%redundant_test(foo, bar), '|') == "foo|bar")) {
+if (!(%concat(%redundant_test.passthrough(foo, bar), '|') == "foo|bar")) {
test_fail
}