We will hope that the packet names are different in every protocol.
The processing modules can now export a list of _additional_ methods
-that they take. The `*module_by_name_and_method()` function walks down
+that they take. The `*module_rlm_by_name_and_method()` function walks down
that list:
* if method[COMPONTENT] is set, then return that
/** Configuration parameters for a redis connection
*
- * @note should be passed as instance data to #module_connection_pool_init.
+ * @note should be passed as instance data to #module_rlm_connection_pool_init.
*/
typedef struct {
char const **hostname; //!< of Redis server.
*
* After this step, all dynamic attributes, xlats, etc. are defined.
*/
- if (modules_bootstrap(cs) < 0) return -1;
+ if (modules_rlm_bootstrap(cs) < 0) return -1;
/*
* And then load the virtual servers.
map_proc.c \
method.c \
module.c \
+ module_rlm.c \
paircmp.c \
pairmove.c \
password.c \
$(call DEFINE_LOG_ID_SECTION,config, 1,cf_file.c cf_parse.c cf_util.c)
$(call DEFINE_LOG_ID_SECTION,conditions,2,conf_eval.c cond_tokenize.c)
$(call DEFINE_LOG_ID_SECTION,exec, 3,exec.c exec_legacy.c)
-$(call DEFINE_LOG_ID_SECTION,modules, 4,dl_module.c module.c method.c)
+$(call DEFINE_LOG_ID_SECTION,modules, 4,dl_module.c module.c module_rlm.c method.c)
$(call DEFINE_LOG_ID_SECTION,map, 5,map.c map_proc.c map_async.c)
$(call DEFINE_LOG_ID_SECTION,snmp, 6,snmp.c)
$(call DEFINE_LOG_ID_SECTION,templates, 7,tmpl_eval.c tmpl_tokenize.c)
#ifdef __cplusplus
extern "C" {
#endif
-int module_sibling_section_find(CONF_SECTION **out, CONF_SECTION *module, char const *name);
+
+extern fr_cmd_table_t module_cmd_table[];
+
+extern fr_cmd_table_t module_cmd_list_table[];
+
+int module_instantiate(void *instance);
+
+int module_rlm_sibling_section_find(CONF_SECTION **out, CONF_SECTION *module, char const *name);
int unlang_fixup_update(map_t *map, void *ctx);
#include <freeradius-devel/server/cf_file.h>
#include <freeradius-devel/server/cond.h>
#include <freeradius-devel/server/modpriv.h>
+#include <freeradius-devel/server/module_rlm.h>
#include <freeradius-devel/server/radmin.h>
#include <freeradius-devel/server/request_data.h>
/*
* For simplicity, this is just array[instance_num]. Once we
- * finish with modules_bootstrap(), the "instance_num" above MUST
+ * finish with modules_rlm_bootstrap(), the "instance_num" above MUST
* NOT change.
*/
static _Thread_local module_thread_instance_t **module_thread_inst_array;
*/
static fr_rb_tree_t *module_instance_data_tree;
-/** Lookup virtual module by name
- */
-static fr_rb_tree_t *virtual_module_name_tree;
+static int cmd_show_module_config(FILE *fp, UNUSED FILE *fp_err, void *ctx, UNUSED fr_cmd_info_t const *info);
+static int module_name_tab_expand(UNUSED TALLOC_CTX *talloc_ctx, UNUSED void *uctx, fr_cmd_info_t *info, int max_expansions, char const **expansions);
+static int cmd_show_module_list(FILE *fp, UNUSED FILE *fp_err, UNUSED void *uctx, UNUSED fr_cmd_info_t const *info);
+static int cmd_show_module_status(FILE *fp, UNUSED FILE *fp_err, void *ctx, UNUSED fr_cmd_info_t const *info);
+static int cmd_set_module_status(UNUSED FILE *fp, FILE *fp_err, void *ctx, fr_cmd_info_t const *info);
-typedef struct {
- fr_rb_node_t name_node; //!< Entry in the name tree.
- char const *name; //!< module name
- CONF_SECTION *cs; //!< CONF_SECTION where it is defined
- bool all_same;
-} virtual_module_t;
+fr_cmd_table_t module_cmd_table[] = {
+ {
+ .parent = "show module",
+ .add_name = true,
+ .name = "status",
+ .func = cmd_show_module_status,
+ .help = "Show the status of a particular module.",
+ .read_only = true,
+ },
+ {
+ .parent = "show module",
+ .add_name = true,
+ .name = "config",
+ .func = cmd_show_module_config,
+ .help = "Show configuration for a module",
+ // @todo - do tab expand, by walking over the whole module list...
+ .read_only = true,
+ },
-/** Module command table
- */
-static fr_cmd_table_t cmd_module_table[];
+ {
+ .parent = "set module",
+ .add_name = true,
+ .name = "status",
+ .syntax = "(alive|disallow|fail|reject|handled|invalid|notfound|noop|ok|updated)",
+ .func = cmd_set_module_status,
+ .help = "Change module status to fixed value.",
+ .read_only = false,
+ },
-static int _module_instantiate(void *instance);
+ CMD_TABLE_END
+};
-static int virtual_module_bootstrap(CONF_SECTION *vm_cs);
+fr_cmd_table_t module_cmd_list_table[] = {
+ {
+ .parent = "show",
+ .name = "module",
+ .help = "Show information about modules.",
+ .tab_expand = module_name_tab_expand,
+ .read_only = true,
+ },
-/*
- * Ordered by component
- */
-const char *section_type_value[MOD_COUNT] = {
- "authenticate",
- "authorize",
- "preacct",
- "accounting",
- "post-auth"
-};
+ // @todo - what if there's a module called "list" ?
+ {
+ .parent = "show module",
+ .name = "list",
+ .func = cmd_show_module_list,
+ .help = "Show the list of modules loaded in the server.",
+ .read_only = true,
+ },
+
+ {
+ .parent = "set",
+ .name = "module",
+ .help = "Change module settings.",
+ .tab_expand = module_name_tab_expand,
+ .read_only = false,
+ },
+ CMD_TABLE_END
+};
+
static int cmd_show_module_config(FILE *fp, UNUSED FILE *fp_err, void *ctx, UNUSED fr_cmd_info_t const *info)
{
module_instance_t *mi = ctx;
return 0;
}
-
-static fr_cmd_table_t cmd_module_table[] = {
- {
- .parent = "show module",
- .add_name = true,
- .name = "status",
- .func = cmd_show_module_status,
- .help = "Show the status of a particular module.",
- .read_only = true,
- },
-
- {
- .parent = "show module",
- .add_name = true,
- .name = "config",
- .func = cmd_show_module_config,
- .help = "Show configuration for a module",
- // @todo - do tab expand, by walking over the whole module list...
- .read_only = true,
- },
-
- {
- .parent = "set module",
- .add_name = true,
- .name = "status",
- .syntax = "(alive|disallow|fail|reject|handled|invalid|notfound|noop|ok|updated)",
- .func = cmd_set_module_status,
- .help = "Change module status to fixed value.",
- .read_only = false,
- },
-
- CMD_TABLE_END
-};
-
-
-static fr_cmd_table_t cmd_table[] = {
- {
- .parent = "show",
- .name = "module",
- .help = "Show information about modules.",
- .tab_expand = module_name_tab_expand,
- .read_only = true,
- },
-
- // @todo - what if there's a module called "list" ?
- {
- .parent = "show module",
- .name = "list",
- .func = cmd_show_module_list,
- .help = "Show the list of modules loaded in the server.",
- .read_only = true,
- },
-
- {
- .parent = "set",
- .name = "module",
- .help = "Change module settings.",
- .tab_expand = module_name_tab_expand,
- .read_only = false,
- },
-
-
- CMD_TABLE_END
-};
-
/** Compare module instances by parent and name
*
* The reason why we need parent, is because we could have submodules with names
module_instance_t const *b = two;
dl_module_inst_t const *dl_inst;
int a_depth = 0, b_depth = 0;
- int ret;
-
- /*
- * Sort by depth, so for tree walking we start
- * at the shallowest node, and finish with
- * the deepest child.
- */
- for (dl_inst = a->dl_inst; dl_inst; dl_inst = dl_inst->parent) a_depth++;
- for (dl_inst = b->dl_inst; dl_inst; dl_inst = dl_inst->parent) b_depth++;
-
- ret = CMP(a_depth, b_depth);
- if (ret != 0) return ret;
-
- /*
- * This happens, as dl_inst is is used in
- * as the loop condition above.
- */
-#ifdef __clang_analyzer__
- if (!fr_cond_assert(a->dl_inst)) return +1;
- if (!fr_cond_assert(b->dl_inst)) return -1;
-#endif
-
- ret = CMP(a->dl_inst->parent, b->dl_inst->parent);
- if (ret != 0) return ret;
-
- ret = strcmp(a->name, b->name);
- return CMP(ret, 0);
-}
-
-/** Compare module's by their private instance data
- *
- */
-static int8_t module_instance_data_cmp(void const *one, void const *two)
-{
- void const *a = (((module_instance_t const *)one)->dl_inst)->data;
- void const *b = (((module_instance_t const *)two)->dl_inst)->data;
-
- return CMP(a, b);
-}
-
-/** Compare virtual modules by name
- */
-static int8_t virtual_module_name_cmp(void const *one, void const *two)
-{
- virtual_module_t const *a = one;
- virtual_module_t const *b = two;
- int ret;
-
- ret = strcmp(a->name, b->name);
- return CMP(ret, 0);
-}
-
-/** Initialise a module specific exfile handle
- *
- * @see exfile_init
- *
- * @param[in] ctx to bind the lifetime of the exfile handle to.
- * @param[in] module section.
- * @param[in] max_entries Max file descriptors to cache, and manage locks for.
- * @param[in] max_idle Maximum time a file descriptor can be idle before it's closed.
- * @param[in] locking Whether or not to lock the files.
- * @param[in] trigger_prefix if NULL will be set automatically from the module CONF_SECTION.
- * @param[in] trigger_args to make available in any triggers executed by the connection pool.
- * @return
- * - New connection pool.
- * - NULL on error.
- */
-exfile_t *module_exfile_init(TALLOC_CTX *ctx,
- CONF_SECTION *module,
- uint32_t max_entries,
- fr_time_delta_t max_idle,
- bool locking,
- char const *trigger_prefix,
- fr_pair_list_t *trigger_args)
-{
- char trigger_prefix_buff[128];
- exfile_t *handle;
-
- if (!trigger_prefix) {
- snprintf(trigger_prefix_buff, sizeof(trigger_prefix_buff), "modules.%s.file", cf_section_name1(module));
- trigger_prefix = trigger_prefix_buff;
- }
-
- handle = exfile_init(ctx, max_entries, max_idle, locking);
- if (!handle) return NULL;
-
- exfile_enable_triggers(handle, cf_section_find(module, "file", NULL), trigger_prefix, trigger_args);
-
- return handle;
-}
-
-/** Resolve polymorphic item's from a module's #CONF_SECTION to a subsection in another module
- *
- * This allows certain module sections to reference module sections in other instances
- * of the same module and share #CONF_DATA associated with them.
- *
- * @verbatim
- example {
- data {
- ...
- }
- }
-
- example inst {
- data = example
- }
- * @endverbatim
- *
- * @param[out] out where to write the pointer to a module's config section. May be NULL on success,
- * indicating the config item was not found within the module #CONF_SECTION
- * or the chain of module references was followed and the module at the end of the chain
- * did not a subsection.
- * @param[in] module #CONF_SECTION.
- * @param[in] name of the polymorphic sub-section.
- * @return
- * - 0 on success with referenced section.
- * - 1 on success with local section.
- * - -1 on failure.
- */
-int module_sibling_section_find(CONF_SECTION **out, CONF_SECTION *module, char const *name)
-{
- CONF_PAIR *cp;
- CONF_SECTION *cs;
- CONF_DATA const *cd;
-
-
- module_instance_t *mi;
- char const *inst_name;
-
-#define FIND_SIBLING_CF_KEY "find_sibling"
-
- *out = NULL;
-
- /*
- * Is a real section (not referencing sibling module).
- */
- cs = cf_section_find(module, name, NULL);
- if (cs) {
- *out = cs;
-
- return 0;
- }
-
- /*
- * Item omitted completely from module config.
- */
- cp = cf_pair_find(module, name);
- if (!cp) return 0;
-
- if (cf_data_find(module, CONF_SECTION, FIND_SIBLING_CF_KEY)) {
- cf_log_err(cp, "Module reference loop found");
-
- return -1;
- }
- cd = cf_data_add(module, module, FIND_SIBLING_CF_KEY, false);
-
- /*
- * Item found, resolve it to a module instance.
- * This triggers module loading, so we don't have
- * instantiation order issues.
- */
- inst_name = cf_pair_value(cp);
- mi = module_by_name(NULL, inst_name);
- if (!mi) {
- cf_log_err(cp, "Unknown module instance \"%s\"", inst_name);
-
- return -1;
- }
-
- if (!mi->instantiated) {
- CONF_SECTION *parent = module;
-
- /*
- * Find the root of the config...
- */
- do {
- CONF_SECTION *tmp;
-
- tmp = cf_item_to_section(cf_parent(parent));
- if (!tmp) break;
-
- parent = tmp;
- } while (true);
-
- _module_instantiate(module_by_name(NULL, inst_name));
- }
-
- /*
- * Remove the config data we added for loop
- * detection.
- */
- cf_data_remove(module, cd);
-
- /*
- * Check the module instances are of the same type.
- */
- if (strcmp(cf_section_name1(mi->dl_inst->conf), cf_section_name1(module)) != 0) {
- cf_log_err(cp, "Referenced module is a rlm_%s instance, must be a rlm_%s instance",
- cf_section_name1(mi->dl_inst->conf), cf_section_name1(module));
-
- return -1;
- }
-
- *out = cf_section_find(mi->dl_inst->conf, name, NULL);
-
- return 1;
-}
-
-/** Initialise a module specific connection pool
- *
- * @see fr_pool_init
- *
- * @param[in] module section.
- * @param[in] opaque data pointer to pass to callbacks.
- * @param[in] c Callback to create new connections.
- * @param[in] a Callback to check the status of connections.
- * @param[in] log_prefix override, if NULL will be set automatically from the module CONF_SECTION.
- * @param[in] trigger_prefix if NULL will be set automatically from the module CONF_SECTION.
- * @param[in] trigger_args to make available in any triggers executed by the connection pool.
- * @return
- * - New connection pool.
- * - NULL on error.
- */
-fr_pool_t *module_connection_pool_init(CONF_SECTION *module,
- void *opaque,
- fr_pool_connection_create_t c,
- fr_pool_connection_alive_t a,
- char const *log_prefix,
- char const *trigger_prefix,
- fr_pair_list_t *trigger_args)
-{
- CONF_SECTION *cs, *mycs;
- char log_prefix_buff[128];
- char trigger_prefix_buff[128];
-
- fr_pool_t *pool;
- char const *cs_name1, *cs_name2;
-
- int ret;
-
-#define parent_name(_x) cf_section_name(cf_item_to_section(cf_parent(_x)))
-
- cs_name1 = cf_section_name1(module);
- cs_name2 = cf_section_name2(module);
- if (!cs_name2) cs_name2 = cs_name1;
-
- if (!trigger_prefix) {
- snprintf(trigger_prefix_buff, sizeof(trigger_prefix_buff), "modules.%s.pool", cs_name1);
- trigger_prefix = trigger_prefix_buff;
- }
-
- if (!log_prefix) {
- snprintf(log_prefix_buff, sizeof(log_prefix_buff), "rlm_%s (%s)", cs_name1, cs_name2);
- log_prefix = log_prefix_buff;
- }
-
- /*
- * Get sibling's pool config section
- */
- ret = module_sibling_section_find(&cs, module, "pool");
- switch (ret) {
- case -1:
- return NULL;
-
- case 1:
- DEBUG4("%s: Using pool section from \"%s\"", log_prefix, parent_name(cs));
- break;
-
- case 0:
- DEBUG4("%s: Using local pool section", log_prefix);
- break;
- }
-
- /*
- * Get our pool config section
- */
- mycs = cf_section_find(module, "pool", NULL);
- if (!mycs) {
- DEBUG4("%s: Adding pool section to config item \"%s\" to store pool references", log_prefix,
- cf_section_name(module));
-
- mycs = cf_section_alloc(module, module, "pool", NULL);
- }
-
- /*
- * Sibling didn't have a pool config section
- * Use our own local pool.
- */
- if (!cs) {
- DEBUG4("%s: \"%s.pool\" section not found, using \"%s.pool\"", log_prefix,
- parent_name(cs), parent_name(mycs));
- cs = mycs;
- }
-
- /*
- * If fr_pool_init has already been called
- * for this config section, reuse the previous instance.
- *
- * This allows modules to pass in the config sections
- * they would like to use the connection pool from.
- */
- pool = cf_data_value(cf_data_find(cs, fr_pool_t, NULL));
- if (!pool) {
- DEBUG4("%s: No pool reference found for config item \"%s.pool\"", log_prefix, parent_name(cs));
- pool = fr_pool_init(cs, cs, opaque, c, a, log_prefix);
- if (!pool) return NULL;
-
- fr_pool_enable_triggers(pool, trigger_prefix, trigger_args);
-
- if (fr_pool_start(pool) < 0) {
- ERROR("%s: Starting initial connections failed", log_prefix);
- return NULL;
- }
-
- DEBUG4("%s: Adding pool reference %p to config item \"%s.pool\"", log_prefix, pool, parent_name(cs));
- cf_data_add(cs, pool, NULL, false);
- return pool;
- }
- fr_pool_ref(pool);
-
- DEBUG4("%s: Found pool reference %p in config item \"%s.pool\"", log_prefix, pool, parent_name(cs));
-
- /*
- * We're reusing pool data add it to our local config
- * section. This allows other modules to transitively
- * re-use a pool through this module.
- */
- if (mycs != cs) {
- DEBUG4("%s: Copying pool reference %p from config item \"%s.pool\" to config item \"%s.pool\"",
- log_prefix, pool, parent_name(cs), parent_name(mycs));
- cf_data_add(mycs, pool, NULL, false);
- }
-
- return pool;
-}
-
-
-/*
- * Convert a string to an integer
- */
-module_method_t module_state_str_to_method(module_state_func_table_t const *table,
- char const *name, module_method_t def)
-{
- module_state_func_table_t const *this;
-
- if (!name) return def;
-
- for (this = table; this->name != NULL; this++) {
- if (strcasecmp(this->name, name) == 0) return this->func;
- }
-
- return def;
-}
-
-/*
- * Convert an integer to a string.
- */
-char const *module_state_method_to_str(module_state_func_table_t const *table,
- module_method_t method, char const *def)
-{
- module_state_func_table_t const *this;
-
- for (this = table; this->name != NULL; this++) if (this->func == method) return this->name;
-
- return def;
-}
-
-/** Set the next section type if it's not already set
- *
- * @param[in] request The current request.
- * @param[in] type_da to use. Usually attr_auth_type.
- * @param[in] enumv Enumeration value of the specified type_da.
- */
-bool module_section_type_set(request_t *request, fr_dict_attr_t const *type_da, fr_dict_enum_value_t const *enumv)
-{
- fr_pair_t *vp;
-
- switch (pair_update_control(&vp, type_da)) {
- case 0:
- fr_value_box_copy(vp, &vp->data, enumv->value);
- vp->data.enumv = vp->da; /* So we get the correct string alias */
- RDEBUG2("Setting &control.%pP", vp);
- return true;
-
- case 1:
- RDEBUG2("&control.%s already set. Not setting to %s", vp->da->name, enumv->name);
- return false;
-
- default:
- return false;
- }
-}
-
-/** Find an existing module instance by its name and parent
- *
- * @param[in] parent to qualify search with.
- * @param[in] asked_name The name of the module we're attempting to find.
- * May include '-' which indicates that it's ok for
- * the module not to be loaded.
- * @return
- * - Module instance matching name.
- * - NULL if no such module exists.
- */
-module_instance_t *module_by_name(module_instance_t const *parent, char const *asked_name)
-{
- char const *inst_name;
- void *inst;
-
- if (!module_instance_name_tree) return NULL;
-
- /*
- * Look for the real name. Ignore the first character,
- * which tells the server "it's OK for this module to not
- * exist."
- */
- inst_name = asked_name;
- if (inst_name[0] == '-') inst_name++;
-
- inst = fr_rb_find(module_instance_name_tree,
- &(module_instance_t){
- .dl_inst = &(dl_module_inst_t){ .parent = parent ? parent->dl_inst : NULL },
- .name = inst_name
- });
- if (!inst) return NULL;
-
- return talloc_get_type_abort(inst, module_instance_t);
-}
-
-/** 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.
- *
- * @param[out] method the method function we will call
- * @param[in,out] component the default component to use. Updated to be the found component
- * @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
- * @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.
- */
-module_instance_t *module_by_name_and_method(module_method_t *method, rlm_components_t *component,
- char const **name1, char const **name2,
- char const *name)
-{
- char *p, *q, *inst_name;
- size_t len;
- int j;
- rlm_components_t i;
- module_instance_t *mi;
- module_method_names_t const *methods;
- char const *method_name1, *method_name2;
-
- if (method) *method = NULL;
-
- method_name1 = method_name2 = NULL;
- if (name1) {
- method_name1 = *name1;
- *name1 = NULL;
- }
- if (name2) {
- method_name2 = *name2;
- *name2 = NULL;
- }
-
- /*
- * Module names are allowed to contain '.'
- * so we search for the bare module name first.
- */
- mi = module_by_name(NULL, name);
- if (mi) {
- virtual_server_method_t const *allowed_list;
-
- if (!method) return mi;
-
- /*
- * 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 || !mi->module->method_names) goto return_component;
-
- /*
- * Walk through the module, finding a matching
- * method.
- */
- for (j = 0; mi->module->method_names[j].name1 != NULL; j++) {
- methods = &mi->module->method_names[j];
-
- /*
- * Wildcard match name1, we're
- * done.
- */
- if (methods->name1 == CF_IDENT_ANY) {
- found:
- *method = methods->method;
- if (name1) *name1 = method_name1;
- if (name2) *name2 = method_name2;
- return mi;
- }
-
- /*
- * If name1 doesn't match, skip it.
- */
- if (strcmp(methods->name1, method_name1) != 0) continue;
-
- /*
- * The module can declare a
- * wildcard for name2, in which
- * case it's a match.
- */
- if (methods->name2 == CF_IDENT_ANY) goto found;
-
- /*
- * No name2 is also a match to no name2.
- */
- if (!methods->name2 && !method_name2) goto found;
-
- /*
- * Don't do strcmp on NULLs
- */
- if (!methods->name2 || !method_name2) continue;
-
- if (strcmp(methods->name2, method_name2) == 0) goto found;
- }
-
- /*
- * 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(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 shoul 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].name != NULL; j++) {
- int k;
- virtual_server_method_t const *allowed = &allowed_list[j];
-
- for (k = 0; mi->module->method_names[k].name1 != NULL; k++) {
- methods = &mi->module->method_names[k];
-
- fr_assert(methods->name1 != CF_IDENT_ANY); /* should have been caught above */
-
- if (strcmp(methods->name1, allowed->name) != 0) continue;
-
- /*
- * The module matches "recv *",
- * call this method.
- */
- if (methods->name2 == CF_IDENT_ANY) {
- found_allowed:
- *method = methods->method;
- return mi;
- }
-
- /*
- * No name2 is also a match to no name2.
- */
- if (!methods->name2 && !allowed->name2) goto found_allowed;
-
- /*
- * Don't do strcmp on NULLs
- */
- if (!methods->name2 || !allowed->name2) continue;
-
- if (strcmp(methods->name2, allowed->name2) == 0) goto found_allowed;
- }
- }
-
- return_component:
- /*
- * No matching method. Just return a method
- * based on the component.
- */
- if (component && mi->module->methods[*component]) {
- *method = mi->module->methods[*component];
- }
-
- /*
- * Didn't find a matching method. Just return
- * the module.
- */
- return mi;
- }
-
- /*
- * Find out if the instance name contains
- * a method, if it doesn't, then the module
- * doesn't exist.
- */
- p = strchr(name, '.');
- if (!p) return NULL;
-
- /*
- * 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);
-
- /*
- * Loop over the '.' portions, gradually looking up a
- * longer string, in order to find the full module name.
- */
- do {
- *p = '\0';
-
- mi = module_by_name(NULL, inst_name);
- if (mi) break;
-
- /*
- * Find the next '.'
- */
- *p = '.';
- p = strchr(p + 1, '.');
- } while (p);
-
- /*
- * No such module, we're done.
- */
- if (!mi) {
- talloc_free(inst_name);
- return NULL;
- }
-
- /*
- * We have a module, but the caller doesn't care about
- * method or names, so just return the module.
- */
- if (!method || !method_name1 || !method_name2) {
- talloc_free(inst_name);
- return mi;
- }
-
- /*
- * We MAY have two names.
- */
- p++;
- q = strchr(p, '.');
-
- /*
- * If there's only one component, look for it in the
- * "authorize", etc. list first.
- */
- if (!q) {
- for (i = MOD_AUTHENTICATE; i < MOD_COUNT; i++) {
- if (strcmp(section_type_value[i], p) != 0) continue;
-
- /*
- * Tell the caller which component was
- * referenced, and set the method to the found
- * function.
- */
- if (component) {
- *component = i;
- if (method) *method = mi->module->methods[*component];
- }
-
- /*
- * The string matched. Return it. Also set the
- * names so that the caller gets told the method
- * name being used.
- */
- *name1 = name + (p - inst_name);
- *name2 = NULL;
- talloc_free(inst_name);
- return mi;
- }
- }
-
- /*
- * We've found the module, but it has no named methods.
- */
- if (!mi->module->method_names) {
- *name1 = name + (p - inst_name);
- *name2 = NULL;
- talloc_free(inst_name);
- return mi;
- }
-
- /*
- * We have "module.METHOD", but METHOD doesn't match
- * "authorize", "authenticate", etc. Let's see if it
- * matches anything else.
- */
- if (!q) {
- for (j = 0; mi->module->method_names[j].name1 != NULL; j++) {
- methods = &mi->module->method_names[j];
-
- /*
- * If we do not have the second $method, then ignore it!
- */
- if (methods->name2 && (methods->name2 != CF_IDENT_ANY)) continue;
-
- /*
- * Wildcard match name1, we're
- * done.
- */
- if (!methods->name1 || (methods->name1 == CF_IDENT_ANY)) goto found_name1;
-
- /*
- * If name1 doesn't match, skip it.
- */
- if (strcmp(methods->name1, p) != 0) continue;
-
- found_name1:
- /*
- * We've matched "*", or "name1" or
- * "name1 *". Return that.
- */
- *name1 = p;
- *name2 = NULL;
- *method = methods->method;
- break;
- }
-
- /*
- * Return the found module.
- */
- talloc_free(inst_name);
- return mi;
- }
+ int ret;
/*
- * We CANNOT have '.' in method names.
+ * Sort by depth, so for tree walking we start
+ * at the shallowest node, and finish with
+ * the deepest child.
*/
- if (strchr(q + 1, '.') != 0) {
- talloc_free(inst_name);
- return mi;
- }
+ for (dl_inst = a->dl_inst; dl_inst; dl_inst = dl_inst->parent) a_depth++;
+ for (dl_inst = b->dl_inst; dl_inst; dl_inst = dl_inst->parent) b_depth++;
- len = q - p;
+ ret = CMP(a_depth, b_depth);
+ if (ret != 0) return ret;
/*
- * Trim the '.'.
+ * This happens, as dl_inst is is used in
+ * as the loop condition above.
*/
- if (*q == '.' && *(q + 1)) q++;
+#ifdef __clang_analyzer__
+ if (!fr_cond_assert(a->dl_inst)) return +1;
+ if (!fr_cond_assert(b->dl_inst)) return -1;
+#endif
- /*
- * We have "module.METHOD1.METHOD2".
- *
- * Loop over the method names, seeing if we have a match.
- */
- for (j = 0; mi->module->method_names[j].name1 != NULL; j++) {
- methods = &mi->module->method_names[j];
+ ret = CMP(a->dl_inst->parent, b->dl_inst->parent);
+ if (ret != 0) return ret;
- /*
- * If name1 doesn't match, skip it.
- */
- if (strncmp(methods->name1, p, len) != 0) continue;
+ ret = strcmp(a->name, b->name);
+ return CMP(ret, 0);
+}
- /*
- * 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->name1) != len) continue;
+/** Compare module's by their private instance data
+ *
+ */
+static int8_t module_instance_data_cmp(void const *one, void const *two)
+{
+ void const *a = (((module_instance_t const *)one)->dl_inst)->data;
+ void const *b = (((module_instance_t const *)two)->dl_inst)->data;
- /*
- * The module can declare a
- * wildcard for name2, in which
- * case it's a match.
- */
- if (!methods->name2 || (methods->name2 == CF_IDENT_ANY)) goto found_name2;
+ return CMP(a, b);
+}
- /*
- * Don't do strcmp on NULLs
- */
- if (!methods->name2) continue;
+/** Find an existing module instance by its name and parent
+ *
+ * @param[in] parent to qualify search with.
+ * @param[in] asked_name The name of the module we're attempting to find.
+ * May include '-' which indicates that it's ok for
+ * the module not to be loaded.
+ * @return
+ * - Module instance matching name.
+ * - NULL if no such module exists.
+ */
+module_instance_t *module_by_name(module_instance_t const *parent, char const *asked_name)
+{
+ char const *inst_name;
+ void *inst;
- if (strcmp(methods->name2, q) != 0) continue;
+ if (!module_instance_name_tree) return NULL;
- found_name2:
- /*
- * Update name1/name2 with the methods
- * that were found.
- */
- *name1 = methods->name1;
- *name2 = name + (q - inst_name);
- *method = methods->method;
- break;
- }
+ /*
+ * Look for the real name. Ignore the first character,
+ * which tells the server "it's OK for this module to not
+ * exist."
+ */
+ inst_name = asked_name;
+ if (inst_name[0] == '-') inst_name++;
- *name1 = name + (p - inst_name);
- *name2 = NULL;
+ inst = fr_rb_find(module_instance_name_tree,
+ &(module_instance_t){
+ .dl_inst = &(dl_module_inst_t){ .parent = parent ? parent->dl_inst : NULL },
+ .name = inst_name
+ });
+ if (!inst) return NULL;
- talloc_free(inst_name);
- return mi;
+ return talloc_get_type_abort(inst, module_instance_t);
}
/** Find an existing module instance by its private instance data
talloc_free(mi);
}
-
-/** Free all modules loaded by the server
- */
-void modules_free(void)
-{
- if (module_instance_name_tree) {
- fr_rb_iter_inorder_t iter;
- module_instance_t *mi;
-
- for (mi = fr_rb_iter_init_inorder(&iter, module_instance_name_tree);
- mi;
- mi = fr_rb_iter_next_inorder(&iter)) {
- mi->in_name_tree = false; /* about to be deleted */
- mi->in_data_tree = false;
-
- fr_rb_iter_delete_inorder(&iter);
- fr_rb_remove(module_instance_data_tree, mi);
-
- talloc_free(mi);
- }
- TALLOC_FREE(module_instance_name_tree);
- }
-
- TALLOC_FREE(module_instance_data_tree);
- TALLOC_FREE(virtual_module_name_tree);
- TALLOC_FREE(instance_ctx);
-}
-
-int modules_init(void)
-{
- MEM(module_instance_name_tree = fr_rb_inline_alloc(NULL, module_instance_t, name_node,
- module_instance_name_cmp, NULL));
- MEM(module_instance_data_tree = fr_rb_inline_alloc(NULL, module_instance_t, data_node,
- module_instance_data_cmp, NULL));
- MEM(virtual_module_name_tree = fr_rb_inline_alloc(NULL, virtual_module_t, name_node,
- virtual_module_name_cmp, NULL));
- instance_ctx = talloc_init("module instance context");
-
- return 0;
-}
-
/** Destructor for module_thread_instance_t array
*/
static int _module_thread_inst_array_free(module_thread_instance_t **array)
* - 0 on success.
* - -1 on failure.
*/
-static int _module_instantiate(void *instance)
+int module_instantiate(void *instance)
{
module_instance_t *mi = talloc_get_type_abort(instance, module_instance_t);
if (mi->instantiated) return 0;
- if (fr_command_register_hook(NULL, mi->name, mi, cmd_module_table) < 0) {
+ if (fr_command_register_hook(NULL, mi->name, mi, module_cmd_table) < 0) {
PERROR("Failed registering radmin commands for module %s", mi->name);
return -1;
}
for (instance = fr_rb_iter_init_inorder(&iter, module_instance_name_tree);
instance;
instance = fr_rb_iter_next_inorder(&iter)) {
- if (_module_instantiate(instance) < 0) {
- return -1;
- }
+ if (module_instantiate(instance) < 0) return -1;
}
return 0;
char *inst_name = NULL;
module_instance_t *mi;
char const *name1 = cf_section_name1(cs);
- CONF_SECTION *actions;
module_instance_name(NULL, &inst_name, parent, cs);
}
}
- /*
- * Compile the default "actions" subsection, which includes retries.
- */
- actions = cf_section_find(cs, "actions", NULL);
- if (actions && unlang_compile_actions(&mi->actions, actions, (mi->module->type & RLM_TYPE_RETRY) != 0)) {
- talloc_free(mi);
- return NULL;
- }
-
return mi;
}
-CONF_SECTION *module_by_name_virtual(char const *asked_name)
-{
- virtual_module_t *inst;
-
- inst = fr_rb_find(virtual_module_name_tree,
- &(virtual_module_t){
- .name = asked_name,
- });
- if (!inst) return NULL;
-
- return inst->cs;
-}
-
-/** Create a virtual module.
- *
- * @param[in] cs that defines the virtual module.
- * @return
- * - 0 on success.
- * - -1 on failure.
+/** Free all modules loaded by the server
*/
-static int virtual_module_bootstrap(CONF_SECTION *cs)
+void modules_free(void)
{
- char const *name;
- bool all_same;
- module_t const *last = NULL;
- CONF_ITEM *sub_ci = NULL;
- CONF_PAIR *cp;
- module_instance_t *mi;
- virtual_module_t *inst;
-
- name = cf_section_name1(cs);
-
- /*
- * Groups, etc. must have a name.
- */
- if ((strcmp(name, "group") == 0) ||
- (strcmp(name, "redundant") == 0) ||
- (strcmp(name, "redundant-load-balance") == 0) ||
- (strcmp(name, "load-balance") == 0)) {
- name = cf_section_name2(cs);
- if (!name) {
- cf_log_err(cs, "Keyword module must have a second name");
- return -1;
- }
-
- /*
- * name2 was already checked in modules_bootstrap()
- */
- fr_assert(!unlang_compile_is_keyword(name));
- } else {
- cf_log_err(cs, "Module names cannot be unlang keywords '%s'", name);
- return -1;
- }
-
- /*
- * Ensure that the module doesn't exist.
- */
- mi = module_by_name(NULL, name);
- if (mi) {
- ERROR("Duplicate module \"%s\" in file %s[%d] and file %s[%d]",
- name,
- cf_filename(cs),
- cf_lineno(cs),
- cf_filename(mi->dl_inst->conf),
- cf_lineno(mi->dl_inst->conf));
- return -1;
- }
-
- /*
- * Don't bother registering redundant xlats for a simple "group".
- */
- all_same = (strcmp(cf_section_name1(cs), "group") != 0);
+ if (module_instance_name_tree) {
+ fr_rb_iter_inorder_t iter;
+ module_instance_t *mi;
- /*
- * 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;
- }
+ for (mi = fr_rb_iter_init_inorder(&iter, module_instance_name_tree);
+ mi;
+ mi = fr_rb_iter_next_inorder(&iter)) {
+ mi->in_name_tree = false; /* about to be deleted */
+ mi->in_data_tree = false;
- /*
- * 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_by_name_and_method(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;
- }
+ fr_rb_iter_delete_inorder(&iter);
+ fr_rb_remove(module_instance_data_tree, mi);
- if (all_same) {
- if (!last) {
- last = mi->module;
- } else if (last != mi->module) {
- last = NULL;
- all_same = false;
- }
- }
- } else {
- all_same = false;
+ talloc_free(mi);
}
-
- /*
- * 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, virtual_module_t);
- if (!inst) return -1;
-
- inst->cs = cs;
- inst->name = talloc_strdup(inst, name);
- inst->all_same = all_same;
-
- if (!fr_cond_assert(fr_rb_insert(virtual_module_name_tree, inst))) {
- talloc_free(inst);
- return -1;
+ TALLOC_FREE(module_instance_name_tree);
}
-
- return 0;
+ TALLOC_FREE(module_instance_data_tree);
+ modules_rlm_init();
+ TALLOC_FREE(instance_ctx);
}
-
-/** Bootstrap modules and virtual modules
+/** Allocate the global module tree
*
- * Parse the module config sections, and load and call each module's init() function.
- *
- * @param[in] root of the server configuration.
- * @return
- * - 0 if all modules were bootstrapped successfully.
- * - -1 if a module/virtual module failed to boostrap.
+ * This allocates all the trees necessary to hold module name and module instance data,
+ * as well as the main ctx all module data gets allocated in.
*/
-int modules_bootstrap(CONF_SECTION *root)
+int modules_init(void)
{
- CONF_ITEM *ci;
- CONF_SECTION *cs, *modules;
- virtual_module_t *vm;
- fr_rb_iter_inorder_t iter;
-
- /*
- * Remember where the modules were stored.
- */
- modules = cf_section_find(root, "modules", NULL);
- if (!modules) {
- WARN("Cannot find a \"modules\" section in the configuration file!");
- return 0;
- }
-
- DEBUG2("#### Bootstrapping modules ####");
-
- cf_log_debug(modules, " modules {");
-
- /*
- * Loop over module definitions, looking for duplicates.
- *
- * This is O(N^2) in the number of modules, but most
- * systems should have less than 100 modules.
- */
- for (ci = cf_item_next(modules, NULL);
- ci != NULL;
- ci = cf_item_next(modules, ci)) {
- char const *name;
- CONF_SECTION *subcs;
- module_instance_t *instance;
-
- /*
- * @todo - maybe this should be a warning?
- */
- if (!cf_item_is_section(ci)) continue;
-
- subcs = cf_item_to_section(ci);
-
- /*
- * name2 can't be a keyword
- */
- name = cf_section_name2(subcs);
- if (name && unlang_compile_is_keyword(name)) {
- invalid_name:
- cf_log_err(subcs, "Module names cannot be unlang keywords '%s'", name);
- return -1;
- }
-
- name = cf_section_name1(subcs);
-
- /*
- * For now, ignore name1 which is a keyword.
- */
- if (unlang_compile_is_keyword(name)) {
- if (!cf_section_name2(subcs)) {
- cf_log_err(subcs, "Missing second name at '%s'", name);
- return -1;
- }
-
- if (virtual_module_bootstrap(subcs) < 0) return -1;
- continue;
- }
-
- /*
- * Skip inline templates, and disallow "template { ... }"
- */
- if (strcmp(name, "template") == 0) {
- if (!cf_section_name2(subcs)) goto invalid_name;
- continue;
- }
-
- instance = module_bootstrap(NULL, subcs);
- if (!instance) return -1;
- }
-
- cf_log_debug(modules, " } # modules");
-
- if (fr_command_register_hook(NULL, NULL, modules, cmd_table) < 0) {
- PERROR("Failed registering radmin commands for modules");
- return -1;
- }
-
- /*
- * Check for duplicate policies. They're treated as
- * modules, so we might as well check them here.
- */
- cs = cf_section_find(root, "policy", NULL);
- if (cs) {
- while ((ci = cf_item_next(cs, ci))) {
- CONF_SECTION *subcs, *problemcs;
- char const *name1;
-
- /*
- * Skip anything that isn't a section.
- */
- if (!cf_item_is_section(ci)) continue;
-
- subcs = cf_item_to_section(ci);
- name1 = cf_section_name1(subcs);
-
- if (unlang_compile_is_keyword(name1)) {
- cf_log_err(subcs, "Policy name '%s' cannot be an unlang keyword", name1);
- return -1;
- }
-
- if (cf_section_name2(subcs)) {
- cf_log_err(subcs, "Policies cannot have two names");
- return -1;
- }
-
- problemcs = cf_section_find_next(cs, subcs, name1, CF_IDENT_ANY);
- if (!problemcs) continue;
-
- cf_log_err(problemcs, "Duplicate policy '%s' is forbidden.",
- cf_section_name1(subcs));
- return -1;
- }
- }
-
- /*
- * Now that all of the xlat things have been registered,
- * register our redundant xlats. But only when all of
- * the items in such a section are the same.
- */
- for (vm = fr_rb_iter_init_inorder(&iter, virtual_module_name_tree);
- vm;
- vm = fr_rb_iter_next_inorder(&iter)) {
- if (!vm->all_same) continue;
-
- if (xlat_register_redundant(vm->cs) < 0) return -1;
- }
+ MEM(module_instance_name_tree = fr_rb_inline_alloc(NULL, module_instance_t, name_node,
+ module_instance_name_cmp, NULL));
+ MEM(module_instance_data_tree = fr_rb_inline_alloc(NULL, module_instance_t, data_node,
+ module_instance_data_cmp, NULL));
+ modules_rlm_init();
+ instance_ctx = talloc_init("module instance context");
return 0;
}
extern "C" {
#endif
-/** Mappings between section names, and control attributes
- *
- * Defined in module.c.
- */
-extern const char *section_type_value[MOD_COUNT];
-
/** Common fields for submodules
*
* This should either be the first field in the structure exported from
*
* @{
*/
-fr_pool_t *module_connection_pool_init(CONF_SECTION *module,
+fr_pool_t *module_rlm_connection_pool_init(CONF_SECTION *module,
void *opaque,
fr_pool_connection_create_t c,
fr_pool_connection_alive_t a,
char const *log_prefix,
char const *trigger_prefix,
fr_pair_list_t *trigger_args);
-exfile_t *module_exfile_init(TALLOC_CTX *ctx,
+exfile_t *module_rlm_exfile_init(TALLOC_CTX *ctx,
CONF_SECTION *module,
uint32_t max_entries,
fr_time_delta_t max_idle,
*
* @{
*/
-module_method_t module_state_str_to_method(module_state_func_table_t const *table,
+module_method_t module_rlm_state_str_to_method(module_state_func_table_t const *table,
char const *name, module_method_t def);
-char const *module_state_method_to_str(module_state_func_table_t const *table,
+char const *module_rlm_state_method_to_str(module_state_func_table_t const *table,
module_method_t method, char const *def);
-bool module_section_type_set(request_t *request, fr_dict_attr_t const *type_da, fr_dict_enum_value_t const *enumv);
+bool module_rlm_section_type_set(request_t *request, fr_dict_attr_t const *type_da, fr_dict_enum_value_t const *enumv);
/** @} */
/** @name Module and module thread lookup
*/
module_instance_t *module_by_name(module_instance_t const *parent, char const *asked_name);
-module_instance_t *module_by_name_and_method(module_method_t *method, rlm_components_t *component,
+module_instance_t *module_rlm_by_name_and_method(module_method_t *method, rlm_components_t *component,
char const **name1, char const **name2,
char const *asked_name);
module_thread_instance_t *module_thread_by_data(void const *data);
-CONF_SECTION *module_by_name_virtual(char const *asked_name);
+CONF_SECTION *module_rlm_by_name_virtual(char const *asked_name);
/** @} */
module_instance_t *module_bootstrap(module_instance_t const *parent, CONF_SECTION *cs) CC_HINT(nonnull(2));
-int modules_bootstrap(CONF_SECTION *root) CC_HINT(nonnull);
+int modules_rlm_bootstrap(CONF_SECTION *root) CC_HINT(nonnull);
/** @} */
#ifdef __cplusplus
--- /dev/null
+/*
+ * 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
+ */
+
+/**
+ * $Id$
+ *
+ * @file src/lib/server/module_rlm.c
+ * @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 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/modpriv.h>
+#include <freeradius-devel/server/module_rlm.h>
+#include <freeradius-devel/server/pair.h>
+#include <freeradius-devel/server/virtual_servers.h>
+
+/** Lookup virtual module by name
+ */
+static fr_rb_tree_t *module_rlm_virtual_name_tree;
+
+typedef struct {
+ fr_rb_node_t name_node; //!< Entry in the name tree.
+ char const *name; //!< module name
+ CONF_SECTION *cs; //!< CONF_SECTION where it is defined
+ bool all_same;
+} module_rlm_virtual_t;
+
+/** Compare virtual modules by name
+ */
+static int8_t module_rlm_virtual_name_cmp(void const *one, void const *two)
+{
+ module_rlm_virtual_t const *a = one;
+ module_rlm_virtual_t const *b = two;
+ int ret;
+
+ ret = strcmp(a->name, b->name);
+ return CMP(ret, 0);
+}
+
+char const *section_type_value[MOD_COUNT] = {
+ "authenticate",
+ "authorize",
+ "preacct",
+ "accounting",
+ "post-auth"
+};
+
+/** Initialise a module specific exfile handle
+ *
+ * @see exfile_init
+ *
+ * @param[in] ctx to bind the lifetime of the exfile handle to.
+ * @param[in] module section.
+ * @param[in] max_entries Max file descriptors to cache, and manage locks for.
+ * @param[in] max_idle Maximum time a file descriptor can be idle before it's closed.
+ * @param[in] locking Whether or not to lock the files.
+ * @param[in] trigger_prefix if NULL will be set automatically from the module CONF_SECTION.
+ * @param[in] trigger_args to make available in any triggers executed by the connection pool.
+ * @return
+ * - New connection pool.
+ * - NULL on error.
+ */
+exfile_t *module_rlm_exfile_init(TALLOC_CTX *ctx,
+ CONF_SECTION *module,
+ uint32_t max_entries,
+ fr_time_delta_t max_idle,
+ bool locking,
+ char const *trigger_prefix,
+ fr_pair_list_t *trigger_args)
+{
+ char trigger_prefix_buff[128];
+ exfile_t *handle;
+
+ if (!trigger_prefix) {
+ snprintf(trigger_prefix_buff, sizeof(trigger_prefix_buff), "modules.%s.file", cf_section_name1(module));
+ trigger_prefix = trigger_prefix_buff;
+ }
+
+ handle = exfile_init(ctx, max_entries, max_idle, locking);
+ if (!handle) return NULL;
+
+ exfile_enable_triggers(handle, cf_section_find(module, "file", NULL), trigger_prefix, trigger_args);
+
+ return handle;
+}
+
+/** Resolve polymorphic item's from a module's #CONF_SECTION to a subsection in another module
+ *
+ * This allows certain module sections to reference module sections in other instances
+ * of the same module and share #CONF_DATA associated with them.
+ *
+ * @verbatim
+ example {
+ data {
+ ...
+ }
+ }
+
+ example inst {
+ data = example
+ }
+ * @endverbatim
+ *
+ * @param[out] out where to write the pointer to a module's config section. May be NULL on success,
+ * indicating the config item was not found within the module #CONF_SECTION
+ * or the chain of module references was followed and the module at the end of the chain
+ * did not a subsection.
+ * @param[in] module #CONF_SECTION.
+ * @param[in] name of the polymorphic sub-section.
+ * @return
+ * - 0 on success with referenced section.
+ * - 1 on success with local section.
+ * - -1 on failure.
+ */
+int module_rlm_sibling_section_find(CONF_SECTION **out, CONF_SECTION *module, char const *name)
+{
+ CONF_PAIR *cp;
+ CONF_SECTION *cs;
+ CONF_DATA const *cd;
+
+
+ module_instance_t *mi;
+ char const *inst_name;
+
+#define FIND_SIBLING_CF_KEY "find_sibling"
+
+ *out = NULL;
+
+ /*
+ * Is a real section (not referencing sibling module).
+ */
+ cs = cf_section_find(module, name, NULL);
+ if (cs) {
+ *out = cs;
+
+ return 0;
+ }
+
+ /*
+ * Item omitted completely from module config.
+ */
+ cp = cf_pair_find(module, name);
+ if (!cp) return 0;
+
+ if (cf_data_find(module, CONF_SECTION, FIND_SIBLING_CF_KEY)) {
+ cf_log_err(cp, "Module reference loop found");
+
+ return -1;
+ }
+ cd = cf_data_add(module, module, FIND_SIBLING_CF_KEY, false);
+
+ /*
+ * Item found, resolve it to a module instance.
+ * This triggers module loading, so we don't have
+ * instantiation order issues.
+ */
+ inst_name = cf_pair_value(cp);
+ mi = module_by_name(NULL, inst_name);
+ if (!mi) {
+ cf_log_err(cp, "Unknown module instance \"%s\"", inst_name);
+
+ return -1;
+ }
+
+ if (!mi->instantiated) {
+ CONF_SECTION *parent = module;
+
+ /*
+ * Find the root of the config...
+ */
+ do {
+ CONF_SECTION *tmp;
+
+ tmp = cf_item_to_section(cf_parent(parent));
+ if (!tmp) break;
+
+ parent = tmp;
+ } while (true);
+
+ module_instantiate(module_by_name(NULL, inst_name));
+ }
+
+ /*
+ * Remove the config data we added for loop
+ * detection.
+ */
+ cf_data_remove(module, cd);
+
+ /*
+ * Check the module instances are of the same type.
+ */
+ if (strcmp(cf_section_name1(mi->dl_inst->conf), cf_section_name1(module)) != 0) {
+ cf_log_err(cp, "Referenced module is a rlm_%s instance, must be a rlm_%s instance",
+ cf_section_name1(mi->dl_inst->conf), cf_section_name1(module));
+
+ return -1;
+ }
+
+ *out = cf_section_find(mi->dl_inst->conf, name, NULL);
+
+ return 1;
+}
+
+/** Initialise a module specific connection pool
+ *
+ * @see fr_pool_init
+ *
+ * @param[in] module section.
+ * @param[in] opaque data pointer to pass to callbacks.
+ * @param[in] c Callback to create new connections.
+ * @param[in] a Callback to check the status of connections.
+ * @param[in] log_prefix override, if NULL will be set automatically from the module CONF_SECTION.
+ * @param[in] trigger_prefix if NULL will be set automatically from the module CONF_SECTION.
+ * @param[in] trigger_args to make available in any triggers executed by the connection pool.
+ * @return
+ * - New connection pool.
+ * - NULL on error.
+ */
+fr_pool_t *module_rlm_connection_pool_init(CONF_SECTION *module,
+ void *opaque,
+ fr_pool_connection_create_t c,
+ fr_pool_connection_alive_t a,
+ char const *log_prefix,
+ char const *trigger_prefix,
+ fr_pair_list_t *trigger_args)
+{
+ CONF_SECTION *cs, *mycs;
+ char log_prefix_buff[128];
+ char trigger_prefix_buff[128];
+
+ fr_pool_t *pool;
+ char const *cs_name1, *cs_name2;
+
+ int ret;
+
+#define parent_name(_x) cf_section_name(cf_item_to_section(cf_parent(_x)))
+
+ cs_name1 = cf_section_name1(module);
+ cs_name2 = cf_section_name2(module);
+ if (!cs_name2) cs_name2 = cs_name1;
+
+ if (!trigger_prefix) {
+ snprintf(trigger_prefix_buff, sizeof(trigger_prefix_buff), "modules.%s.pool", cs_name1);
+ trigger_prefix = trigger_prefix_buff;
+ }
+
+ if (!log_prefix) {
+ snprintf(log_prefix_buff, sizeof(log_prefix_buff), "rlm_%s (%s)", cs_name1, cs_name2);
+ log_prefix = log_prefix_buff;
+ }
+
+ /*
+ * Get sibling's pool config section
+ */
+ ret = module_rlm_sibling_section_find(&cs, module, "pool");
+ switch (ret) {
+ case -1:
+ return NULL;
+
+ case 1:
+ DEBUG4("%s: Using pool section from \"%s\"", log_prefix, parent_name(cs));
+ break;
+
+ case 0:
+ DEBUG4("%s: Using local pool section", log_prefix);
+ break;
+ }
+
+ /*
+ * Get our pool config section
+ */
+ mycs = cf_section_find(module, "pool", NULL);
+ if (!mycs) {
+ DEBUG4("%s: Adding pool section to config item \"%s\" to store pool references", log_prefix,
+ cf_section_name(module));
+
+ mycs = cf_section_alloc(module, module, "pool", NULL);
+ }
+
+ /*
+ * Sibling didn't have a pool config section
+ * Use our own local pool.
+ */
+ if (!cs) {
+ DEBUG4("%s: \"%s.pool\" section not found, using \"%s.pool\"", log_prefix,
+ parent_name(cs), parent_name(mycs));
+ cs = mycs;
+ }
+
+ /*
+ * If fr_pool_init has already been called
+ * for this config section, reuse the previous instance.
+ *
+ * This allows modules to pass in the config sections
+ * they would like to use the connection pool from.
+ */
+ pool = cf_data_value(cf_data_find(cs, fr_pool_t, NULL));
+ if (!pool) {
+ DEBUG4("%s: No pool reference found for config item \"%s.pool\"", log_prefix, parent_name(cs));
+ pool = fr_pool_init(cs, cs, opaque, c, a, log_prefix);
+ if (!pool) return NULL;
+
+ fr_pool_enable_triggers(pool, trigger_prefix, trigger_args);
+
+ if (fr_pool_start(pool) < 0) {
+ ERROR("%s: Starting initial connections failed", log_prefix);
+ return NULL;
+ }
+
+ DEBUG4("%s: Adding pool reference %p to config item \"%s.pool\"", log_prefix, pool, parent_name(cs));
+ cf_data_add(cs, pool, NULL, false);
+ return pool;
+ }
+ fr_pool_ref(pool);
+
+ DEBUG4("%s: Found pool reference %p in config item \"%s.pool\"", log_prefix, pool, parent_name(cs));
+
+ /*
+ * We're reusing pool data add it to our local config
+ * section. This allows other modules to transitively
+ * re-use a pool through this module.
+ */
+ if (mycs != cs) {
+ DEBUG4("%s: Copying pool reference %p from config item \"%s.pool\" to config item \"%s.pool\"",
+ log_prefix, pool, parent_name(cs), parent_name(mycs));
+ cf_data_add(mycs, pool, NULL, false);
+ }
+
+ return pool;
+}
+
+/*
+ * Convert a string to an integer
+ */
+module_method_t module_rlm_state_str_to_method(module_state_func_table_t const *table,
+ char const *name, module_method_t def)
+{
+ module_state_func_table_t const *this;
+
+ if (!name) return def;
+
+ for (this = table; this->name != NULL; this++) {
+ if (strcasecmp(this->name, name) == 0) return this->func;
+ }
+
+ return def;
+}
+
+/*
+ * Convert an integer to a string.
+ */
+char const *module_rlm_state_method_to_str(module_state_func_table_t const *table,
+ module_method_t method, char const *def)
+{
+ module_state_func_table_t const *this;
+
+ for (this = table; this->name != NULL; this++) if (this->func == method) return this->name;
+
+ return def;
+}
+
+/** Set the next section type if it's not already set
+ *
+ * @param[in] request The current request.
+ * @param[in] type_da to use. Usually attr_auth_type.
+ * @param[in] enumv Enumeration value of the specified type_da.
+ */
+bool module_rlm_section_type_set(request_t *request, fr_dict_attr_t const *type_da, fr_dict_enum_value_t const *enumv)
+{
+ fr_pair_t *vp;
+
+ switch (pair_update_control(&vp, type_da)) {
+ case 0:
+ fr_value_box_copy(vp, &vp->data, enumv->value);
+ vp->data.enumv = vp->da; /* So we get the correct string alias */
+ RDEBUG2("Setting &control.%pP", vp);
+ return true;
+
+ case 1:
+ RDEBUG2("&control.%s already set. Not setting to %s", vp->da->name, enumv->name);
+ return false;
+
+ default:
+ return false;
+ }
+}
+
+/** 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.
+ *
+ * @param[out] method the method function we will call
+ * @param[in,out] component the default component to use. Updated to be the found component
+ * @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
+ * @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.
+ */
+module_instance_t *module_rlm_by_name_and_method(module_method_t *method, rlm_components_t *component,
+ char const **name1, char const **name2,
+ char const *name)
+{
+ char *p, *q, *inst_name;
+ size_t len;
+ int j;
+ rlm_components_t i;
+ module_instance_t *mi;
+ module_method_names_t const *methods;
+ char const *method_name1, *method_name2;
+
+ if (method) *method = NULL;
+
+ method_name1 = method_name2 = NULL;
+ if (name1) {
+ method_name1 = *name1;
+ *name1 = NULL;
+ }
+ if (name2) {
+ method_name2 = *name2;
+ *name2 = NULL;
+ }
+
+ /*
+ * Module names are allowed to contain '.'
+ * so we search for the bare module name first.
+ */
+ mi = module_by_name(NULL, name);
+ if (mi) {
+ virtual_server_method_t const *allowed_list;
+
+ if (!method) return mi;
+
+ /*
+ * 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 || !mi->module->method_names) goto return_component;
+
+ /*
+ * Walk through the module, finding a matching
+ * method.
+ */
+ for (j = 0; mi->module->method_names[j].name1 != NULL; j++) {
+ methods = &mi->module->method_names[j];
+
+ /*
+ * Wildcard match name1, we're
+ * done.
+ */
+ if (methods->name1 == CF_IDENT_ANY) {
+ found:
+ *method = methods->method;
+ if (name1) *name1 = method_name1;
+ if (name2) *name2 = method_name2;
+ return mi;
+ }
+
+ /*
+ * If name1 doesn't match, skip it.
+ */
+ if (strcmp(methods->name1, method_name1) != 0) continue;
+
+ /*
+ * The module can declare a
+ * wildcard for name2, in which
+ * case it's a match.
+ */
+ if (methods->name2 == CF_IDENT_ANY) goto found;
+
+ /*
+ * No name2 is also a match to no name2.
+ */
+ if (!methods->name2 && !method_name2) goto found;
+
+ /*
+ * Don't do strcmp on NULLs
+ */
+ if (!methods->name2 || !method_name2) continue;
+
+ if (strcmp(methods->name2, method_name2) == 0) goto found;
+ }
+
+ /*
+ * 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(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 shoul 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].name != NULL; j++) {
+ int k;
+ virtual_server_method_t const *allowed = &allowed_list[j];
+
+ for (k = 0; mi->module->method_names[k].name1 != NULL; k++) {
+ methods = &mi->module->method_names[k];
+
+ fr_assert(methods->name1 != CF_IDENT_ANY); /* should have been caught above */
+
+ if (strcmp(methods->name1, allowed->name) != 0) continue;
+
+ /*
+ * The module matches "recv *",
+ * call this method.
+ */
+ if (methods->name2 == CF_IDENT_ANY) {
+ found_allowed:
+ *method = methods->method;
+ return mi;
+ }
+
+ /*
+ * No name2 is also a match to no name2.
+ */
+ if (!methods->name2 && !allowed->name2) goto found_allowed;
+
+ /*
+ * Don't do strcmp on NULLs
+ */
+ if (!methods->name2 || !allowed->name2) continue;
+
+ if (strcmp(methods->name2, allowed->name2) == 0) goto found_allowed;
+ }
+ }
+
+ return_component:
+ /*
+ * No matching method. Just return a method
+ * based on the component.
+ */
+ if (component && mi->module->methods[*component]) {
+ *method = mi->module->methods[*component];
+ }
+
+ /*
+ * Didn't find a matching method. Just return
+ * the module.
+ */
+ return mi;
+ }
+
+ /*
+ * Find out if the instance name contains
+ * a method, if it doesn't, then the module
+ * doesn't exist.
+ */
+ p = strchr(name, '.');
+ if (!p) return NULL;
+
+ /*
+ * 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);
+
+ /*
+ * Loop over the '.' portions, gradually looking up a
+ * longer string, in order to find the full module name.
+ */
+ do {
+ *p = '\0';
+
+ mi = module_by_name(NULL, inst_name);
+ if (mi) break;
+
+ /*
+ * Find the next '.'
+ */
+ *p = '.';
+ p = strchr(p + 1, '.');
+ } while (p);
+
+ /*
+ * No such module, we're done.
+ */
+ if (!mi) {
+ talloc_free(inst_name);
+ return NULL;
+ }
+
+ /*
+ * We have a module, but the caller doesn't care about
+ * method or names, so just return the module.
+ */
+ if (!method || !method_name1 || !method_name2) {
+ talloc_free(inst_name);
+ return mi;
+ }
+
+ /*
+ * We MAY have two names.
+ */
+ p++;
+ q = strchr(p, '.');
+
+ /*
+ * If there's only one component, look for it in the
+ * "authorize", etc. list first.
+ */
+ if (!q) {
+ for (i = MOD_AUTHENTICATE; i < MOD_COUNT; i++) {
+ if (strcmp(section_type_value[i], p) != 0) continue;
+
+ /*
+ * Tell the caller which component was
+ * referenced, and set the method to the found
+ * function.
+ */
+ if (component) {
+ *component = i;
+ if (method) *method = mi->module->methods[*component];
+ }
+
+ /*
+ * The string matched. Return it. Also set the
+ * names so that the caller gets told the method
+ * name being used.
+ */
+ *name1 = name + (p - inst_name);
+ *name2 = NULL;
+ talloc_free(inst_name);
+ return mi;
+ }
+ }
+
+ /*
+ * We've found the module, but it has no named methods.
+ */
+ if (!mi->module->method_names) {
+ *name1 = name + (p - inst_name);
+ *name2 = NULL;
+ talloc_free(inst_name);
+ return mi;
+ }
+
+ /*
+ * We have "module.METHOD", but METHOD doesn't match
+ * "authorize", "authenticate", etc. Let's see if it
+ * matches anything else.
+ */
+ if (!q) {
+ for (j = 0; mi->module->method_names[j].name1 != NULL; j++) {
+ methods = &mi->module->method_names[j];
+
+ /*
+ * If we do not have the second $method, then ignore it!
+ */
+ if (methods->name2 && (methods->name2 != CF_IDENT_ANY)) continue;
+
+ /*
+ * Wildcard match name1, we're
+ * done.
+ */
+ if (!methods->name1 || (methods->name1 == CF_IDENT_ANY)) goto found_name1;
+
+ /*
+ * If name1 doesn't match, skip it.
+ */
+ if (strcmp(methods->name1, p) != 0) continue;
+
+ found_name1:
+ /*
+ * We've matched "*", or "name1" or
+ * "name1 *". Return that.
+ */
+ *name1 = p;
+ *name2 = NULL;
+ *method = methods->method;
+ break;
+ }
+
+ /*
+ * Return the found module.
+ */
+ talloc_free(inst_name);
+ return mi;
+ }
+
+ /*
+ * We CANNOT have '.' in method names.
+ */
+ if (strchr(q + 1, '.') != 0) {
+ talloc_free(inst_name);
+ return mi;
+ }
+
+ len = q - p;
+
+ /*
+ * Trim the '.'.
+ */
+ if (*q == '.' && *(q + 1)) q++;
+
+ /*
+ * We have "module.METHOD1.METHOD2".
+ *
+ * Loop over the method names, seeing if we have a match.
+ */
+ for (j = 0; mi->module->method_names[j].name1 != NULL; j++) {
+ methods = &mi->module->method_names[j];
+
+ /*
+ * If name1 doesn't match, skip it.
+ */
+ if (strncmp(methods->name1, p, len) != 0) continue;
+
+ /*
+ * 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->name1) != len) continue;
+
+ /*
+ * The module can declare a
+ * wildcard for name2, in which
+ * case it's a match.
+ */
+ if (!methods->name2 || (methods->name2 == CF_IDENT_ANY)) goto found_name2;
+
+ /*
+ * Don't do strcmp on NULLs
+ */
+ if (!methods->name2) continue;
+
+ if (strcmp(methods->name2, q) != 0) continue;
+
+ found_name2:
+ /*
+ * Update name1/name2 with the methods
+ * that were found.
+ */
+ *name1 = methods->name1;
+ *name2 = name + (q - inst_name);
+ *method = methods->method;
+ break;
+ }
+
+ *name1 = name + (p - inst_name);
+ *name2 = NULL;
+
+ talloc_free(inst_name);
+ return mi;
+}
+
+CONF_SECTION *module_rlm_by_name_virtual(char const *asked_name)
+{
+ module_rlm_virtual_t *inst;
+
+ inst = fr_rb_find(module_rlm_virtual_name_tree,
+ &(module_rlm_virtual_t){
+ .name = asked_name,
+ });
+ if (!inst) return NULL;
+
+ return inst->cs;
+}
+
+/** Create a virtual module.
+ *
+ * @param[in] cs that defines the virtual module.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int module_rlm_bootstrap_virtual(CONF_SECTION *cs)
+{
+ char const *name;
+ bool all_same;
+ module_t const *last = NULL;
+ CONF_ITEM *sub_ci = NULL;
+ CONF_PAIR *cp;
+ module_instance_t *mi;
+ module_rlm_virtual_t *inst;
+
+ name = cf_section_name1(cs);
+
+ /*
+ * Groups, etc. must have a name.
+ */
+ if ((strcmp(name, "group") == 0) ||
+ (strcmp(name, "redundant") == 0) ||
+ (strcmp(name, "redundant-load-balance") == 0) ||
+ (strcmp(name, "load-balance") == 0)) {
+ name = cf_section_name2(cs);
+ if (!name) {
+ cf_log_err(cs, "Keyword module must have a second name");
+ return -1;
+ }
+
+ /*
+ * name2 was already checked in modules_rlm_bootstrap()
+ */
+ fr_assert(!unlang_compile_is_keyword(name));
+ } else {
+ cf_log_err(cs, "Module names cannot be unlang keywords '%s'", name);
+ return -1;
+ }
+
+ /*
+ * Ensure that the module doesn't exist.
+ */
+ mi = module_by_name(NULL, name);
+ if (mi) {
+ ERROR("Duplicate module \"%s\" in file %s[%d] and file %s[%d]",
+ name,
+ cf_filename(cs),
+ cf_lineno(cs),
+ cf_filename(mi->dl_inst->conf),
+ cf_lineno(mi->dl_inst->conf));
+ return -1;
+ }
+
+ /*
+ * Don't bother registering redundant xlats for a simple "group".
+ */
+ 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;
+ }
+
+ /*
+ * 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, 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;
+ }
+
+ if (all_same) {
+ if (!last) {
+ last = mi->module;
+ } else if (last != mi->module) {
+ last = NULL;
+ 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 */
+
+ inst = talloc_zero(cs, module_rlm_virtual_t);
+ if (!inst) return -1;
+
+ inst->cs = cs;
+ inst->name = talloc_strdup(inst, name);
+ inst->all_same = all_same;
+
+ if (!fr_cond_assert(fr_rb_insert(module_rlm_virtual_name_tree, inst))) {
+ talloc_free(inst);
+ return -1;
+ }
+
+ return 0;
+}
+
+/** Bootstrap modules and virtual modules
+ *
+ * Parse the module config sections, and load and call each module's init() function.
+ *
+ * @param[in] root of the server configuration.
+ * @return
+ * - 0 if all modules were bootstrapped successfully.
+ * - -1 if a module/virtual module failed to boostrap.
+ */
+int modules_rlm_bootstrap(CONF_SECTION *root)
+{
+ CONF_ITEM *ci;
+ CONF_SECTION *cs, *modules;
+ module_rlm_virtual_t *vm;
+ fr_rb_iter_inorder_t iter;
+ CONF_SECTION *actions;
+ /*
+ * Remember where the modules were stored.
+ */
+ modules = cf_section_find(root, "modules", NULL);
+ if (!modules) {
+ WARN("Cannot find a \"modules\" section in the configuration file!");
+ return 0;
+ }
+
+ DEBUG2("#### Bootstrapping modules ####");
+
+ cf_log_debug(modules, " modules {");
+
+ /*
+ * Loop over module definitions, looking for duplicates.
+ *
+ * This is O(N^2) in the number of modules, but most
+ * systems should have less than 100 modules.
+ */
+ for (ci = cf_item_next(modules, NULL);
+ ci != NULL;
+ ci = cf_item_next(modules, ci)) {
+ char const *name;
+ CONF_SECTION *subcs;
+ module_instance_t *mi;
+
+ /*
+ * @todo - maybe this should be a warning?
+ */
+ if (!cf_item_is_section(ci)) continue;
+
+ subcs = cf_item_to_section(ci);
+
+ /*
+ * name2 can't be a keyword
+ */
+ name = cf_section_name2(subcs);
+ if (name && unlang_compile_is_keyword(name)) {
+ invalid_name:
+ cf_log_err(subcs, "Module names cannot be unlang keywords '%s'", name);
+ return -1;
+ }
+
+ name = cf_section_name1(subcs);
+
+ /*
+ * For now, ignore name1 which is a keyword.
+ */
+ if (unlang_compile_is_keyword(name)) {
+ if (!cf_section_name2(subcs)) {
+ cf_log_err(subcs, "Missing second name at '%s'", name);
+ return -1;
+ }
+ if (module_rlm_bootstrap_virtual(subcs) < 0) return -1;
+ continue;
+ }
+
+ /*
+ * Skip inline templates, and disallow "template { ... }"
+ */
+ if (strcmp(name, "template") == 0) {
+ if (!cf_section_name2(subcs)) goto invalid_name;
+ continue;
+ }
+
+ mi = module_bootstrap(NULL, subcs);
+ if (!mi) return -1;
+
+ /*
+ * Compile the default "actions" subsection, which includes retries.
+ */
+ actions = cf_section_find(subcs, "actions", NULL);
+ if (actions && unlang_compile_actions(&mi->actions, actions, (mi->module->type & RLM_TYPE_RETRY) != 0)) {
+ talloc_free(mi);
+ return -1;
+ }
+ }
+
+ cf_log_debug(modules, " } # modules");
+
+ if (fr_command_register_hook(NULL, NULL, modules, module_cmd_list_table) < 0) {
+ PERROR("Failed registering radmin commands for modules");
+ return -1;
+ }
+
+ /*
+ * Check for duplicate policies. They're treated as
+ * modules, so we might as well check them here.
+ */
+ cs = cf_section_find(root, "policy", NULL);
+ if (cs) {
+ while ((ci = cf_item_next(cs, ci))) {
+ CONF_SECTION *subcs, *problemcs;
+ char const *name1;
+
+ /*
+ * Skip anything that isn't a section.
+ */
+ if (!cf_item_is_section(ci)) continue;
+
+ subcs = cf_item_to_section(ci);
+ name1 = cf_section_name1(subcs);
+
+ if (unlang_compile_is_keyword(name1)) {
+ cf_log_err(subcs, "Policy name '%s' cannot be an unlang keyword", name1);
+ return -1;
+ }
+
+ if (cf_section_name2(subcs)) {
+ cf_log_err(subcs, "Policies cannot have two names");
+ return -1;
+ }
+
+ problemcs = cf_section_find_next(cs, subcs, name1, CF_IDENT_ANY);
+ if (!problemcs) continue;
+
+ cf_log_err(problemcs, "Duplicate policy '%s' is forbidden.",
+ cf_section_name1(subcs));
+ return -1;
+ }
+ }
+
+ /*
+ * Now that all of the xlat things have been registered,
+ * register our redundant xlats. But only when all of
+ * the items in such a section are the same.
+ */
+ for (vm = fr_rb_iter_init_inorder(&iter, module_rlm_virtual_name_tree);
+ vm;
+ vm = fr_rb_iter_next_inorder(&iter)) {
+ if (!vm->all_same) continue;
+
+ if (xlat_register_redundant(vm->cs) < 0) return -1;
+ }
+
+ return 0;
+}
+
+void modules_rlm_free(void)
+{
+ TALLOC_FREE(module_rlm_virtual_name_tree);
+}
+
+int modules_rlm_init(void)
+{
+ MEM(module_rlm_virtual_name_tree = fr_rb_inline_alloc(NULL, module_rlm_virtual_t, name_node,
+ module_rlm_virtual_name_cmp, NULL));
+ return 0;
+}
--- /dev/null
+/*
+ * 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
+ */
+
+/**
+ * $Id$
+ *
+ * @file src/lib/server/module_rlm.h
+ * @brief Defines functions for rlm module (re-)initialisation.
+ *
+ * @copyright 2022 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+RCSIDH(module_rlm_h, "$Id$")
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <stdint.h>
+
+extern char const *section_type_value[MOD_COUNT];
+
+void modules_rlm_free(void);
+
+int modules_rlm_init(void);
+
+#ifdef __cplusplus
+}
+#endif
*
* Return it to the caller, with the updated method.
*/
- subcs = module_by_name_virtual(virtual_name);
+ subcs = module_rlm_by_name_virtual(virtual_name);
if (subcs) {
*pcomponent = method;
goto check_for_loop;
* name2, etc.
*/
UPDATE_CTX2;
- inst = module_by_name_and_method(&method, &unlang_ctx2.component,
+ inst = module_rlm_by_name_and_method(&method, &unlang_ctx2.component,
&unlang_ctx2.section_name1, &unlang_ctx2.section_name2,
realname);
if (inst) {
return -1;
}
- driver->pool = module_connection_pool_init(conf, driver, mod_conn_create, NULL,
+ driver->pool = module_rlm_connection_pool_init(conf, driver, mod_conn_create, NULL,
buffer, "modules.rlm_cache.pool", NULL);
if (!driver->pool) return -1;
RETURN_MODULE_NOOP;
}
- if (!module_section_type_set(request, attr_auth_type, inst->auth_type)) {
+ if (!module_rlm_section_type_set(request, attr_auth_type, inst->auth_type)) {
RETURN_MODULE_NOOP;
}
}
/* initiate connection pool */
- inst->pool = module_connection_pool_init(conf, inst, mod_conn_create, mod_conn_alive, NULL, NULL, NULL);
+ inst->pool = module_rlm_connection_pool_init(conf, inst, mod_conn_create, mod_conn_alive, NULL, NULL, NULL);
/* check connection pool */
if (!inst->pool) {
inst->escape_func = rad_filename_make_safe;
}
- inst->ef = module_exfile_init(inst, conf, 256, fr_time_delta_from_sec(30), inst->locking, NULL, NULL);
+ inst->ef = module_rlm_exfile_init(inst, conf, 256, fr_time_delta_from_sec(30), inst->locking, NULL, NULL);
if (!inst->ef) {
cf_log_err(conf, "Failed creating log file context");
return -1;
/*
* Everything's OK, add a digest authentication type.
*/
- if (!module_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
+ if (!module_rlm_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
RETURN_MODULE_OK;
}
break;
}
- if (!module_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
+ if (!module_rlm_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
if (status == RLM_MODULE_OK) RETURN_MODULE_OK;
/*
* Initialize the socket pool.
*/
- inst->pool = module_connection_pool_init(mctx->inst->conf, inst, krb5_mod_conn_create, NULL, NULL, NULL, NULL);
+ inst->pool = module_rlm_connection_pool_init(mctx->inst->conf, inst, krb5_mod_conn_create, NULL, NULL, NULL, NULL);
if (!inst->pool) return -1;
#else
inst->conn = krb5_mod_conn_create(inst, inst, fr_time_delta_wrap(0));
#include "rlm_ldap.h"
#include <freeradius-devel/server/map_proc.h>
+#include <freeradius-devel/server/module_rlm.h>
static CONF_PARSER sasl_mech_dynamic[] = {
{ FR_CONF_OFFSET("mech", FR_TYPE_TMPL | FR_TYPE_NOT_EMPTY, fr_ldap_sasl_t_dynamic_t, mech) },
return -1;
}
- inst->file.ef = module_exfile_init(inst, conf, 256, fr_time_delta_from_sec(30), true, NULL, NULL);
+ inst->file.ef = module_rlm_exfile_init(inst, conf, 256, fr_time_delta_from_sec(30), true, NULL, NULL);
if (!inst->file.ef) {
cf_log_err(conf, "Failed creating log file context");
return -1;
cf_log_err(conf, "Unix sockets are not supported on this sytem");
return -1;
#else
- inst->pool = module_connection_pool_init(cf_section_find(conf, "unix", NULL),
+ inst->pool = module_rlm_connection_pool_init(cf_section_find(conf, "unix", NULL),
inst, mod_conn_create, NULL, prefix, NULL, NULL);
if (!inst->pool) return -1;
#endif
break;
case LINELOG_DST_UDP:
- inst->pool = module_connection_pool_init(cf_section_find(conf, "udp", NULL),
+ inst->pool = module_rlm_connection_pool_init(cf_section_find(conf, "udp", NULL),
inst, mod_conn_create, NULL, prefix, NULL, NULL);
if (!inst->pool) return -1;
break;
case LINELOG_DST_TCP:
- inst->pool = module_connection_pool_init(cf_section_find(conf, "tcp", NULL),
+ inst->pool = module_rlm_connection_pool_init(cf_section_find(conf, "tcp", NULL),
inst, mod_conn_create, NULL, prefix, NULL, NULL);
if (!inst->pool) return -1;
break;
RETURN_MODULE_NOOP;
}
- if (!module_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
+ if (!module_rlm_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
RETURN_MODULE_OK;
}
#ifdef WITH_AUTH_WINBIND
inst->method = AUTH_WBCLIENT;
- inst->wb_pool = module_connection_pool_init(conf, inst, mod_conn_create, NULL, NULL, NULL, NULL);
+ inst->wb_pool = module_rlm_connection_pool_init(conf, inst, mod_conn_create, NULL, NULL, NULL, NULL);
if (!inst->wb_pool) {
cf_log_err(conf, "Unable to initialise winbind connection pool");
return -1;
RETURN_MODULE_NOOP;
}
- if (!module_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
+ if (!module_rlm_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
RETURN_MODULE_OK;
}
RETURN_MODULE_NOOP;
}
- if (!module_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
+ if (!module_rlm_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
RETURN_MODULE_UPDATED;
}
#include <freeradius-devel/curl/base.h>
#include <freeradius-devel/server/base.h>
#include <freeradius-devel/server/module.h>
+#include <freeradius-devel/server/module_rlm.h>
#include <freeradius-devel/server/pairmove.h>
#include <freeradius-devel/tls/base.h>
#include <freeradius-devel/util/debug.h>
--- /dev/null
+Subproject commit f5f31d34d1f4c21cc5cd2342930c7cf481651a1f
--- /dev/null
+Subproject commit 2687d8fb7246a6c516fec9ec91746af4376d0ffb
inst->driver->sql_escape_func :
sql_escape_func;
- inst->ef = module_exfile_init(inst, conf, 256, fr_time_delta_from_sec(30), true, NULL, NULL);
+ inst->ef = module_rlm_exfile_init(inst, conf, 256, fr_time_delta_from_sec(30), true, NULL, NULL);
if (!inst->ef) {
cf_log_err(conf, "Failed creating log file context");
return -1;
*/
INFO("Attempting to connect to database \"%s\"", inst->config.sql_db);
- inst->pool = module_connection_pool_init(conf, inst, sql_mod_conn_create, NULL, NULL, NULL, NULL);
+ inst->pool = module_rlm_connection_pool_init(conf, inst, sql_mod_conn_create, NULL, NULL, NULL, NULL);
if (!inst->pool) return -1;
return 0;
return -1;
}
- inst->wb_pool = module_connection_pool_init(conf, inst, mod_conn_create, NULL, NULL, NULL, NULL);
+ inst->wb_pool = module_rlm_connection_pool_init(conf, inst, mod_conn_create, NULL, NULL, NULL, NULL);
if (!inst->wb_pool) {
cf_log_err(conf, "Unable to initialise winbind connection pool");
return -1;
RETURN_MODULE_NOOP;
}
- if (!module_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
+ if (!module_rlm_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
RETURN_MODULE_OK;
}
RETURN_MODULE_NOOP;
}
- if (!module_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
+ if (!module_rlm_section_type_set(request, attr_auth_type, inst->auth_type)) RETURN_MODULE_NOOP;
RETURN_MODULE_OK;
}
return -1;
}
- inst->pool = module_connection_pool_init(conf, inst, mod_conn_create, NULL, inst->name, NULL, NULL);
+ inst->pool = module_rlm_connection_pool_init(conf, inst, mod_conn_create, NULL, inst->name, NULL, NULL);
if (!inst->pool) {
ykclient_done(&inst->ykc);