diff options
Diffstat (limited to '')
-rw-r--r-- | lib/ns/hooks.c | 544 |
1 files changed, 544 insertions, 0 deletions
diff --git a/lib/ns/hooks.c b/lib/ns/hooks.c new file mode 100644 index 0000000..1b8b8d0 --- /dev/null +++ b/lib/ns/hooks.c @@ -0,0 +1,544 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#if HAVE_DLFCN_H +#include <dlfcn.h> +#elif _WIN32 +#include <windows.h> +#endif /* if HAVE_DLFCN_H */ + +#include <isc/errno.h> +#include <isc/list.h> +#include <isc/log.h> +#include <isc/mem.h> +#include <isc/mutex.h> +#include <isc/platform.h> +#include <isc/print.h> +#include <isc/result.h> +#include <isc/types.h> +#include <isc/util.h> + +#include <dns/view.h> + +#include <ns/hooks.h> +#include <ns/log.h> +#include <ns/query.h> + +#define CHECK(op) \ + do { \ + result = (op); \ + if (result != ISC_R_SUCCESS) { \ + goto cleanup; \ + } \ + } while (0) + +struct ns_plugin { + isc_mem_t *mctx; + void *handle; + void *inst; + char *modpath; + ns_plugin_check_t *check_func; + ns_plugin_register_t *register_func; + ns_plugin_destroy_t *destroy_func; + LINK(ns_plugin_t) link; +}; + +static ns_hooklist_t default_hooktable[NS_HOOKPOINTS_COUNT]; +LIBNS_EXTERNAL_DATA ns_hooktable_t *ns__hook_table = &default_hooktable; + +isc_result_t +ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) { + int result; + +#ifndef WIN32 + /* + * On Unix systems, differentiate between paths and filenames. + */ + if (strchr(src, '/') != NULL) { + /* + * 'src' is an absolute or relative path. Copy it verbatim. + */ + result = snprintf(dst, dstsize, "%s", src); + } else { + /* + * 'src' is a filename. Prepend default plugin directory path. + */ + result = snprintf(dst, dstsize, "%s/%s", NAMED_PLUGINDIR, src); + } +#else /* ifndef WIN32 */ + /* + * On Windows, always copy 'src' do 'dst'. + */ + result = snprintf(dst, dstsize, "%s", src); +#endif /* ifndef WIN32 */ + + if (result < 0) { + return (isc_errno_toresult(errno)); + } else if ((size_t)result >= dstsize) { + return (ISC_R_NOSPACE); + } else { + return (ISC_R_SUCCESS); + } +} + +#if HAVE_DLFCN_H && HAVE_DLOPEN +static isc_result_t +load_symbol(void *handle, const char *modpath, const char *symbol_name, + void **symbolp) { + void *symbol = NULL; + + REQUIRE(handle != NULL); + REQUIRE(symbolp != NULL && *symbolp == NULL); + + /* + * Clear any pre-existing error conditions before running dlsym(). + * (In this case, we expect dlsym() to return non-NULL values + * and will always return an error if it returns NULL, but + * this ensures that we'll report the correct error condition + * if there is one.) + */ + dlerror(); + symbol = dlsym(handle, symbol_name); + if (symbol == NULL) { + const char *errmsg = dlerror(); + if (errmsg == NULL) { + errmsg = "returned function pointer is NULL"; + } + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, + "failed to look up symbol %s in " + "plugin '%s': %s", + symbol_name, modpath, errmsg); + return (ISC_R_FAILURE); + } + + *symbolp = symbol; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) { + isc_result_t result; + void *handle = NULL; + ns_plugin_t *plugin = NULL; + ns_plugin_check_t *check_func = NULL; + ns_plugin_register_t *register_func = NULL; + ns_plugin_destroy_t *destroy_func = NULL; + ns_plugin_version_t *version_func = NULL; + int version, flags; + + REQUIRE(pluginp != NULL && *pluginp == NULL); + + flags = RTLD_LAZY | RTLD_LOCAL; +#if defined(RTLD_DEEPBIND) && !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__ + flags |= RTLD_DEEPBIND; +#endif /* if defined(RTLD_DEEPBIND) && !__SANITIZE_ADDRESS__ && \ + !__SANITIZE_THREAD__ */ + + handle = dlopen(modpath, flags); + if (handle == NULL) { + const char *errmsg = dlerror(); + if (errmsg == NULL) { + errmsg = "unknown error"; + } + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, + "failed to dlopen() plugin '%s': %s", modpath, + errmsg); + return (ISC_R_FAILURE); + } + + CHECK(load_symbol(handle, modpath, "plugin_version", + (void **)&version_func)); + + version = version_func(); + if (version < (NS_PLUGIN_VERSION - NS_PLUGIN_AGE) || + version > NS_PLUGIN_VERSION) + { + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, + "plugin API version mismatch: %d/%d", version, + NS_PLUGIN_VERSION); + CHECK(ISC_R_FAILURE); + } + + CHECK(load_symbol(handle, modpath, "plugin_check", + (void **)&check_func)); + CHECK(load_symbol(handle, modpath, "plugin_register", + (void **)®ister_func)); + CHECK(load_symbol(handle, modpath, "plugin_destroy", + (void **)&destroy_func)); + + plugin = isc_mem_get(mctx, sizeof(*plugin)); + memset(plugin, 0, sizeof(*plugin)); + isc_mem_attach(mctx, &plugin->mctx); + plugin->handle = handle; + plugin->modpath = isc_mem_strdup(plugin->mctx, modpath); + plugin->check_func = check_func; + plugin->register_func = register_func; + plugin->destroy_func = destroy_func; + + ISC_LINK_INIT(plugin, link); + + *pluginp = plugin; + plugin = NULL; + +cleanup: + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, + "failed to dynamically load " + "plugin '%s': %s", + modpath, isc_result_totext(result)); + + if (plugin != NULL) { + isc_mem_putanddetach(&plugin->mctx, plugin, + sizeof(*plugin)); + } + + (void)dlclose(handle); + } + + return (result); +} + +static void +unload_plugin(ns_plugin_t **pluginp) { + ns_plugin_t *plugin = NULL; + + REQUIRE(pluginp != NULL && *pluginp != NULL); + + plugin = *pluginp; + *pluginp = NULL; + + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_DEBUG(1), "unloading plugin '%s'", + plugin->modpath); + + if (plugin->inst != NULL) { + plugin->destroy_func(&plugin->inst); + } + if (plugin->handle != NULL) { + (void)dlclose(plugin->handle); + } + if (plugin->modpath != NULL) { + isc_mem_free(plugin->mctx, plugin->modpath); + } + + isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin)); +} +#elif _WIN32 +static isc_result_t +load_symbol(HMODULE handle, const char *modpath, const char *symbol_name, + void **symbolp) { + void *symbol = NULL; + + REQUIRE(handle != NULL); + REQUIRE(symbolp != NULL && *symbolp == NULL); + + symbol = GetProcAddress(handle, symbol_name); + if (symbol == NULL) { + int errstatus = GetLastError(); + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, + "failed to look up symbol %s in " + "plugin '%s': %d", + symbol_name, modpath, errstatus); + return (ISC_R_FAILURE); + } + + *symbolp = symbol; + + return (ISC_R_SUCCESS); +} + +static isc_result_t +load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) { + isc_result_t result; + HMODULE handle; + ns_plugin_t *plugin = NULL; + ns_plugin_register_t *register_func = NULL; + ns_plugin_destroy_t *destroy_func = NULL; + ns_plugin_version_t *version_func = NULL; + int version; + + REQUIRE(pluginp != NULL && *pluginp == NULL); + + handle = LoadLibraryA(modpath); + if (handle == NULL) { + CHECK(ISC_R_FAILURE); + } + + CHECK(load_symbol(handle, modpath, "plugin_version", + (void **)&version_func)); + + version = version_func(); + if (version < (NS_PLUGIN_VERSION - NS_PLUGIN_AGE) || + version > NS_PLUGIN_VERSION) + { + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, + "plugin API version mismatch: %d/%d", version, + NS_PLUGIN_VERSION); + CHECK(ISC_R_FAILURE); + } + + CHECK(load_symbol(handle, modpath, "plugin_register", + (void **)®ister_func)); + CHECK(load_symbol(handle, modpath, "plugin_destroy", + (void **)&destroy_func)); + + plugin = isc_mem_get(mctx, sizeof(*plugin)); + memset(plugin, 0, sizeof(*plugin)); + isc_mem_attach(mctx, &plugin->mctx); + plugin->handle = handle; + plugin->modpath = isc_mem_strdup(plugin->mctx, modpath); + plugin->register_func = register_func; + plugin->destroy_func = destroy_func; + + ISC_LINK_INIT(plugin, link); + + *pluginp = plugin; + plugin = NULL; + +cleanup: + if (result != ISC_R_SUCCESS) { + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, + NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, + "failed to dynamically load " + "plugin '%s': %d (%s)", + modpath, GetLastError(), + isc_result_totext(result)); + + if (plugin != NULL) { + isc_mem_putanddetach(&plugin->mctx, plugin, + sizeof(*plugin)); + } + + if (handle != NULL) { + FreeLibrary(handle); + } + } + + return (result); +} + +static void +unload_plugin(ns_plugin_t **pluginp) { + ns_plugin_t *plugin = NULL; + + REQUIRE(pluginp != NULL && *pluginp != NULL); + + plugin = *pluginp; + *pluginp = NULL; + + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_DEBUG(1), "unloading plugin '%s'", + plugin->modpath); + + if (plugin->inst != NULL) { + plugin->destroy_func(&plugin->inst); + } + if (plugin->handle != NULL) { + FreeLibrary(plugin->handle); + } + + if (plugin->modpath != NULL) { + isc_mem_free(plugin->mctx, plugin->modpath); + } + + isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin)); +} +#else /* HAVE_DLFCN_H || _WIN32 */ +static isc_result_t +load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) { + UNUSED(mctx); + UNUSED(modpath); + UNUSED(pluginp); + + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_ERROR, "plugin support is not implemented"); + + return (ISC_R_NOTIMPLEMENTED); +} + +static void +unload_plugin(ns_plugin_t **pluginp) { + UNUSED(pluginp); +} +#endif /* HAVE_DLFCN_H */ + +isc_result_t +ns_plugin_register(const char *modpath, const char *parameters, const void *cfg, + const char *cfg_file, unsigned long cfg_line, + isc_mem_t *mctx, isc_log_t *lctx, void *actx, + dns_view_t *view) { + isc_result_t result; + ns_plugin_t *plugin = NULL; + + REQUIRE(mctx != NULL); + REQUIRE(lctx != NULL); + REQUIRE(view != NULL); + + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_INFO, "loading plugin '%s'", modpath); + + CHECK(load_plugin(mctx, modpath, &plugin)); + + isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, + ISC_LOG_INFO, "registering plugin '%s'", modpath); + + CHECK(plugin->register_func(parameters, cfg, cfg_file, cfg_line, mctx, + lctx, actx, view->hooktable, + &plugin->inst)); + + ISC_LIST_APPEND(*(ns_plugins_t *)view->plugins, plugin, link); + +cleanup: + if (result != ISC_R_SUCCESS && plugin != NULL) { + unload_plugin(&plugin); + } + + return (result); +} + +isc_result_t +ns_plugin_check(const char *modpath, const char *parameters, const void *cfg, + const char *cfg_file, unsigned long cfg_line, isc_mem_t *mctx, + isc_log_t *lctx, void *actx) { + isc_result_t result; + ns_plugin_t *plugin = NULL; + + CHECK(load_plugin(mctx, modpath, &plugin)); + + result = plugin->check_func(parameters, cfg, cfg_file, cfg_line, mctx, + lctx, actx); + +cleanup: + if (plugin != NULL) { + unload_plugin(&plugin); + } + + return (result); +} + +void +ns_hooktable_init(ns_hooktable_t *hooktable) { + int i; + + for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) { + ISC_LIST_INIT((*hooktable)[i]); + } +} + +isc_result_t +ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep) { + ns_hooktable_t *hooktable = NULL; + + REQUIRE(tablep != NULL && *tablep == NULL); + + hooktable = isc_mem_get(mctx, sizeof(*hooktable)); + + ns_hooktable_init(hooktable); + + *tablep = hooktable; + + return (ISC_R_SUCCESS); +} + +void +ns_hooktable_free(isc_mem_t *mctx, void **tablep) { + ns_hooktable_t *table = NULL; + ns_hook_t *hook = NULL, *next = NULL; + int i = 0; + + REQUIRE(tablep != NULL && *tablep != NULL); + + table = *tablep; + *tablep = NULL; + + for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) { + for (hook = ISC_LIST_HEAD((*table)[i]); hook != NULL; + hook = next) + { + next = ISC_LIST_NEXT(hook, link); + ISC_LIST_UNLINK((*table)[i], hook, link); + if (hook->mctx != NULL) { + isc_mem_putanddetach(&hook->mctx, hook, + sizeof(*hook)); + } + } + } + + isc_mem_put(mctx, table, sizeof(*table)); +} + +void +ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx, + ns_hookpoint_t hookpoint, const ns_hook_t *hook) { + ns_hook_t *copy = NULL; + + REQUIRE(hooktable != NULL); + REQUIRE(mctx != NULL); + REQUIRE(hookpoint < NS_HOOKPOINTS_COUNT); + REQUIRE(hook != NULL); + + copy = isc_mem_get(mctx, sizeof(*copy)); + memset(copy, 0, sizeof(*copy)); + + copy->action = hook->action; + copy->action_data = hook->action_data; + isc_mem_attach(mctx, ©->mctx); + + ISC_LINK_INIT(copy, link); + ISC_LIST_APPEND((*hooktable)[hookpoint], copy, link); +} + +void +ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp) { + ns_plugins_t *plugins = NULL; + + REQUIRE(listp != NULL && *listp == NULL); + + plugins = isc_mem_get(mctx, sizeof(*plugins)); + memset(plugins, 0, sizeof(*plugins)); + ISC_LIST_INIT(*plugins); + + *listp = plugins; +} + +void +ns_plugins_free(isc_mem_t *mctx, void **listp) { + ns_plugins_t *list = NULL; + ns_plugin_t *plugin = NULL, *next = NULL; + + REQUIRE(listp != NULL && *listp != NULL); + + list = *listp; + *listp = NULL; + + for (plugin = ISC_LIST_HEAD(*list); plugin != NULL; plugin = next) { + next = ISC_LIST_NEXT(plugin, link); + ISC_LIST_UNLINK(*list, plugin, link); + unload_plugin(&plugin); + } + + isc_mem_put(mctx, list, sizeof(*list)); +} |