/* Copyright (C) 2023 CZ.NIC, z.s.p.o. 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 3 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, see . */ #include #include #include #include #include #include #include "knot/conf/conf.h" #include "knot/conf/confio.h" #include "knot/conf/module.h" #include "knot/common/log.h" #include "knot/modules/static_modules.h" #include "knot/nameserver/query_module.h" #include "contrib/openbsd/strlcat.h" #include "contrib/string.h" #define LIB_EXTENSION ".so" knot_dynarray_define(mod, module_t *, DYNARRAY_VISIBILITY_NORMAL) knot_dynarray_define(old_schema, yp_item_t *, DYNARRAY_VISIBILITY_NORMAL) static module_t STATIC_MODULES[] = { STATIC_MODULES_INIT { NULL } }; module_t *conf_mod_find( conf_t *conf, const char *name, size_t len, bool temporary) { if (conf == NULL || name == NULL) { return NULL; } // First, search in static modules. for (module_t *mod = STATIC_MODULES; mod->api != NULL; mod++) { if (strncmp(name, mod->api->name, len) == 0) { return mod; } } module_type_t excluded_type = temporary ? MOD_EXPLICIT : MOD_TEMPORARY; // Second, search in dynamic modules. knot_dynarray_foreach(mod, module_t *, module, conf->modules) { if ((*module) != NULL && (*module)->type != excluded_type && strncmp(name, (*module)->api->name, len) == 0) { return (*module); } } return NULL; } static int mod_load( conf_t *conf, module_t *mod) { static const yp_item_t module_common[] = { { C_ID, YP_TSTR, YP_VNONE, CONF_IO_FREF }, { C_COMMENT, YP_TSTR, YP_VNONE }, { NULL } }; yp_item_t *sub_items = NULL; int ret; if (mod->api->config != NULL) { ret = yp_schema_merge(&sub_items, module_common, mod->api->config); } else { ret = yp_schema_copy(&sub_items, module_common); } if (ret != KNOT_EOK) { return ret; } /* Synthesise module config section name. */ const size_t name_len = strlen(mod->api->name); if (name_len > YP_MAX_ITEM_NAME_LEN) { return KNOT_YP_EINVAL_ITEM; } char name[1 + YP_MAX_ITEM_NAME_LEN + 1]; name[0] = name_len; memcpy(name + 1, mod->api->name, name_len + 1); const yp_item_t schema[] = { { name, YP_TGRP, YP_VGRP = { sub_items }, YP_FALLOC | YP_FMULTI | CONF_IO_FRLD_MOD | CONF_IO_FRLD_ZONES, { mod->api->config_check } }, { NULL } }; yp_item_t *merged = NULL; ret = yp_schema_merge(&merged, conf->schema, schema); yp_schema_free(sub_items); if (ret != KNOT_EOK) { return ret; } // Update configuration schema (with lazy free). yp_item_t **current_schema = &conf->schema; yp_item_t *old_schema = rcu_xchg_pointer(current_schema, merged); synchronize_rcu(); old_schema_dynarray_add(&conf->old_schemas, &old_schema); return KNOT_EOK; } int conf_mod_load_common( conf_t *conf) { if (conf == NULL) { return KNOT_EINVAL; } int ret = KNOT_EOK; // First, load static modules. for (module_t *mod = STATIC_MODULES; mod->api != NULL; mod++) { ret = mod_load(conf, mod); if (ret != KNOT_EOK) { log_error("module '%s', failed to load (%s)", mod->api->name, knot_strerror(ret)); break; } log_debug("module '%s', loaded static", mod->api->name); } // Second, try to load implicit shared modules if configured. if (strlen(MODULE_DIR) > 0) { struct stat path_stat; glob_t glob_buf = { 0 }; char *path = sprintf_alloc("%s/*%s", MODULE_DIR, LIB_EXTENSION); if (path == NULL) { ret = KNOT_ENOMEM; } else if (stat(MODULE_DIR, &path_stat) != 0 || !S_ISDIR(path_stat.st_mode)) { if (errno == ENOENT) { // Module directory doesn't exist. ret = KNOT_EOK; } else { log_error("module, invalid directory '%s'", MODULE_DIR); ret = KNOT_EINVAL; } } else if (access(MODULE_DIR, F_OK | R_OK) != 0) { log_error("module, failed to access directory '%s'", MODULE_DIR); ret = KNOT_EACCES; } else { ret = glob(path, 0, NULL, &glob_buf); if (ret != 0 && ret != GLOB_NOMATCH) { log_error("module, failed to read directory '%s'", MODULE_DIR); ret = KNOT_EACCES; } else { ret = KNOT_EOK; } } // Process each module in the directory. for (size_t i = 0; i < glob_buf.gl_pathc; i++) { (void)conf_mod_load_extra(conf, NULL, glob_buf.gl_pathv[i], MOD_IMPLICIT); } globfree(&glob_buf); free(path); } conf_mod_load_purge(conf, false); return ret; } int conf_mod_load_extra( conf_t *conf, const char *mod_name, const char *file_name, module_type_t type) { if (conf == NULL || (mod_name == NULL && file_name == NULL)) { return KNOT_EINVAL; } // Synthesize module file name if not specified. char *tmp_name = NULL; if (file_name == NULL) { tmp_name = sprintf_alloc("%s/%s%s", MODULE_INSTDIR, mod_name + strlen(KNOTD_MOD_NAME_PREFIX), LIB_EXTENSION); if (tmp_name == NULL) { return KNOT_ENOMEM; } file_name = tmp_name; } void *handle = dlopen(file_name, RTLD_NOW | RTLD_LOCAL); if (handle == NULL) { log_error("module, failed to open '%s' (%s)", file_name, dlerror()); free(tmp_name); return KNOT_ENOENT; } (void)dlerror(); knotd_mod_api_t *api = dlsym(handle, "knotd_mod_api"); if (api == NULL) { char *err = dlerror(); if (err == NULL) { err = "empty symbol"; } log_error("module, invalid library '%s' (%s)", file_name, err); dlclose(handle); free(tmp_name); return KNOT_ENOENT; } free(tmp_name); if (api->version != KNOTD_MOD_ABI_VERSION) { log_error("module '%s', incompatible version", api->name); dlclose(handle); return KNOT_ENOTSUP; } if (api->name == NULL || (mod_name != NULL && strcmp(api->name, mod_name) != 0)) { log_error("module '%s', module name mismatch", api->name); dlclose(handle); return KNOT_ENOTSUP; } // Check if the module is already loaded. module_t *found = conf_mod_find(conf, api->name, strlen(api->name), type == MOD_TEMPORARY); if (found != NULL) { log_error("module '%s', duplicate module", api->name); dlclose(handle); return KNOT_EEXIST; } module_t *mod = calloc(1, sizeof(*mod)); if (mod == NULL) { dlclose(handle); return KNOT_ENOMEM; } mod->api = api; mod->lib_handle = handle; mod->type = type; int ret = mod_load(conf, mod); if (ret != KNOT_EOK) { log_error("module '%s', failed to load (%s)", api->name, knot_strerror(ret)); dlclose(handle); free(mod); return ret; } mod_dynarray_add(&conf->modules, &mod); log_debug("module '%s', loaded shared", api->name); return KNOT_EOK; } static void unload_shared( module_t *mod) { if (mod != NULL) { assert(mod->lib_handle); (void)dlclose(mod->lib_handle); free(mod); } } void conf_mod_load_purge( conf_t *conf, bool temporary) { if (conf == NULL) { return; } // Switch the current temporary schema with the initial one. if (temporary && conf->old_schemas.size > 0) { yp_item_t **current_schema = &conf->schema; yp_item_t **initial = &(conf->old_schemas.arr(&conf->old_schemas))[0]; yp_item_t *old_schema = rcu_xchg_pointer(current_schema, *initial); synchronize_rcu(); *initial = old_schema; } knot_dynarray_foreach(old_schema, yp_item_t *, schema, conf->old_schemas) { yp_schema_free(*schema); } old_schema_dynarray_free(&conf->old_schemas); knot_dynarray_foreach(mod, module_t *, module, conf->modules) { if ((*module) != NULL && (*module)->type == MOD_TEMPORARY) { unload_shared((*module)); *module = NULL; // Cannot remove from dynarray. } } } void conf_mod_unload_shared( conf_t *conf) { if (conf == NULL) { return; } knot_dynarray_foreach(mod, module_t *, module, conf->modules) { unload_shared((*module)); } mod_dynarray_free(&conf->modules); } #define LOG_ARGS(mod_id, msg) "module '%s%s%.*s', " msg, \ mod_id->name + 1, (mod_id->len > 0) ? "/" : "", (int)mod_id->len, \ mod_id->data #define MOD_ID_LOG(zone, level, mod_id, msg, ...) \ if (zone != NULL) \ log_zone_##level(zone, LOG_ARGS(mod_id, msg), ##__VA_ARGS__); \ else \ log_##level(LOG_ARGS(mod_id, msg), ##__VA_ARGS__); void conf_activate_modules( conf_t *conf, struct server *server, const knot_dname_t *zone_name, list_t *query_modules, struct query_plan **query_plan) { int ret = KNOT_EOK; if (conf == NULL || query_modules == NULL || query_plan == NULL) { ret = KNOT_EINVAL; goto activate_error; } conf_val_t val; // Get list of associated modules. if (zone_name != NULL) { val = conf_zone_get(conf, C_MODULE, zone_name); } else { val = conf_default_get(conf, C_GLOBAL_MODULE); } switch (val.code) { case KNOT_EOK: break; case KNOT_ENOENT: // Check if a module is configured at all. case KNOT_YP_EINVAL_ID: return; default: ret = val.code; goto activate_error; } // Create query plan. *query_plan = query_plan_create(); if (*query_plan == NULL) { ret = KNOT_ENOMEM; goto activate_error; } // Initialize query modules list. init_list(query_modules); // Open the modules. while (val.code == KNOT_EOK) { conf_mod_id_t *mod_id = conf_mod_id(&val); if (mod_id == NULL) { ret = KNOT_ENOMEM; goto activate_error; } // Open the module. knotd_mod_t *mod = query_module_open(conf, server, mod_id, *query_plan, zone_name); if (mod == NULL) { MOD_ID_LOG(zone_name, error, mod_id, "failed to open"); conf_free_mod_id(mod_id); goto skip_module; } // Check the module scope. if ((zone_name == NULL && !(mod->api->flags & KNOTD_MOD_FLAG_SCOPE_GLOBAL)) || (zone_name != NULL && !(mod->api->flags & KNOTD_MOD_FLAG_SCOPE_ZONE))) { MOD_ID_LOG(zone_name, error, mod_id, "out of scope"); query_module_close(mod); goto skip_module; } // Check if the module is loadable. if (mod->api->load == NULL) { MOD_ID_LOG(zone_name, debug, mod_id, "empty module, not loaded"); query_module_close(mod); goto skip_module; } // Load the module. ret = mod->api->load(mod); if (ret != KNOT_EOK) { MOD_ID_LOG(zone_name, error, mod_id, "failed to load (%s)", knot_strerror(ret)); query_module_close(mod); goto skip_module; } mod->config = NULL; // Invalidate the current config. add_tail(query_modules, &mod->node); skip_module: conf_val_next(&val); } return; activate_error: CONF_LOG(LOG_ERR, "failed to activate modules (%s)", knot_strerror(ret)); } void conf_deactivate_modules( list_t *query_modules, struct query_plan **query_plan) { if (query_modules == NULL || query_plan == NULL) { return; } // Free query plan. query_plan_free(*query_plan); *query_plan = NULL; // Free query modules list. knotd_mod_t *mod, *next; WALK_LIST_DELSAFE(mod, next, *query_modules) { if (mod->api->unload != NULL) { mod->api->unload(mod); } query_module_close(mod); } init_list(query_modules); } void conf_reset_modules( conf_t *conf, list_t *query_modules, struct query_plan **query_plan) { if (query_modules == NULL || query_plan == NULL) { return; } struct query_plan *new_plan = query_plan_create(); if (new_plan == NULL) { CONF_LOG(LOG_ERR, "failed to activate modules (%s)", knot_strerror(KNOT_ENOMEM)); return; } struct query_plan *old_plan = rcu_xchg_pointer(query_plan, NULL); synchronize_rcu(); query_plan_free(old_plan); knotd_mod_t *mod; WALK_LIST(mod, *query_modules) { if (mod->api->unload != NULL) { mod->api->unload(mod); } query_module_reset(conf, mod, new_plan); } knotd_mod_t *next; WALK_LIST_DELSAFE(mod, next, *query_modules) { int ret = mod->api->load(mod); if (ret != KNOT_EOK) { MOD_ID_LOG(mod->zone, error, mod->id, "failed to load (%s)", knot_strerror(ret)); rem_node(&mod->node); query_module_close(mod); continue; } mod->config = NULL; // Invalidate the current config. } (void)rcu_xchg_pointer(query_plan, new_plan); }