From 2319ba6bc7901602d596b6755516e74936f3e015 Mon Sep 17 00:00:00 2001 From: Arran Cudbard-Bell Date: Tue, 1 Mar 2022 18:03:48 -0600 Subject: [PATCH] Split out rlm module code from modules.c --- doc/antora/modules/developers/pages/todo.adoc | 2 +- src/lib/redis/base.h | 2 +- src/lib/server/base.c | 2 +- src/lib/server/libfreeradius-server.mk | 3 +- src/lib/server/modpriv.h | 9 +- src/lib/server/module.c | 1355 ++--------------- src/lib/server/module.h | 22 +- src/lib/server/module_rlm.c | 1090 +++++++++++++ src/lib/server/module_rlm.h | 41 + src/lib/unlang/compile.c | 4 +- .../rlm_cache_memcached/rlm_cache_memcached.c | 2 +- src/modules/rlm_chap/rlm_chap.c | 2 +- src/modules/rlm_couchbase/rlm_couchbase.c | 2 +- src/modules/rlm_detail/rlm_detail.c | 2 +- src/modules/rlm_digest/rlm_digest.c | 2 +- src/modules/rlm_eap/rlm_eap.c | 2 +- src/modules/rlm_krb5/rlm_krb5.c | 2 +- src/modules/rlm_ldap/rlm_ldap.c | 1 + src/modules/rlm_linelog/rlm_linelog.c | 8 +- src/modules/rlm_mschap/rlm_mschap.c | 4 +- .../rlm_opendirectory/rlm_opendirectory.c | 2 +- src/modules/rlm_pap/rlm_pap.c | 2 +- src/modules/rlm_rest/rlm_rest.c | 1 + src/modules/rlm_sigtran/libosmo-abis | 1 + src/modules/rlm_sigtran/libosmo-netif | 1 + src/modules/rlm_sql/rlm_sql.c | 4 +- src/modules/rlm_winbind/rlm_winbind.c | 4 +- src/modules/rlm_yubikey/rlm_yubikey.c | 2 +- src/modules/rlm_yubikey/validate.c | 2 +- 29 files changed, 1329 insertions(+), 1247 deletions(-) create mode 100644 src/lib/server/module_rlm.c create mode 100644 src/lib/server/module_rlm.h create mode 160000 src/modules/rlm_sigtran/libosmo-abis create mode 160000 src/modules/rlm_sigtran/libosmo-netif diff --git a/doc/antora/modules/developers/pages/todo.adoc b/doc/antora/modules/developers/pages/todo.adoc index d4ec8d3938..809a8ffda3 100644 --- a/doc/antora/modules/developers/pages/todo.adoc +++ b/doc/antora/modules/developers/pages/todo.adoc @@ -149,7 +149,7 @@ sections. 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 diff --git a/src/lib/redis/base.h b/src/lib/redis/base.h index 301dde9186..01560a2a97 100644 --- a/src/lib/redis/base.h +++ b/src/lib/redis/base.h @@ -99,7 +99,7 @@ typedef struct { /** 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. diff --git a/src/lib/server/base.c b/src/lib/server/base.c index 35ba2ef2b4..4a779be720 100644 --- a/src/lib/server/base.c +++ b/src/lib/server/base.c @@ -72,7 +72,7 @@ int server_init(CONF_SECTION *cs) * * 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. diff --git a/src/lib/server/libfreeradius-server.mk b/src/lib/server/libfreeradius-server.mk index 3e6501cc85..575d6dda58 100644 --- a/src/lib/server/libfreeradius-server.mk +++ b/src/lib/server/libfreeradius-server.mk @@ -24,6 +24,7 @@ SOURCES := \ map_proc.c \ method.c \ module.c \ + module_rlm.c \ paircmp.c \ pairmove.c \ password.c \ @@ -67,7 +68,7 @@ LOG_ID_LIB := 1 $(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) diff --git a/src/lib/server/modpriv.h b/src/lib/server/modpriv.h index 4b1bef1509..e1e655ba2c 100644 --- a/src/lib/server/modpriv.h +++ b/src/lib/server/modpriv.h @@ -33,7 +33,14 @@ RCSIDH(modpriv_h, "$Id$") #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); diff --git a/src/lib/server/module.c b/src/lib/server/module.c index a76a2ce661..5caa00a923 100644 --- a/src/lib/server/module.c +++ b/src/lib/server/module.c @@ -32,6 +32,7 @@ RCSID("$Id$") #include #include #include +#include #include #include @@ -40,7 +41,7 @@ static size_t instance_num = 1; /* * 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; @@ -53,38 +54,75 @@ static fr_rb_tree_t *module_instance_name_tree; */ 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; @@ -178,71 +216,6 @@ static int cmd_set_module_status(UNUSED FILE *fp, FILE *fp_err, void *ctx, fr_cm 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 @@ -254,817 +227,79 @@ static int8_t module_instance_name_cmp(void const *one, void const *two) 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 . @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 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 @@ -1136,47 +371,6 @@ void module_free(module_instance_t *mi) 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) @@ -1302,13 +496,13 @@ void modules_thread_detach(void) * - 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; } @@ -1375,9 +569,7 @@ int modules_instantiate(UNUSED CONF_SECTION *root) 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; @@ -1517,7 +709,6 @@ module_instance_t *module_bootstrap(module_instance_t const *parent, CONF_SECTIO 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); @@ -1596,294 +787,48 @@ module_instance_t *module_bootstrap(module_instance_t const *parent, CONF_SECTIO } } - /* - * 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; } diff --git a/src/lib/server/module.h b/src/lib/server/module.h index e5974d7adb..a61f7b9de1 100644 --- a/src/lib/server/module.h +++ b/src/lib/server/module.h @@ -132,12 +132,6 @@ typedef int (*module_thread_detach_t)(module_thread_inst_ctx_t const *mctx); 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 @@ -255,14 +249,14 @@ typedef struct { * * @{ */ -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, @@ -275,13 +269,13 @@ exfile_t *module_exfile_init(TALLOC_CTX *ctx, * * @{ */ -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 @@ -290,7 +284,7 @@ bool module_section_type_set(request_t *request, fr_dict_attr_t const *type_da, */ 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); @@ -300,7 +294,7 @@ module_thread_instance_t *module_thread(module_instance_t *mi); 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); /** @} */ @@ -322,7 +316,7 @@ int modules_instantiate(CONF_SECTION *root) CC_HINT(nonnull); 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 diff --git a/src/lib/server/module_rlm.c b/src/lib/server/module_rlm.c new file mode 100644 index 0000000000..e362e1103a --- /dev/null +++ b/src/lib/server/module_rlm.c @@ -0,0 +1,1090 @@ +/* + * 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 +#include +#include +#include +#include + +/** 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 . @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 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; +} diff --git a/src/lib/server/module_rlm.h b/src/lib/server/module_rlm.h new file mode 100644 index 0000000000..b4c3fce252 --- /dev/null +++ b/src/lib/server/module_rlm.h @@ -0,0 +1,41 @@ +/* + * 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 + +extern char const *section_type_value[MOD_COUNT]; + +void modules_rlm_free(void); + +int modules_rlm_init(void); + +#ifdef __cplusplus +} +#endif diff --git a/src/lib/unlang/compile.c b/src/lib/unlang/compile.c index a40c745e79..72a15000c5 100644 --- a/src/lib/unlang/compile.c +++ b/src/lib/unlang/compile.c @@ -3915,7 +3915,7 @@ static CONF_SECTION *virtual_module_find_cs(CONF_ITEM *ci, rlm_components_t *pco * * 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; @@ -4253,7 +4253,7 @@ check_for_module: * 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) { diff --git a/src/modules/rlm_cache/drivers/rlm_cache_memcached/rlm_cache_memcached.c b/src/modules/rlm_cache/drivers/rlm_cache_memcached/rlm_cache_memcached.c index df1719ffae..3f5d705e83 100644 --- a/src/modules/rlm_cache/drivers/rlm_cache_memcached/rlm_cache_memcached.c +++ b/src/modules/rlm_cache/drivers/rlm_cache_memcached/rlm_cache_memcached.c @@ -123,7 +123,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) 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; diff --git a/src/modules/rlm_chap/rlm_chap.c b/src/modules/rlm_chap/rlm_chap.c index b33516fcdf..0e302071fa 100644 --- a/src/modules/rlm_chap/rlm_chap.c +++ b/src/modules/rlm_chap/rlm_chap.c @@ -145,7 +145,7 @@ static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, mod 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; } diff --git a/src/modules/rlm_couchbase/rlm_couchbase.c b/src/modules/rlm_couchbase/rlm_couchbase.c index 9d7e19507d..f9114d0b3b 100644 --- a/src/modules/rlm_couchbase/rlm_couchbase.c +++ b/src/modules/rlm_couchbase/rlm_couchbase.c @@ -490,7 +490,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) } /* 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) { diff --git a/src/modules/rlm_detail/rlm_detail.c b/src/modules/rlm_detail/rlm_detail.c index 8a7a894478..543898f07e 100644 --- a/src/modules/rlm_detail/rlm_detail.c +++ b/src/modules/rlm_detail/rlm_detail.c @@ -144,7 +144,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) 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; diff --git a/src/modules/rlm_digest/rlm_digest.c b/src/modules/rlm_digest/rlm_digest.c index e437ba0252..f6e665363d 100644 --- a/src/modules/rlm_digest/rlm_digest.c +++ b/src/modules/rlm_digest/rlm_digest.c @@ -103,7 +103,7 @@ static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, mod /* * 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; } diff --git a/src/modules/rlm_eap/rlm_eap.c b/src/modules/rlm_eap/rlm_eap.c index eb31358779..26c3a8db71 100644 --- a/src/modules/rlm_eap/rlm_eap.c +++ b/src/modules/rlm_eap/rlm_eap.c @@ -963,7 +963,7 @@ static unlang_action_t mod_authorize(rlm_rcode_t *p_result, module_ctx_t const * 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; diff --git a/src/modules/rlm_krb5/rlm_krb5.c b/src/modules/rlm_krb5/rlm_krb5.c index 681e1c2d66..58a6ebbe1a 100644 --- a/src/modules/rlm_krb5/rlm_krb5.c +++ b/src/modules/rlm_krb5/rlm_krb5.c @@ -222,7 +222,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) /* * 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)); diff --git a/src/modules/rlm_ldap/rlm_ldap.c b/src/modules/rlm_ldap/rlm_ldap.c index e848b1ec85..4d5606372e 100644 --- a/src/modules/rlm_ldap/rlm_ldap.c +++ b/src/modules/rlm_ldap/rlm_ldap.c @@ -37,6 +37,7 @@ USES_APPLE_DEPRECATED_API #include "rlm_ldap.h" #include +#include static CONF_PARSER sasl_mech_dynamic[] = { { FR_CONF_OFFSET("mech", FR_TYPE_TMPL | FR_TYPE_NOT_EMPTY, fr_ldap_sasl_t_dynamic_t, mech) }, diff --git a/src/modules/rlm_linelog/rlm_linelog.c b/src/modules/rlm_linelog/rlm_linelog.c index 73ef2e11ca..6ff317a13e 100644 --- a/src/modules/rlm_linelog/rlm_linelog.c +++ b/src/modules/rlm_linelog/rlm_linelog.c @@ -322,7 +322,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) 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; @@ -375,20 +375,20 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) 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; diff --git a/src/modules/rlm_mschap/rlm_mschap.c b/src/modules/rlm_mschap/rlm_mschap.c index 18723a2c94..3ec09184a2 100644 --- a/src/modules/rlm_mschap/rlm_mschap.c +++ b/src/modules/rlm_mschap/rlm_mschap.c @@ -1471,7 +1471,7 @@ static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, mod 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; } @@ -2222,7 +2222,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) #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; diff --git a/src/modules/rlm_opendirectory/rlm_opendirectory.c b/src/modules/rlm_opendirectory/rlm_opendirectory.c index 6363df10a6..29ff569b9c 100644 --- a/src/modules/rlm_opendirectory/rlm_opendirectory.c +++ b/src/modules/rlm_opendirectory/rlm_opendirectory.c @@ -509,7 +509,7 @@ setup_auth_type: 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; } diff --git a/src/modules/rlm_pap/rlm_pap.c b/src/modules/rlm_pap/rlm_pap.c index 53acee0921..b709235d4d 100644 --- a/src/modules/rlm_pap/rlm_pap.c +++ b/src/modules/rlm_pap/rlm_pap.c @@ -157,7 +157,7 @@ static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, mod 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; } diff --git a/src/modules/rlm_rest/rlm_rest.c b/src/modules/rlm_rest/rlm_rest.c index 7b5257cadd..e0e18b32aa 100644 --- a/src/modules/rlm_rest/rlm_rest.c +++ b/src/modules/rlm_rest/rlm_rest.c @@ -26,6 +26,7 @@ RCSID("$Id$") #include #include #include +#include #include #include #include diff --git a/src/modules/rlm_sigtran/libosmo-abis b/src/modules/rlm_sigtran/libosmo-abis new file mode 160000 index 0000000000..f5f31d34d1 --- /dev/null +++ b/src/modules/rlm_sigtran/libosmo-abis @@ -0,0 +1 @@ +Subproject commit f5f31d34d1f4c21cc5cd2342930c7cf481651a1f diff --git a/src/modules/rlm_sigtran/libosmo-netif b/src/modules/rlm_sigtran/libosmo-netif new file mode 160000 index 0000000000..2687d8fb72 --- /dev/null +++ b/src/modules/rlm_sigtran/libosmo-netif @@ -0,0 +1 @@ +Subproject commit 2687d8fb7246a6c516fec9ec91746af4376d0ffb diff --git a/src/modules/rlm_sql/rlm_sql.c b/src/modules/rlm_sql/rlm_sql.c index a8e29a1b5d..1dbbb3379e 100644 --- a/src/modules/rlm_sql/rlm_sql.c +++ b/src/modules/rlm_sql/rlm_sql.c @@ -1200,7 +1200,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) 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; @@ -1211,7 +1211,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) */ 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; diff --git a/src/modules/rlm_winbind/rlm_winbind.c b/src/modules/rlm_winbind/rlm_winbind.c index b4dab032b3..0a0e01f5e5 100644 --- a/src/modules/rlm_winbind/rlm_winbind.c +++ b/src/modules/rlm_winbind/rlm_winbind.c @@ -365,7 +365,7 @@ static int mod_instantiate(module_inst_ctx_t const *mctx) 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; @@ -480,7 +480,7 @@ static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, mod 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; } diff --git a/src/modules/rlm_yubikey/rlm_yubikey.c b/src/modules/rlm_yubikey/rlm_yubikey.c index fb8643ebf2..5aa567d042 100644 --- a/src/modules/rlm_yubikey/rlm_yubikey.c +++ b/src/modules/rlm_yubikey/rlm_yubikey.c @@ -374,7 +374,7 @@ static unlang_action_t CC_HINT(nonnull) mod_authorize(rlm_rcode_t *p_result, mod 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; } diff --git a/src/modules/rlm_yubikey/validate.c b/src/modules/rlm_yubikey/validate.c index b58b73c655..5369f3cddd 100644 --- a/src/modules/rlm_yubikey/validate.c +++ b/src/modules/rlm_yubikey/validate.c @@ -127,7 +127,7 @@ init: 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); -- 2.47.3