X-Git-Url: http://www.project-moonshot.org/gitweb/?a=blobdiff_plain;f=src%2Fmain%2Fmodules.c;h=a569c076c9869836b3924effcf7dca021c69f7cb;hb=9960563934a7da222528a1d82224aecc207c8aa8;hp=85f528c8519cd988f3c34668d78c12871a07a6a2;hpb=18f6a877bde37d77f661e8e81163a0048c7add8c;p=freeradius.git diff --git a/src/main/modules.c b/src/main/modules.c index 85f528c..a569c07 100644 --- a/src/main/modules.c +++ b/src/main/modules.c @@ -25,37 +25,54 @@ #include RCSID("$Id$") -#include - -#include -#include -#include - #include #include #include #include +extern int check_config; + typedef struct indexed_modcallable { - int comp; - int idx; - modcallable *modulelist; + int comp; + int idx; + modcallable *modulelist; } indexed_modcallable; +typedef struct virtual_server_t { + const char *name; + time_t created; + int can_free; + CONF_SECTION *cs; + rbtree_t *components; + modcallable *mc[RLM_COMPONENT_COUNT]; + CONF_SECTION *subcs[RLM_COMPONENT_COUNT]; + struct virtual_server_t *next; +} virtual_server_t; + /* - * For each component, keep an ordered list of ones to call. + * Keep a hash of virtual servers, so that we can reload them. */ -static lrad_hash_table_t *components; +#define VIRTUAL_SERVER_HASH_SIZE (256) +static virtual_server_t *virtual_servers[VIRTUAL_SERVER_HASH_SIZE]; static rbtree_t *module_tree = NULL; +static rbtree_t *instance_tree = NULL; + +struct fr_module_hup_t { + module_instance_t *mi; + time_t when; + void *insthandle; + fr_module_hup_t *next; +}; + + typedef struct section_type_value_t { const char *section; const char *typename; int attr; } section_type_value_t; - /* * Ordered by component */ @@ -67,39 +84,225 @@ static const section_type_value_t section_type_value[RLM_COMPONENT_COUNT] = { { "session", "Session-Type", PW_SESSION_TYPE }, { "pre-proxy", "Pre-Proxy-Type", PW_PRE_PROXY_TYPE }, { "post-proxy", "Post-Proxy-Type", PW_POST_PROXY_TYPE }, - { "post-auth", "Post-Auth-Type", PW_POST_AUTH_TYPE }, + { "post-auth", "Post-Auth-Type", PW_POST_AUTH_TYPE } +#ifdef WITH_COA + , + { "recv-coa", "Recv-CoA-Type", PW_RECV_COA_TYPE }, + { "send-coa", "Send-CoA-Type", PW_SEND_COA_TYPE } +#endif }; + +#ifdef WITHOUT_LIBLTDL +#ifdef WITH_DLOPEN +#include + +#ifndef RTLD_NOW +#define RTLD_NOW (0) +#endif +#ifndef RTLD_LOCAL +#define RTLD_LOCAL (0) +#endif + +#define fr_dlopenext lt_dlopenext +#ifndef LT_SHREXT +#ifdef __APPLE__ +#define LT_SHREXT ".so" +#elif defined (WIN32) +#define LT_SHREXT ".dll" +#else +#define LT_SHREXT ".dylib" +#endif +#endif + +lt_dlhandle lt_dlopenext(const char *name) +{ + char buffer[256]; + + strlcpy(buffer, name, sizeof(buffer)); + + /* + * FIXME: Make this configurable... + */ + strlcat(buffer, LT_SHREXT, sizeof(buffer)); + + return dlopen(buffer, RTLD_NOW | RTLD_LOCAL); +} + +void *lt_dlsym(lt_dlhandle handle, UNUSED const char *symbol) +{ + return dlsym(handle, symbol); +} + +int lt_dlclose(lt_dlhandle handle) +{ + if (!handle) return 0; + + return dlclose(handle); +} + +const char *lt_dlerror(void) +{ + return dlerror(); +} + + +#else /* without dlopen */ +typedef struct lt_dlmodule_t { + const char *name; + void *ref; +} lt_dlmodule_t; + +typedef struct eap_type_t EAP_TYPE; +typedef struct rlm_sql_module_t rlm_sql_module_t; + /* - * Delete ASAP. + * FIXME: Write hackery to auto-generate this data. + * We only need to do this on systems that don't have dlopen. */ -static const section_type_value_t old_section_type_value[] = { - { "authenticate", "authtype", PW_AUTH_TYPE }, - { "authorize", "autztype", PW_AUTZ_TYPE }, - { "preacct", "Pre-Acct-Type", PW_PRE_ACCT_TYPE },/* unused */ - { "accounting", "acctype", PW_ACCT_TYPE }, - { "session", "sesstype", PW_SESSION_TYPE }, - { "pre-proxy", "Pre-Proxy-Type", PW_PRE_PROXY_TYPE }, /* unused */ - { "post-proxy", "Post-Proxy-Type", PW_POST_PROXY_TYPE }, /* unused */ - { "post-auth", "post-authtype", PW_POST_AUTH_TYPE } +extern module_t rlm_pap; +extern module_t rlm_chap; +extern module_t rlm_eap; +extern module_t rlm_sql; +/* and so on ... */ + +extern EAP_TYPE rlm_eap_md5; +extern rlm_sql_module_t rlm_sql_mysql; +/* and so on ... */ + +static const lt_dlmodule_t lt_dlmodules[] = { + { "rlm_pap", &rlm_pap }, + { "rlm_chap", &rlm_chap }, + { "rlm_eap", &rlm_eap }, + /* and so on ... */ + + { "rlm_eap_md5", &rlm_eap_md5 }, + /* and so on ... */ + + { "rlm_sql_mysql", &rlm_sql_mysql }, + /* and so on ... */ + + { NULL, NULL } }; +#define fr_dlopenext lt_dlopenext +lt_dlhandle lt_dlopenext(const char *name) +{ + int i; -static void indexed_modcallable_free(void *data) + for (i = 0; lt_dlmodules[i].name != NULL; i++) { + if (strcmp(name, lt_dlmodules[i].name) == 0) { + return lt_dlmodules[i].ref; + } + } + + return NULL; +} + +void *lt_dlsym(lt_dlhandle handle, UNUSED const char *symbol) { - indexed_modcallable *c = data; + return handle; +} - modcallable_free(&c->modulelist); - free(c); +int lt_dlclose(lt_dlhandle handle) +{ + return 0; } -static uint32_t indexed_modcallable_hash(const void *data) +const char *lt_dlerror(void) +{ + return "Unspecified error"; +} + +#endif /* WITH_DLOPEN */ +#else /* WITHOUT_LIBLTDL */ + +/* + * Solve the issues of libraries linking to other libraries + * by using a newer libltdl API. + */ +#ifndef HAVE_LT_DLADVISE_INIT +#define fr_dlopenext lt_dlopenext +#else +static lt_dlhandle fr_dlopenext(const char *filename) +{ + lt_dlhandle handle = 0; + lt_dladvise advise; + + if (!lt_dladvise_init (&advise) && + !lt_dladvise_ext (&advise) && + !lt_dladvise_global (&advise)) { + handle = lt_dlopenadvise (filename, advise); + } + + lt_dladvise_destroy (&advise); + + return handle; +} +#endif /* HAVE_LT_DLADVISE_INIT */ +#endif /* WITHOUT_LIBLTDL */ + +static int virtual_server_idx(const char *name) { uint32_t hash; - const indexed_modcallable *c = data; - hash = lrad_hash(&c->comp, sizeof(c->comp)); - return lrad_hash_update(&c->idx, sizeof(c->idx), hash); + if (!name) return 0; + + hash = fr_hash_string(name); + + return hash & (VIRTUAL_SERVER_HASH_SIZE - 1); +} + +static void virtual_server_free(virtual_server_t *server) +{ + if (!server) return; + + if (server->components) rbtree_free(server->components); + server->components = NULL; + + free(server); +} + +void virtual_servers_free(time_t when) +{ + int i; + virtual_server_t **last; + + for (i = 0; i < VIRTUAL_SERVER_HASH_SIZE; i++) { + virtual_server_t *server, *next; + + last = &virtual_servers[i]; + for (server = virtual_servers[i]; + server != NULL; + server = next) { + next = server->next; + + /* + * If we delete it, fix the links so that + * we don't orphan anything. Also, + * delete it if it's old, AND a newer one + * was defined. + * + * Otherwise, the last pointer gets set to + * the one we didn't delete. + */ + if ((when == 0) || + ((server->created < when) && server->can_free)) { + *last = server->next; + virtual_server_free(server); + } else { + last = &(server->next); + } + } + } +} + +static void indexed_modcallable_free(void *data) +{ + indexed_modcallable *c = data; + + modcallable_free(&c->modulelist); + free(c); } static int indexed_modcallable_cmp(const void *one, const void *two) @@ -115,14 +318,64 @@ static int indexed_modcallable_cmp(const void *one, const void *two) /* + * Compare two module entries + */ +static int module_instance_cmp(const void *one, const void *two) +{ + const module_instance_t *a = one; + const module_instance_t *b = two; + + return strcmp(a->name, b->name); +} + + +static void module_instance_free_old(CONF_SECTION *cs, module_instance_t *node, + time_t when) +{ + fr_module_hup_t *mh, **last; + + /* + * Walk the list, freeing up old instances. + */ + last = &(node->mh); + while (*last) { + mh = *last; + + /* + * Free only every 60 seconds. + */ + if ((when - mh->when) < 60) { + last = &(mh->next); + continue; + } + + cf_section_parse_free(cs, mh->insthandle); + + if (node->entry->module->detach) { + (node->entry->module->detach)(mh->insthandle); + } else { + free(mh->insthandle); + } + + *last = mh->next; + free(mh); + } +} + + +/* * Free a module instance. */ static void module_instance_free(void *data) { module_instance_t *this = data; - if (this->entry->module->detach) + module_instance_free_old(this->cs, this, time(NULL) + 100); + + if (this->entry->module->detach) { (this->entry->module->detach)(this->insthandle); + } + #ifdef HAVE_PTHREAD_H if (this->mutex) { /* @@ -134,6 +387,7 @@ static void module_instance_free(void *data) free(this->mutex); } #endif + memset(this, 0, sizeof(*this)); free(this); } @@ -157,6 +411,7 @@ static void module_entry_free(void *data) module_entry_t *this = data; lt_dlclose(this->handle); /* ignore any errors */ + memset(this, 0, sizeof(*this)); free(this); } @@ -166,9 +421,11 @@ static void module_entry_free(void *data) */ int detach_modules(void) { - lrad_hash_table_free(components); + rbtree_free(instance_tree); rbtree_free(module_tree); + lt_dlexit(); + return 0; } @@ -177,30 +434,20 @@ int detach_modules(void) * Find a module on disk or in memory, and link to it. */ static module_entry_t *linkto_module(const char *module_name, - const char *cffilename, int cflineno) + CONF_SECTION *cs) { module_entry_t myentry; module_entry_t *node; - lt_dlhandle handle; + lt_dlhandle handle = NULL; char module_struct[256]; char *p; - const void *module; + const module_t *module; - strNcpy(myentry.name, module_name, sizeof(myentry.name)); + strlcpy(myentry.name, module_name, sizeof(myentry.name)); node = rbtree_finddata(module_tree, &myentry); if (node) return node; /* - * Keep the handle around so we can dlclose() it. - */ - handle = lt_dlopenext(module_name); - if (handle == NULL) { - radlog(L_ERR|L_CONS, "%s[%d] Failed to link to module '%s':" - " %s\n", cffilename, cflineno, module_name, lt_dlerror()); - return NULL; - } - - /* * Link to the module's rlm_FOO{} module structure. * * The module_name variable has the version number @@ -210,6 +457,22 @@ static module_entry_t *linkto_module(const char *module_name, p = strrchr(module_struct, '-'); if (p) *p = '\0'; +#if defined(WITHOUT_LIBLTDL) && defined (WITH_DLOPEN) && defined(RTLD_SELF) + module = lt_dlsym(RTLD_SELF, module_struct); + if (module) goto open_self; +#endif + + /* + * Keep the handle around so we can dlclose() it. + */ + handle = fr_dlopenext(module_name); + if (handle == NULL) { + cf_log_err(cf_sectiontoitem(cs), + "Failed to link to module '%s': %s\n", + module_name, lt_dlerror()); + return NULL; + } + DEBUG3(" (Loaded %s, checking if it's valid)", module_name); /* @@ -218,32 +481,36 @@ static module_entry_t *linkto_module(const char *module_name, */ module = lt_dlsym(handle, module_struct); if (!module) { - radlog(L_ERR|L_CONS, "%s[%d] Failed linking to " - "%s structure in %s: %s\n", - cffilename, cflineno, - module_name, cffilename, lt_dlerror()); + cf_log_err(cf_sectiontoitem(cs), + "Failed linking to %s structure: %s\n", + module_name, lt_dlerror()); lt_dlclose(handle); return NULL; } + +#if defined(WITHOUT_LIBLTDL) && defined (WITH_DLOPEN) && defined(RTLD_SELF) + open_self: +#endif /* * Before doing anything else, check if it's sane. */ - if ((*(const uint32_t *) module) != RLM_MODULE_MAGIC_NUMBER) { + if (module->magic != RLM_MODULE_MAGIC_NUMBER) { lt_dlclose(handle); - radlog(L_ERR|L_CONS, "%s[%d] Invalid version in module '%s'", - cffilename, cflineno, module_name); + cf_log_err(cf_sectiontoitem(cs), + "Invalid version in module '%s'", + module_name); return NULL; - + } /* make room for the module type */ node = rad_malloc(sizeof(*node)); memset(node, 0, sizeof(*node)); - strNcpy(node->name, module_name, sizeof(node->name)); + strlcpy(node->name, module_name, sizeof(node->name)); node->module = module; node->handle = handle; - DEBUG("Module: Loaded %s ", node->module->name); + cf_log_module(cs, "Linked to module %s", module_name); /* * Add the module as "rlm_foo-version" to the configuration @@ -263,11 +530,12 @@ static module_entry_t *linkto_module(const char *module_name, * Find a module instance. */ module_instance_t *find_module_instance(CONF_SECTION *modules, - const char *instname) + const char *instname, int do_link) { + int check_config_safe = FALSE; CONF_SECTION *cs; - const char *name1, *name2; - module_instance_t *node; + const char *name1; + module_instance_t *node, myNode; char module_name[256]; if (!modules) return NULL; @@ -280,18 +548,20 @@ module_instance_t *find_module_instance(CONF_SECTION *modules, */ cs = cf_section_sub_find_name2(modules, NULL, instname); if (cs == NULL) { - radlog(L_ERR|L_CONS, "ERROR: Cannot find a configuration entry for module \"%s\".\n", instname); + radlog(L_ERR, "ERROR: Cannot find a configuration entry for module \"%s\".\n", instname); return NULL; } /* * If there's already a module instance, return it. */ - node = cf_data_find(cs, "instance"); + strlcpy(myNode.name, instname, sizeof(myNode.name)); + node = rbtree_finddata(instance_tree, &myNode); if (node) return node; + if (!do_link) return NULL; + name1 = cf_section_name1(cs); - name2 = cf_section_name2(cs); /* * Found the configuration entry. @@ -300,6 +570,7 @@ module_instance_t *find_module_instance(CONF_SECTION *modules, memset(node, 0, sizeof(*node)); node->insthandle = NULL; + node->cs = cs; /* * Names in the "modules" section aren't prefixed @@ -307,24 +578,40 @@ module_instance_t *find_module_instance(CONF_SECTION *modules, */ snprintf(module_name, sizeof(module_name), "rlm_%s", name1); - node->entry = linkto_module(module_name, - mainconfig.radiusd_conf, - cf_section_lineno(cs)); + node->entry = linkto_module(module_name, cs); if (!node->entry) { free(node); /* linkto_module logs any errors */ return NULL; } + if (check_config && (node->entry->module->instantiate) && + (node->entry->module->type & RLM_TYPE_CHECK_CONFIG_SAFE) == 0) { + const char *value = NULL; + CONF_PAIR *cp; + + cp = cf_pair_find(cs, "force_check_config"); + if (cp) value = cf_pair_value(cp); + + if (value && (strcmp(value, "yes") == 0)) goto print_inst; + + cf_log_module(cs, "Skipping instantiation of %s", instname); + } else { + print_inst: + check_config_safe = TRUE; + cf_log_module(cs, "Instantiating module \"%s\" from file %s", + instname, cf_section_filename(cs)); + } + /* * Call the module's instantiation routine. */ if ((node->entry->module->instantiate) && + (!check_config || check_config_safe) && ((node->entry->module->instantiate)(cs, &node->insthandle) < 0)) { - radlog(L_ERR|L_CONS, - "%s[%d]: %s: Module instantiation failed.\n", - mainconfig.radiusd_conf, cf_section_lineno(cs), - instname); + cf_log_err(cf_sectiontoitem(cs), + "Instantiation failed for module \"%s\"", + instname); free(node); return NULL; } @@ -333,7 +620,7 @@ module_instance_t *find_module_instance(CONF_SECTION *modules, * We're done. Fill in the rest of the data structure, * and link it to the module instance list. */ - strNcpy(node->name, instname, sizeof(node->name)); + strlcpy(node->name, instname, sizeof(node->name)); #ifdef HAVE_PTHREAD_H /* @@ -355,31 +642,30 @@ module_instance_t *find_module_instance(CONF_SECTION *modules, } #endif - cf_data_add(cs, "instance", node, module_instance_free); - - DEBUG("Module: Instantiated %s (%s) ", name1, node->name); + rbtree_insert(instance_tree, node); return node; } -static indexed_modcallable *lookup_by_index(int comp, int idx) +static indexed_modcallable *lookup_by_index(rbtree_t *components, + int comp, int idx) { indexed_modcallable myc; - + myc.comp = comp; myc.idx = idx; - return lrad_hash_table_finddata(components, &myc); + return rbtree_finddata(components, &myc); } /* * Create a new sublist. */ -static indexed_modcallable *new_sublist(int comp, int idx) +static indexed_modcallable *new_sublist(rbtree_t *components, int comp, int idx) { indexed_modcallable *c; - c = lookup_by_index(comp, idx); + c = lookup_by_index(components, comp, idx); /* It is an error to try to create a sublist that already * exists. It would almost certainly be caused by accidental @@ -401,7 +687,7 @@ static indexed_modcallable *new_sublist(int comp, int idx) c->comp = comp; c->idx = idx; - if (!lrad_hash_table_insert(components, c)) { + if (!rbtree_insert(components, c)) { free(c); return NULL; } @@ -409,26 +695,62 @@ static indexed_modcallable *new_sublist(int comp, int idx) return c; } -static int indexed_modcall(int comp, int idx, REQUEST *request) +int indexed_modcall(int comp, int idx, REQUEST *request) { int rcode; - indexed_modcallable *this; - - this = lookup_by_index(comp, idx); - if (!this) { - if (idx != 0) DEBUG2(" ERROR: Unknown value specified for %s. Cannot perform requested action.", - section_type_value[comp].typename); - request->component = section_type_value[comp].typename; - rcode = modcall(comp, NULL, request); /* does default action */ + modcallable *list = NULL; + virtual_server_t *server; + + /* + * Hack to find the correct virtual server. + */ + rcode = virtual_server_idx(request->server); + for (server = virtual_servers[rcode]; + server != NULL; + server = server->next) { + if (!request->server && !server->name) break; + + if ((request->server && server->name) && + (strcmp(request->server, server->name) == 0)) break; + } + + if (!server) { + RDEBUG("No such virtual server \"%s\"", request->server); + return RLM_MODULE_FAIL; + } + + if (idx == 0) { + list = server->mc[comp]; + if (!list) RDEBUG2(" WARNING: Empty %s section. Using default return values.", section_type_value[comp].section); + } else { - DEBUG2(" Processing the %s section of %s", - section_type_value[comp].section, - mainconfig.radiusd_conf); - request->component = section_type_value[comp].typename; - rcode = modcall(comp, this->modulelist, request); + indexed_modcallable *this; + + this = lookup_by_index(server->components, comp, idx); + if (this) { + list = this->modulelist; + } else { + RDEBUG2(" WARNING: Unknown value specified for %s. Cannot perform requested action.", + section_type_value[comp].typename); + } + } + + if (server->subcs[comp]) { + if (idx == 0) { + RDEBUG("# Executing section %s from file %s", + section_type_value[comp].section, + cf_section_filename(server->subcs[comp])); + } else { + RDEBUG("# Executing group from file %s", + cf_section_filename(server->subcs[comp])); + } } - request->module = ""; - request->component = ""; + request->component = section_type_value[comp].section; + + rcode = modcall(comp, list, request); + + request->module = ""; + request->component = ""; return rcode; } @@ -436,9 +758,8 @@ static int indexed_modcall(int comp, int idx, REQUEST *request) * Load a sub-module list, as found inside an Auth-Type foo {} * block */ -static int load_subcomponent_section(modcallable *parent, - CONF_SECTION *cs, int comp, - const char *filename) +static int load_subcomponent_section(modcallable *parent, CONF_SECTION *cs, + rbtree_t *components, int attr, int comp) { indexed_modcallable *subcomp; modcallable *ml; @@ -446,49 +767,43 @@ static int load_subcomponent_section(modcallable *parent, const char *name2 = cf_section_name2(cs); rad_assert(comp >= RLM_COMPONENT_AUTH); - rad_assert(comp <= RLM_COMPONENT_COUNT); + rad_assert(comp < RLM_COMPONENT_COUNT); /* * Sanity check. */ if (!name2) { - radlog(L_ERR|L_CONS, - "%s[%d]: No name specified for %s block", - filename, cf_section_lineno(cs), - section_type_value[comp].typename); + cf_log_err(cf_sectiontoitem(cs), + "No name specified for %s block", + section_type_value[comp].typename); return 1; } /* * Compile the group. */ - ml = compile_modgroup(parent, comp, cs, filename); + ml = compile_modgroup(parent, comp, cs); if (!ml) { return 0; - } + } /* * We must assign a numeric index to this subcomponent. - * It is generated and placed in the dictionary by - * setup_modules(), when it loads the sections. If it - * isn't found, it's a serious error. + * It is generated and placed in the dictionary + * automatically. If it isn't found, it's a serious + * error. */ - dval = dict_valbyname(section_type_value[comp].attr, name2); + dval = dict_valbyname(attr, 0, name2); if (!dval) { - radlog(L_ERR|L_CONS, - "%s[%d] %s %s Not previously configured", - filename, cf_section_lineno(cs), - section_type_value[comp].typename, name2); + cf_log_err(cf_sectiontoitem(cs), + "%s %s Not previously configured", + section_type_value[comp].typename, name2); modcallable_free(&ml); return 0; } - subcomp = new_sublist(comp, dval->value); + subcomp = new_sublist(components, comp, dval->value); if (!subcomp) { - radlog(L_ERR|L_CONS, - "%s[%d] %s %s already configured - skipping", - filename, cf_section_lineno(cs), - section_type_value[comp].typename, name2); modcallable_free(&ml); return 1; } @@ -497,9 +812,39 @@ static int load_subcomponent_section(modcallable *parent, return 1; /* OK */ } -static int load_component_section(modcallable *parent, - CONF_SECTION *cs, int comp, - const char *filename) +static int define_type(const DICT_ATTR *dattr, const char *name) +{ + uint32_t value; + DICT_VALUE *dval; + + /* + * If the value already exists, don't + * create it again. + */ + dval = dict_valbyname(dattr->attr, dattr->vendor, name); + if (dval) return 1; + + /* + * Create a new unique value with a + * meaningless number. You can't look at + * it from outside of this code, so it + * doesn't matter. The only requirement + * is that it's unique. + */ + do { + value = fr_rand() & 0x00ffffff; + } while (dict_valbyattr(dattr->attr, dattr->vendor, value)); + + if (dict_addvalue(name, dattr->name, value) < 0) { + radlog(L_ERR, "%s", fr_strerror()); + return 0; + } + + return 1; +} + +static int load_component_section(CONF_SECTION *cs, + rbtree_t *components, int comp) { modcallable *this; CONF_ITEM *modref; @@ -507,55 +852,51 @@ static int load_component_section(modcallable *parent, indexed_modcallable *subcomp; const char *modname; const char *visiblename; + const DICT_ATTR *dattr; /* - * Loop over the entries in the named section. + * Find the attribute used to store VALUEs for this section. + */ + dattr = dict_attrbyvalue(section_type_value[comp].attr, 0); + if (!dattr) { + cf_log_err(cf_sectiontoitem(cs), + "No such attribute %s", + section_type_value[comp].typename); + return -1; + } + + /* + * Loop over the entries in the named section, loading + * the sections this time. */ for (modref = cf_item_find_next(cs, NULL); modref != NULL; modref = cf_item_find_next(cs, modref)) { + const char *name1; CONF_PAIR *cp = NULL; CONF_SECTION *scs = NULL; - /* - * Look for Auth-Type foo {}, which are special - * cases of named sections, and allowable ONLY - * at the top-level. - * - * i.e. They're not allowed in a "group" or "redundant" - * subsection. - */ if (cf_item_is_section(modref)) { - const char *sec_name; scs = cf_itemtosection(modref); - sec_name = cf_section_name1(scs); + name1 = cf_section_name1(scs); - if (strcmp(sec_name, + if (strcmp(name1, section_type_value[comp].typename) == 0) { - if (!load_subcomponent_section(parent, scs, - comp, - filename)) { + if (!load_subcomponent_section(NULL, scs, + components, + dattr->attr, + comp)) { return -1; /* FIXME: memleak? */ } continue; } - /* - * Allow old names, too. - */ - if (strcmp(sec_name, - old_section_type_value[comp].typename) == 0) { - if (!load_subcomponent_section(parent, scs, - comp, - filename)) { - return -1; /* FIXME: memleak? */ - } - continue; - } cp = NULL; + } else if (cf_item_is_pair(modref)) { cp = cf_itemtopair(modref); + } else { continue; /* ignore it */ } @@ -563,45 +904,48 @@ static int load_component_section(modcallable *parent, /* * Try to compile one entry. */ - this = compile_modsingle(parent, comp, modref, filename, - &modname); + this = compile_modsingle(NULL, comp, modref, &modname); if (!this) { - radlog(L_ERR|L_CONS, - "%s[%d] Failed to parse %s section.\n", - filename, cf_section_lineno(cs), - cf_section_name1(cs)); + cf_log_err(cf_sectiontoitem(cs), + "Errors parsing %s section.\n", + cf_section_name1(cs)); return -1; } + /* + * Look for Auth-Type foo {}, which are special + * cases of named sections, and allowable ONLY + * at the top-level. + * + * i.e. They're not allowed in a "group" or "redundant" + * subsection. + */ if (comp == RLM_COMPONENT_AUTH) { DICT_VALUE *dval; const char *modrefname = NULL; - int lineno = 0; - if (cp) { modrefname = cf_pair_attr(cp); - lineno = cf_pair_lineno(cp); } else { modrefname = cf_section_name2(scs); - lineno = cf_section_lineno(scs); if (!modrefname) { - radlog(L_ERR|L_CONS, - "%s[%d] Failed to parse %s sub-section.\n", - filename, lineno, - cf_section_name1(scs)); + modcallable_free(&this); + cf_log_err(cf_sectiontoitem(cs), + "Errors parsing %s sub-section.\n", + cf_section_name1(scs)); return -1; } } - dval = dict_valbyname(PW_AUTH_TYPE, modrefname); + dval = dict_valbyname(PW_AUTH_TYPE, 0, modrefname); if (!dval) { /* * It's a section, but nothing we * recognize. Die! */ - radlog(L_ERR|L_CONS, "%s[%d] Unknown Auth-Type \"%s\" in %s sub-section.", - filename, lineno, - modrefname, section_type_value[comp].section); + modcallable_free(&this); + cf_log_err(cf_sectiontoitem(cs), + "Unknown Auth-Type \"%s\" in %s sub-section.", + modrefname, section_type_value[comp].section); return -1; } idx = dval->value; @@ -611,12 +955,8 @@ static int load_component_section(modcallable *parent, idx = 0; } - subcomp = new_sublist(comp, idx); + subcomp = new_sublist(components, comp, idx); if (subcomp == NULL) { - radlog(L_INFO|L_CONS, - "%s %s %s already configured - skipping", - filename, section_type_value[comp].typename, - modname); modcallable_free(&this); continue; } @@ -633,270 +973,486 @@ static int load_component_section(modcallable *parent, return 0; } - -/* - * Parse the module config sections, and load - * and call each module's init() function. - * - * Libtool makes your life a LOT easier, especially with libltdl. - * see: http://www.gnu.org/software/libtool/ - */ -int setup_modules(int reload) +static int load_byserver(CONF_SECTION *cs) { - int comp; - CONF_SECTION *cs, *modules; - int do_component[RLM_COMPONENT_COUNT]; - rad_listen_t *listener; + int comp, flag; + const char *name = cf_section_name2(cs); + rbtree_t *components; + virtual_server_t *server = NULL; + indexed_modcallable *c; + + if (name) { + cf_log_info(cs, "server %s { # from file %s", + name, cf_section_filename(cs)); + } else { + cf_log_info(cs, "server { # from file %s", + cf_section_filename(cs)); + } + + cf_log_info(cs, " modules {"); + + components = rbtree_create(indexed_modcallable_cmp, + indexed_modcallable_free, 0); + if (!components) { + radlog(L_ERR, "Failed to initialize components\n"); + goto error; + } + + server = rad_malloc(sizeof(*server)); + memset(server, 0, sizeof(*server)); + + server->name = name; + server->created = time(NULL); + server->cs = cs; + server->components = components; /* - * If necessary, initialize libltdl. + * Define types first. */ - if (!reload) { + for (comp = 0; comp < RLM_COMPONENT_COUNT; ++comp) { + CONF_SECTION *subcs; + CONF_ITEM *modref; + DICT_ATTR *dattr; + + subcs = cf_section_sub_find(cs, + section_type_value[comp].section); + if (!subcs) continue; + + if (cf_item_find_next(subcs, NULL) == NULL) continue; + /* - * Set the default list of preloaded symbols. - * This is used to initialize libltdl's list of - * preloaded modules. - * - * i.e. Static modules. + * Find the attribute used to store VALUEs for this section. */ - LTDL_SET_PRELOADED_SYMBOLS(); - - if (lt_dlinit() != 0) { - radlog(L_ERR|L_CONS, "Failed to initialize libraries: %s\n", - lt_dlerror()); + dattr = dict_attrbyvalue(section_type_value[comp].attr, 0); + if (!dattr) { + cf_log_err(cf_sectiontoitem(subcs), + "No such attribute %s", + section_type_value[comp].typename); + error: + if (debug_flag == 0) { + radlog(L_ERR, "Failed to load virtual server %s", + (name != NULL) ? name : ""); + } + virtual_server_free(server); return -1; } /* - * Set the search path to ONLY our library directory. - * This prevents the modules from being found from - * any location on the disk. + * Define dynamic types, so that others can reference + * them. */ - lt_dlsetsearchpath(radlib_dir); + for (modref = cf_item_find_next(subcs, NULL); + modref != NULL; + modref = cf_item_find_next(subcs, modref)) { + const char *name1; + CONF_SECTION *subsubcs; + + /* + * Create types for simple references + * only when parsing the authenticate + * section. + */ + if ((section_type_value[comp].attr == PW_AUTH_TYPE) && + cf_item_is_pair(modref)) { + CONF_PAIR *cp = cf_itemtopair(modref); + if (!define_type(dattr, cf_pair_attr(cp))) { + goto error; + } - DEBUG2("Module: Library search path is %s", - lt_dlgetsearchpath()); + continue; + } + + if (!cf_item_is_section(modref)) continue; + + subsubcs = cf_itemtosection(modref); + name1 = cf_section_name1(subsubcs); + + if (strcmp(name1, section_type_value[comp].typename) == 0) { + if (!define_type(dattr, + cf_section_name2(subsubcs))) { + goto error; + } + } + } + } /* loop over components */ + + /* + * Loop over all of the known components, finding their + * configuration section, and loading it. + */ + flag = 0; + for (comp = 0; comp < RLM_COMPONENT_COUNT; ++comp) { + CONF_SECTION *subcs; + + subcs = cf_section_sub_find(cs, + section_type_value[comp].section); + if (!subcs) continue; + + if (cf_item_find_next(subcs, NULL) == NULL) continue; + + cf_log_module(cs, "Checking %s {...} for more modules to load", + section_type_value[comp].section); /* - * Set up the internal module struct. + * Skip pre/post-proxy sections if we're not + * proxying. */ - module_tree = rbtree_create(module_entry_cmp, - module_entry_free, 0); - if (!module_tree) { - radlog(L_ERR|L_CONS, "Failed to initialize modules\n"); - return -1; + if ( +#ifdef WITH_PROXY + !mainconfig.proxy_requests && +#endif + ((comp == RLM_COMPONENT_PRE_PROXY) || + (comp == RLM_COMPONENT_POST_PROXY))) { + continue; } - } else { - lrad_hash_table_free(components); + + if (load_component_section(subcs, components, comp) < 0) { + goto error; + } + + /* + * Cache a default, if it exists. Some people + * put empty sections for some reason... + */ + c = lookup_by_index(components, comp, 0); + if (c) server->mc[comp] = c->modulelist; + + server->subcs[comp] = subcs; + + flag = 1; + } /* loop over components */ + + /* + * We haven't loaded any of the normal sections. Maybe we're + * supposed to load the vmps section. + * + * This is a bit of a hack... + */ + if (!flag) { + CONF_SECTION *subcs; + + subcs = cf_section_sub_find(cs, "vmps"); + if (subcs) { + cf_log_module(cs, "Checking vmps {...} for more modules to load"); + if (load_component_section(subcs, components, + RLM_COMPONENT_POST_AUTH) < 0) { + goto error; + } + c = lookup_by_index(components, + RLM_COMPONENT_POST_AUTH, 0); + if (c) server->mc[RLM_COMPONENT_POST_AUTH] = c->modulelist; + flag = 1; + } + +#ifdef WITH_DHCP + if (!flag) { + const DICT_ATTR *dattr; + + dattr = dict_attrbyname("DHCP-Message-Type"); + + /* + * Handle each DHCP Message type separately. + */ + if (dattr) for (subcs = cf_subsection_find_next(cs, NULL, "dhcp"); + subcs != NULL; + subcs = cf_subsection_find_next(cs, subcs, + "dhcp")) { + const char *name2 = cf_section_name2(subcs); + + DEBUG2(" Module: Checking dhcp %s {...} for more modules to load", name2); + if (!load_subcomponent_section(NULL, subcs, + components, + dattr->attr, + RLM_COMPONENT_POST_AUTH)) { + goto error; /* FIXME: memleak? */ + } + c = lookup_by_index(components, + RLM_COMPONENT_POST_AUTH, 0); + if (c) server->mc[RLM_COMPONENT_POST_AUTH] = c->modulelist; + flag = 1; + } + } +#endif } - components = lrad_hash_table_create(indexed_modcallable_hash, - indexed_modcallable_cmp, - indexed_modcallable_free); - if (!components) { - radlog(L_ERR|L_CONS, "Failed to initialize components\n"); - return -1; + cf_log_info(cs, " } # modules"); + cf_log_info(cs, "} # server"); + + if (!flag && name) { + DEBUG("WARNING: Server %s is empty, and will do nothing!", + name); + } + + if (debug_flag == 0) { + radlog(L_INFO, "Loaded virtual server %s", + (name != NULL) ? name : ""); } /* - * Figure out which sections to load. + * Now that it is OK, insert it into the list. + * + * This is thread-safe... */ - memset(do_component, 0, sizeof(do_component)); - for (listener = mainconfig.listen; - listener != NULL; - listener = listener->next) { - switch (listener->type) { - case RAD_LISTEN_AUTH: - do_component[RLM_COMPONENT_AUTZ] = 1; - do_component[RLM_COMPONENT_AUTH] = 1; - do_component[RLM_COMPONENT_POST_AUTH] = 1; - do_component[RLM_COMPONENT_SESS] = 1; - break; + comp = virtual_server_idx(name); + server->next = virtual_servers[comp]; + virtual_servers[comp] = server; - case RAD_LISTEN_DETAIL: /* just like acct */ - case RAD_LISTEN_ACCT: - do_component[RLM_COMPONENT_PREACCT] = 1; - do_component[RLM_COMPONENT_ACCT] = 1; + /* + * Mark OLDER ones of the same name as being unused. + */ + server = server->next; + while (server) { + if ((!name && !server->name) || + (name && server->name && + (strcmp(server->name, name) == 0))) { + server->can_free = TRUE; break; + } + server = server->next; + } - case RAD_LISTEN_PROXY: - do_component[RLM_COMPONENT_PRE_PROXY] = 1; - do_component[RLM_COMPONENT_POST_PROXY] = 1; - break; + return 0; +} - default: - rad_assert(0 == 1); - break; + +/* + * Load all of the virtual servers. + */ +int virtual_servers_load(CONF_SECTION *config) +{ + int null_server = FALSE; + CONF_SECTION *cs; + static int first_time = TRUE; + + DEBUG2("%s: #### Loading Virtual Servers ####", mainconfig.name); + + /* + * Load all of the virtual servers. + */ + for (cs = cf_subsection_find_next(config, NULL, "server"); + cs != NULL; + cs = cf_subsection_find_next(config, cs, "server")) { + if (!cf_section_name2(cs)) null_server = TRUE; + + if (load_byserver(cs) < 0) { + /* + * Once we successfully staryed once, + * continue loading the OTHER servers, + * even if one fails. + */ + if (!first_time) continue; + return -1; } } - for (comp = RLM_COMPONENT_AUTH; comp < RLM_COMPONENT_COUNT; comp++) { - /* - * Have the debugging messages all in one place. - */ - if (!do_component[comp]) { - DEBUG2("modules: Not loading %s{} section", - section_type_value[comp].section); + /* + * No empty server defined. Try to load an old-style + * one for backwards compatibility. + */ + if (!null_server) { + if (load_byserver(config) < 0) { + return -1; } } /* - * Create any DICT_VALUE's for the types. See - * 'doc/configurable_failover' for examples of 'authtype' - * used to create new Auth-Type values. In order to - * let the user create new names, we've got to look for - * those names, and create DICT_VALUE's for them. + * If we succeed the first time around, remember that. + */ + first_time = FALSE; + + return 0; +} + +int module_hup_module(CONF_SECTION *cs, module_instance_t *node, time_t when) +{ + void *insthandle = NULL; + fr_module_hup_t *mh; + + if (!node || + !node->entry->module->instantiate || + ((node->entry->module->type & RLM_TYPE_HUP_SAFE) == 0)) { + return 1; + } + + cf_log_module(cs, "Trying to reload module \"%s\"", node->name); + + if ((node->entry->module->instantiate)(cs, &insthandle) < 0) { + cf_log_err(cf_sectiontoitem(cs), + "HUP failed for module \"%s\". Using old configuration.", + node->name); + return 0; + } + + radlog(L_INFO, " Module: Reloaded module \"%s\"", node->name); + + module_instance_free_old(cs, node, when); + + /* + * Save the old instance handle for later deletion. */ - for (comp = RLM_COMPONENT_AUTH; comp < RLM_COMPONENT_COUNT; comp++) { - int value; - const char *name2; - DICT_ATTR *dattr; - DICT_VALUE *dval; - CONF_SECTION *sub, *next; - CONF_PAIR *cp; + mh = rad_malloc(sizeof(*mh)); + mh->mi = node; + mh->when = when; + mh->insthandle = node->insthandle; + mh->next = node->mh; + node->mh = mh; + + node->insthandle = insthandle; + + /* + * FIXME: Set a timeout to come back in 60s, so that + * we can pro-actively clean up the old instances. + */ + + return 1; +} + + +int module_hup(CONF_SECTION *modules) +{ + time_t when; + CONF_ITEM *ci; + CONF_SECTION *cs; + module_instance_t *node; + + if (!modules) return 0; + + when = time(NULL); + + /* + * Loop over the modules + */ + for (ci=cf_item_find_next(modules, NULL); + ci != NULL; + ci=cf_item_find_next(modules, ci)) { + const char *instname; + module_instance_t myNode; /* - * Not needed, don't load it. + * If it's not a section, ignore it. */ - if (!do_component[comp]) { - continue; - } - cs = cf_section_find(section_type_value[comp].section); + if (!cf_item_is_section(ci)) continue; - if (!cs) continue; - - sub = NULL; - do { - /* - * See if there's a sub-section by that - * name. - */ - next = cf_subsection_find_next(cs, sub, - section_type_value[comp].typename); + cs = cf_itemtosection(ci); + instname = cf_section_name2(cs); + if (!instname) instname = cf_section_name1(cs); - /* - * Allow some old names, too. - */ - if (!next && (comp <= 4)) { - next = cf_subsection_find_next(cs, sub, - old_section_type_value[comp].typename); - } - sub = next; + strlcpy(myNode.name, instname, sizeof(myNode.name)); + node = rbtree_finddata(instance_tree, &myNode); - /* - * If so, look for it to define a new - * value. - */ - name2 = cf_section_name2(sub); - if (!name2) continue; + module_hup_module(cs, node, when); + } + return 1; +} - /* - * If the value already exists, don't - * create it again. - */ - dval = dict_valbyname(section_type_value[comp].attr, - name2); - if (dval) continue; - /* - * Find the attribute for the value. - */ - dattr = dict_attrbyvalue(section_type_value[comp].attr); - if (!dattr) { - radlog(L_ERR, "%s[%d]: No such attribute %s", - mainconfig.radiusd_conf, - cf_section_lineno(sub), - section_type_value[comp].typename); - continue; - } +/* + * Parse the module config sections, and load + * and call each module's init() function. + * + * Libtool makes your life a LOT easier, especially with libltdl. + * see: http://www.gnu.org/software/libtool/ + */ +int setup_modules(int reload, CONF_SECTION *config) +{ + CONF_SECTION *cs, *modules; + rad_listen_t *listener; - /* - * Create a new unique value with a - * meaningless number. You can't look at - * it from outside of this code, so it - * doesn't matter. The only requirement - * is that it's unique. - */ - do { - value = lrad_rand() & 0x00ffffff; - } while (dict_valbyattr(dattr->attr, value)); + if (reload) return 0; - if (dict_addvalue(name2, dattr->name, value) < 0) { - radlog(L_ERR, "%s", librad_errstr); - return -1; - } - } while (sub != NULL); + /* + * If necessary, initialize libltdl. + */ + if (!reload) { + /* + * This line works around a completely + * + * RIDICULOUS INSANE IDIOTIC + * + * bug in libltdl on certain systems. The "set + * preloaded symbols" macro below ends up + * referencing this name, but it isn't defined + * anywhere in the libltdl source. As a result, + * any program STUPID enough to rely on libltdl + * fails to link, because the symbol isn't + * defined anywhere. + * + * It's like libtool and libltdl are some kind + * of sick joke. + */ +#ifdef IE_LIBTOOL_DIE +#define lt__PROGRAM__LTX_preloaded_symbols lt_libltdl_LTX_preloaded_symbols +#endif /* - * Loop over the non-sub-sections, too. + * Set the default list of preloaded symbols. + * This is used to initialize libltdl's list of + * preloaded modules. + * + * i.e. Static modules. */ - cp = NULL; - do { - /* - * See if there's a conf-pair by that - * name. - */ - cp = cf_pair_find_next(cs, cp, NULL); - if (!cp) break; + LTDL_SET_PRELOADED_SYMBOLS(); + if (lt_dlinit() != 0) { + radlog(L_ERR, "Failed to initialize libraries: %s\n", + lt_dlerror()); + return -1; + } - /* - * If the value already exists, don't - * create it again. - */ - name2 = cf_pair_attr(cp); - dval = dict_valbyname(section_type_value[comp].attr, - name2); - if (dval) continue; + /* + * Set the search path to ONLY our library directory. + * This prevents the modules from being found from + * any location on the disk. + */ + lt_dlsetsearchpath(radlib_dir); - /* - * Find the attribute for the value. - */ - dattr = dict_attrbyvalue(section_type_value[comp].attr); - if (!dattr) { - radlog(L_ERR, "%s[%d]: No such attribute %s", - mainconfig.radiusd_conf, - cf_section_lineno(sub), - section_type_value[comp].typename); - continue; - } + /* + * Set up the internal module struct. + */ + module_tree = rbtree_create(module_entry_cmp, + module_entry_free, 0); + if (!module_tree) { + radlog(L_ERR, "Failed to initialize modules\n"); + return -1; + } - /* - * Finally, create the new attribute. - */ - do { - value = lrad_rand() & 0x00ffffff; - } while (dict_valbyattr(dattr->attr, value)); - if (dict_addvalue(name2, dattr->name, value) < 0) { - radlog(L_ERR, "%s", librad_errstr); - return -1; - } - } while (cp != NULL); - } /* over the sections which can have redundent sub-sections */ + instance_tree = rbtree_create(module_instance_cmp, + module_instance_free, 0); + if (!instance_tree) { + radlog(L_ERR, "Failed to initialize modules\n"); + return -1; + } + } + + memset(virtual_servers, 0, sizeof(virtual_servers)); /* * Remember where the modules were stored. */ - modules = cf_section_find("modules"); + modules = cf_section_sub_find(config, "modules"); if (!modules) { radlog(L_ERR, "Cannot find a \"modules\" section in the configuration file!"); return -1; } + DEBUG2("%s: #### Instantiating modules ####", mainconfig.name); + /* * Look for the 'instantiate' section, which tells us * the instantiation order of the modules, and also allows * us to load modules with no authorize/authenticate/etc. * sections. */ - cs = cf_section_find("instantiate"); + cs = cf_section_sub_find(config, "instantiate"); if (cs != NULL) { CONF_ITEM *ci; CONF_PAIR *cp; module_instance_t *module; const char *name; + cf_log_info(cs, " instantiate {"); + /* * Loop over the items in the 'instantiate' section. */ @@ -905,40 +1461,50 @@ int setup_modules(int reload) ci=cf_item_find_next(cs, ci)) { /* - * Skip sections. They'll be handled - * later, if they're referenced at all... + * Skip sections and "other" stuff. + * Sections will be handled later, if + * they're referenced at all... */ - if (cf_item_is_section(ci)) { + if (!cf_item_is_pair(ci)) { continue; } cp = cf_itemtopair(ci); name = cf_pair_attr(cp); - module = find_module_instance(modules, name); + module = find_module_instance(modules, name, 1); if (!module) { return -1; } } /* loop over items in the subsection */ + + cf_log_info(cs, " }"); } /* if there's an 'instantiate' section. */ /* - * Loop over all of the known components, finding their - * configuration section, and loading it. + * Loop over the listeners, figuring out which sections + * to load. */ - for (comp = 0; comp < RLM_COMPONENT_COUNT; ++comp) { - cs = cf_section_find(section_type_value[comp].section); - if (cs == NULL) - continue; + for (listener = mainconfig.listen; + listener != NULL; + listener = listener->next) { + char buffer[256]; - if (!do_component[comp]) { - continue; - } +#ifdef WITH_PROXY + if (listener->type == RAD_LISTEN_PROXY) continue; +#endif - if (load_component_section(NULL, cs, comp, mainconfig.radiusd_conf) < 0) { + cs = cf_section_sub_find_name2(config, + "server", listener->server); + if (!cs && (listener->server != NULL)) { + listener->print(listener, buffer, sizeof(buffer)); + + radlog(L_ERR, "No server has been defined for %s", buffer); return -1; } } + if (virtual_servers_load(config) < 0) return -1; + return 0; } @@ -948,20 +1514,6 @@ int setup_modules(int reload) */ int module_authorize(int autz_type, REQUEST *request) { - /* - * Older versions of the server would pass proxy requests - * through the 'authorize' sections twice; once when the - * packet was received from the NAS, and again after the - * reply was received from the home server. Now that we - * have a 'post_proxy' section, the replies from the home - * server should be sent through that, instead of through - * the 'authorize' section again. - */ - if (request->proxy != NULL) { - DEBUG2(" authorize: Skipping authorize in post-proxy stage"); - return RLM_MODULE_NOOP; - } - return indexed_modcall(RLM_COMPONENT_AUTZ, autz_type, request); } @@ -973,6 +1525,7 @@ int module_authenticate(int auth_type, REQUEST *request) return indexed_modcall(RLM_COMPONENT_AUTH, auth_type, request); } +#ifdef WITH_ACCOUNTING /* * Do pre-accounting for ALL configured sessions */ @@ -988,7 +1541,9 @@ int module_accounting(int acct_type, REQUEST *request) { return indexed_modcall(RLM_COMPONENT_ACCT, acct_type, request); } +#endif +#ifdef WITH_SESSION_MGMT /* * See if a user is already logged in. * @@ -1014,7 +1569,9 @@ int module_checksimul(int sess_type, REQUEST *request, int maxsimul) return (request->simul_count < maxsimul) ? 0 : request->simul_mpp; } +#endif +#ifdef WITH_PROXY /* * Do pre-proxying for ALL configured sessions */ @@ -1030,6 +1587,7 @@ int module_post_proxy(int type, REQUEST *request) { return indexed_modcall(RLM_COMPONENT_POST_PROXY, type, request); } +#endif /* * Do post-authentication for ALL configured sessions @@ -1039,3 +1597,14 @@ int module_post_auth(int postauth_type, REQUEST *request) return indexed_modcall(RLM_COMPONENT_POST_AUTH, postauth_type, request); } +#ifdef WITH_COA +int module_recv_coa(int recv_coa_type, REQUEST *request) +{ + return indexed_modcall(RLM_COMPONENT_RECV_COA, recv_coa_type, request); +} + +int module_send_coa(int send_coa_type, REQUEST *request) +{ + return indexed_modcall(RLM_COMPONENT_SEND_COA, send_coa_type, request); +} +#endif