/** 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, VALUE_PAIR *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; }
/** 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), NULL); } /* * 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; }
RADCLIENT_LIST *client_list_parse_section(CONF_SECTION *section, UNUSED bool tls_required) #endif { bool global = false, in_server = false; CONF_SECTION *cs; RADCLIENT *c; RADCLIENT_LIST *clients; /* * Be forgiving. If there's already a clients, return * it. Otherwise create a new one. */ clients = cf_data_find(section, "clients"); if (clients) return clients; clients = client_list_init(section); if (!clients) return NULL; if (cf_top_section(section) == section) global = true; if (strcmp("server", cf_section_name1(section)) == 0) in_server = true; /* * Associate the clients structure with the section. */ if (cf_data_add(section, "clients", clients, NULL) < 0) { cf_log_err_cs(section, "Failed to associate clients with section %s", cf_section_name1(section)); client_list_free(clients); return NULL; } for (cs = cf_subsection_find_next(section, NULL, "client"); cs != NULL; cs = cf_subsection_find_next(section, cs, "client")) { c = client_afrom_cs(cs, cs, in_server, false); if (!c) { return NULL; } #ifdef WITH_TLS /* * TLS clients CANNOT use non-TLS listeners. * non-TLS clients CANNOT use TLS listeners. */ if (tls_required != c->tls_required) { cf_log_err_cs(cs, "Client does not have the same TLS configuration as the listener"); client_free(c); client_list_free(clients); return NULL; } #endif /* * FIXME: Add the client as data via cf_data_add, * for migration issues. */ #ifdef WITH_DYNAMIC_CLIENTS #ifdef HAVE_DIRENT_H if (c->client_server) { char const *value; CONF_PAIR *cp; DIR *dir; struct dirent *dp; struct stat stat_buf; char buf2[2048]; /* * Find the directory where individual * client definitions are stored. */ cp = cf_pair_find(cs, "directory"); if (!cp) goto add_client; value = cf_pair_value(cp); if (!value) { cf_log_err_cs(cs, "The \"directory\" entry must not be empty"); client_free(c); return NULL; } DEBUG("including dynamic clients in %s", value); dir = opendir(value); if (!dir) { cf_log_err_cs(cs, "Error reading directory %s: %s", value, fr_syserror(errno)); client_free(c); return NULL; } /* * Read the directory, ignoring "." files. */ while ((dp = readdir(dir)) != NULL) { char const *p; RADCLIENT *dc; if (dp->d_name[0] == '.') continue; /* * Check for valid characters */ for (p = dp->d_name; *p != '\0'; p++) { if (isalpha((int)*p) || isdigit((int)*p) || (*p == ':') || (*p == '.')) continue; break; } if (*p != '\0') continue; snprintf(buf2, sizeof(buf2), "%s/%s", value, dp->d_name); if ((stat(buf2, &stat_buf) != 0) || S_ISDIR(stat_buf.st_mode)) continue; dc = client_read(buf2, in_server, true); if (!dc) { cf_log_err_cs(cs, "Failed reading client file \"%s\"", buf2); client_free(c); closedir(dir); return NULL; } /* * Validate, and add to the list. */ if (!client_add_dynamic(clients, c, dc)) { client_free(c); closedir(dir); return NULL; } } /* loop over the directory */ closedir(dir); } #endif /* HAVE_DIRENT_H */ add_client: #endif /* WITH_DYNAMIC_CLIENTS */ if (!client_add(clients, c)) { cf_log_err_cs(cs, "Failed to add client %s", cf_section_name2(cs)); client_free(c); return NULL; } } /* * Replace the global list of clients with the new one. * The old one is still referenced from the original * configuration, and will be freed when that is freed. */ if (global) { root_clients = clients; } return clients; }
/** Execute a trigger - call an executable to process an event * * @param request The current request. * @param cs to search for triggers in. If not NULL, only the portion after the last '.' * in name is used for the trigger. If cs is NULL, the entire name is used to find * the trigger in the global trigger section. * @param name the path relative to the global trigger section ending in the trigger name * e.g. module.ldap.pool.start. * @param quench whether to rate limit triggers. */ void exec_trigger(REQUEST *request, CONF_SECTION *cs, char const *name, bool quench) { CONF_SECTION *subcs; CONF_ITEM *ci; CONF_PAIR *cp; char const *attr; char const *value; VALUE_PAIR *vp; bool alloc = false; /* * Use global "trigger" section if no local config is given. */ if (!cs) { cs = exec_trigger_main; attr = name; } else { /* * Try to use pair name, rather than reference. */ attr = strrchr(name, '.'); if (attr) { attr++; } else { attr = name; } } /* * Find local "trigger" subsection. If it isn't found, * try using the global "trigger" section, and reset the * reference to the full path, rather than the sub-path. */ subcs = cf_section_sub_find(cs, "trigger"); if (!subcs && exec_trigger_main && (cs != exec_trigger_main)) { subcs = exec_trigger_subcs; attr = name; } if (!subcs) return; ci = cf_reference_item(subcs, exec_trigger_main, attr); if (!ci) { ERROR("No such item in trigger section: %s", attr); return; } if (!cf_item_is_pair(ci)) { ERROR("Trigger is not a configuration variable: %s", attr); return; } cp = cf_item_to_pair(ci); if (!cp) return; value = cf_pair_value(cp); if (!value) { ERROR("Trigger has no value: %s", name); return; } /* * May be called for Status-Server packets. */ vp = NULL; if (request && request->packet) vp = request->packet->vps; /* * Perform periodic quenching. */ if (quench) { time_t *last_time; last_time = cf_data_find(cs, value); if (!last_time) { last_time = rad_malloc(sizeof(*last_time)); *last_time = 0; if (cf_data_add(cs, value, last_time, time_free) < 0) { free(last_time); last_time = NULL; } } /* * Send the quenched traps at most once per second. */ if (last_time) { time_t now = time(NULL); if (*last_time == now) return; *last_time = now; } } /* * radius_exec_program always needs a request. */ if (!request) { request = request_alloc(NULL); alloc = true; } DEBUG("Trigger %s -> %s", name, value); radius_exec_program(request, NULL, 0, NULL, request, value, vp, false, true, EXEC_TIMEOUT); if (alloc) talloc_free(request); }
/** Add a client to a RADCLIENT_LIST * * @param clients list to add client to, may be NULL if global client list is being used. * @param client to add. * @return true on success, false on failure. */ bool client_add(RADCLIENT_LIST *clients, RADCLIENT *client) { RADCLIENT *old; char buffer[INET6_ADDRSTRLEN + 3]; if (!client) return false; /* * Hack to fixup wildcard clients * * If the IP is all zeros, with a 32 or 128 bit netmask * assume the user meant to configure 0.0.0.0/0 instead * of 0.0.0.0/32 - which would require the src IP of * the client to be all zeros. */ if (fr_inaddr_any(&client->ipaddr) == 1) switch (client->ipaddr.af) { case AF_INET: if (client->ipaddr.prefix == 32) client->ipaddr.prefix = 0; break; case AF_INET6: if (client->ipaddr.prefix == 128) client->ipaddr.prefix = 0; break; default: rad_assert(0); } fr_ntop(buffer, sizeof(buffer), &client->ipaddr); DEBUG3("Adding client %s (%s) to prefix tree %i", buffer, client->longname, client->ipaddr.prefix); /* * If the client also defines a server, do that now. */ if (client->defines_coa_server) if (!realm_home_server_add(client->coa_server)) return false; /* * If "clients" is NULL, it means add to the global list, * unless we're trying to add it to a virtual server... */ if (!clients) { if (client->server != NULL) { CONF_SECTION *cs; cs = cf_section_sub_find_name2(main_config.config, "server", client->server); if (!cs) { ERROR("Failed to find virtual server %s", client->server); return false; } /* * If the client list already exists, use that. * Otherwise, create a new client list. */ clients = cf_data_find(cs, "clients"); if (!clients) { clients = client_list_init(cs); if (!clients) { ERROR("Out of memory"); return false; } if (cf_data_add(cs, "clients", clients, (void (*)(void *)) client_list_free) < 0) { ERROR("Failed to associate clients with virtual server %s", client->server); client_list_free(clients); return false; } } } else { /* * Initialize the global list, if not done already. */ if (!root_clients) { root_clients = client_list_init(NULL); if (!root_clients) return false; } clients = root_clients; } } /* * Create a tree for it. */ if (!clients->trees[client->ipaddr.prefix]) { clients->trees[client->ipaddr.prefix] = rbtree_create(clients, client_ipaddr_cmp, NULL, 0); if (!clients->trees[client->ipaddr.prefix]) { return false; } } #define namecmp(a) ((!old->a && !client->a) || (old->a && client->a && (strcmp(old->a, client->a) == 0))) /* * Cannot insert the same client twice. */ old = rbtree_finddata(clients->trees[client->ipaddr.prefix], client); if (old) { /* * If it's a complete duplicate, then free the new * one, and return "OK". */ if ((fr_ipaddr_cmp(&old->ipaddr, &client->ipaddr) == 0) && (old->ipaddr.prefix == client->ipaddr.prefix) && namecmp(longname) && namecmp(secret) && namecmp(shortname) && namecmp(nas_type) && namecmp(login) && namecmp(password) && namecmp(server) && #ifdef WITH_DYNAMIC_CLIENTS (old->lifetime == client->lifetime) && namecmp(client_server) && #endif #ifdef WITH_COA namecmp(coa_name) && (old->coa_server == client->coa_server) && (old->coa_pool == client->coa_pool) && #endif (old->message_authenticator == client->message_authenticator)) { WARN("Ignoring duplicate client %s", client->longname); client_free(client); return true; } ERROR("Failed to add duplicate client %s", client->shortname); return false; } #undef namecmp /* * Other error adding client: likely is fatal. */ if (!rbtree_insert(clients->trees[client->ipaddr.prefix], client)) { return false; } #ifdef WITH_STATS if (!tree_num) { tree_num = rbtree_create(clients, client_num_cmp, NULL, 0); } #ifdef WITH_DYNAMIC_CLIENTS /* * More catching of clients added by rlm_sql. * * The sql modules sets the dynamic flag BEFORE calling * us. The client_afrom_request() function sets it AFTER * calling us. */ if (client->dynamic && (client->lifetime == 0)) { RADCLIENT *network; /* * If there IS an enclosing network, * inherit the lifetime from it. */ network = client_find(clients, &client->ipaddr, client->proto); if (network) { client->lifetime = network->lifetime; } } #endif client->number = tree_num_max; tree_num_max++; if (tree_num) rbtree_insert(tree_num, client); #endif if (client->ipaddr.prefix < clients->min_prefix) { clients->min_prefix = client->ipaddr.prefix; } (void) talloc_steal(clients, client); /* reparent it */ return true; }