mirror of
https://gitlab.isc.org/isc-projects/bind9
synced 2025-09-05 17:15:31 +00:00
During server reconfiguration, plugin instances set up for the old views are unloaded very close to the end of the whole process, after new plugin instances are set up. As the log message announcing plugin unloading is emitted at the default "info" level, the user might be misled into thinking that it is the new plugin instances that are being unloaded for some reason, particularly because all other messages logged at the "info" level around the same time inform about setting things up rather than tearing them down. Since no distinction is currently made between destroying a view due to reconfiguration and due to a shutdown in progress, there is no easy way to vary the contents of the log message depending on circumstances. Since this message is not a particularly critical one, demote it to debug level to prevent confusion.
558 lines
13 KiB
C
558 lines
13 KiB
C
/*
|
|
* Copyright (C) Internet Systems Consortium, Inc. ("ISC")
|
|
*
|
|
* 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 http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* See the COPYRIGHT file distributed with this work for additional
|
|
* information regarding copyright ownership.
|
|
*/
|
|
|
|
/*! \file */
|
|
|
|
#include <config.h>
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#if HAVE_DLFCN_H
|
|
#include <dlfcn.h>
|
|
#elif _WIN32
|
|
#include <windows.h>
|
|
#endif
|
|
|
|
#include <isc/errno.h>
|
|
#include <isc/list.h>
|
|
#include <isc/log.h>
|
|
#include <isc/mem.h>
|
|
#include <isc/mutex.h>
|
|
#include <isc/print.h>
|
|
#include <isc/result.h>
|
|
#include <isc/platform.h>
|
|
#include <isc/util.h>
|
|
#include <isc/types.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
|
|
/*
|
|
* On Windows, always copy 'src' do 'dst'.
|
|
*/
|
|
result = snprintf(dst, dstsize, "%s", src);
|
|
#endif
|
|
|
|
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__
|
|
flags |= RTLD_DEEPBIND;
|
|
#endif
|
|
|
|
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));
|
|
}
|
|
|
|
if (handle != NULL) {
|
|
(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));
|
|
}
|