/* $NetBSD: dyndb.c,v 1.1.2.2 2024/02/24 13:06:57 martin Exp $ */ /* * 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. */ #if HAVE_DLFCN_H #include #elif _WIN32 #include #endif /* if HAVE_DLFCN_H */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define CHECK(op) \ do { \ result = (op); \ if (result != ISC_R_SUCCESS) \ goto cleanup; \ } while (0) typedef struct dyndb_implementation dyndb_implementation_t; struct dyndb_implementation { isc_mem_t *mctx; void *handle; dns_dyndb_register_t *register_func; dns_dyndb_destroy_t *destroy_func; char *name; void *inst; LINK(dyndb_implementation_t) link; }; /* * List of dyndb implementations. Locked by dyndb_lock. * * These are stored here so they can be cleaned up on shutdown. * (The order in which they are stored is not important.) */ static LIST(dyndb_implementation_t) dyndb_implementations; /* Locks dyndb_implementations. */ static isc_mutex_t dyndb_lock; static isc_once_t once = ISC_ONCE_INIT; static void dyndb_initialize(void) { isc_mutex_init(&dyndb_lock); INIT_LIST(dyndb_implementations); } static dyndb_implementation_t * impfind(const char *name) { dyndb_implementation_t *imp; for (imp = ISC_LIST_HEAD(dyndb_implementations); imp != NULL; imp = ISC_LIST_NEXT(imp, link)) { if (strcasecmp(name, imp->name) == 0) { return (imp); } } return (NULL); } #if HAVE_DLFCN_H && HAVE_DLOPEN static isc_result_t load_symbol(void *handle, const char *filename, const char *symbol_name, void **symbolp) { const char *errmsg; void *symbol; REQUIRE(handle != NULL); REQUIRE(symbolp != NULL && *symbolp == NULL); symbol = dlsym(handle, symbol_name); if (symbol == NULL) { errmsg = dlerror(); if (errmsg == NULL) { errmsg = "returned function pointer is NULL"; } isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, "failed to lookup symbol %s in " "dyndb module '%s': %s", symbol_name, filename, errmsg); return (ISC_R_FAILURE); } dlerror(); *symbolp = symbol; return (ISC_R_SUCCESS); } static isc_result_t load_library(isc_mem_t *mctx, const char *filename, const char *instname, dyndb_implementation_t **impp) { isc_result_t result; void *handle = NULL; dyndb_implementation_t *imp = NULL; dns_dyndb_register_t *register_func = NULL; dns_dyndb_destroy_t *destroy_func = NULL; dns_dyndb_version_t *version_func = NULL; int version; REQUIRE(impp != NULL && *impp == NULL); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, ISC_LOG_INFO, "loading DynDB instance '%s' driver '%s'", instname, filename); handle = dlopen(filename, RTLD_NOW | RTLD_LOCAL); if (handle == NULL) { CHECK(ISC_R_FAILURE); } /* Clear dlerror */ dlerror(); CHECK(load_symbol(handle, filename, "dyndb_version", (void **)&version_func)); version = version_func(NULL); if (version < (DNS_DYNDB_VERSION - DNS_DYNDB_AGE) || version > DNS_DYNDB_VERSION) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, "driver API version mismatch: %d/%d", version, DNS_DYNDB_VERSION); CHECK(ISC_R_FAILURE); } CHECK(load_symbol(handle, filename, "dyndb_init", (void **)®ister_func)); CHECK(load_symbol(handle, filename, "dyndb_destroy", (void **)&destroy_func)); imp = isc_mem_get(mctx, sizeof(dyndb_implementation_t)); imp->mctx = NULL; isc_mem_attach(mctx, &imp->mctx); imp->handle = handle; imp->register_func = register_func; imp->destroy_func = destroy_func; imp->name = isc_mem_strdup(mctx, instname); imp->inst = NULL; INIT_LINK(imp, link); *impp = imp; imp = NULL; cleanup: if (result != ISC_R_SUCCESS) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, "failed to dynamically load instance '%s' " "driver '%s': %s (%s)", instname, filename, dlerror(), isc_result_totext(result)); } if (imp != NULL) { isc_mem_putanddetach(&imp->mctx, imp, sizeof(dyndb_implementation_t)); } if (result != ISC_R_SUCCESS && handle != NULL) { dlclose(handle); } return (result); } static void unload_library(dyndb_implementation_t **impp) { dyndb_implementation_t *imp; REQUIRE(impp != NULL && *impp != NULL); imp = *impp; *impp = NULL; isc_mem_free(imp->mctx, imp->name); isc_mem_putanddetach(&imp->mctx, imp, sizeof(dyndb_implementation_t)); } #elif _WIN32 static isc_result_t load_symbol(HMODULE handle, const char *filename, const char *symbol_name, void **symbolp) { void *symbol; REQUIRE(handle != NULL); REQUIRE(symbolp != NULL && *symbolp == NULL); symbol = GetProcAddress(handle, symbol_name); if (symbol == NULL) { int errstatus = GetLastError(); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, "failed to lookup symbol %s in " "dyndb module '%s': %d", symbol_name, filename, errstatus); return (ISC_R_FAILURE); } *symbolp = symbol; return (ISC_R_SUCCESS); } static isc_result_t load_library(isc_mem_t *mctx, const char *filename, const char *instname, dyndb_implementation_t **impp) { isc_result_t result; HMODULE handle; dyndb_implementation_t *imp = NULL; dns_dyndb_register_t *register_func = NULL; dns_dyndb_destroy_t *destroy_func = NULL; dns_dyndb_version_t *version_func = NULL; int version; REQUIRE(impp != NULL && *impp == NULL); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, ISC_LOG_INFO, "loading DynDB instance '%s' driver '%s'", instname, filename); handle = LoadLibraryA(filename); if (handle == NULL) { CHECK(ISC_R_FAILURE); } CHECK(load_symbol(handle, filename, "dyndb_version", (void **)&version_func)); version = version_func(NULL); if (version < (DNS_DYNDB_VERSION - DNS_DYNDB_AGE) || version > DNS_DYNDB_VERSION) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, "driver API version mismatch: %d/%d", version, DNS_DYNDB_VERSION); CHECK(ISC_R_FAILURE); } CHECK(load_symbol(handle, filename, "dyndb_init", (void **)®ister_func)); CHECK(load_symbol(handle, filename, "dyndb_destroy", (void **)&destroy_func)); imp = isc_mem_get(mctx, sizeof(dyndb_implementation_t)); imp->mctx = NULL; isc_mem_attach(mctx, &imp->mctx); imp->handle = handle; imp->register_func = register_func; imp->destroy_func = destroy_func; imp->name = isc_mem_strdup(mctx, instname); imp->inst = NULL; INIT_LINK(imp, link); *impp = imp; imp = NULL; cleanup: if (result != ISC_R_SUCCESS) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, "failed to dynamically load instance '%s' " "driver '%s': %d (%s)", instname, filename, GetLastError(), isc_result_totext(result)); } if (imp != NULL) { isc_mem_putanddetach(&imp->mctx, imp, sizeof(dyndb_implementation_t)); } if (result != ISC_R_SUCCESS && handle != NULL) { FreeLibrary(handle); } return (result); } static void unload_library(dyndb_implementation_t **impp) { dyndb_implementation_t *imp; REQUIRE(impp != NULL && *impp != NULL); imp = *impp; *impp = NULL; isc_mem_free(imp->mctx, imp->name); isc_mem_putanddetach(&imp->mctx, imp, sizeof(dyndb_implementation_t)); } #else /* HAVE_DLFCN_H || _WIN32 */ static isc_result_t load_library(isc_mem_t *mctx, const char *filename, const char *instname, dyndb_implementation_t **impp) { UNUSED(mctx); UNUSED(filename); UNUSED(instname); UNUSED(impp); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR, "dynamic database support is not implemented"); return (ISC_R_NOTIMPLEMENTED); } static void unload_library(dyndb_implementation_t **impp) { UNUSED(impp); } #endif /* HAVE_DLFCN_H */ isc_result_t dns_dyndb_load(const char *libname, const char *name, const char *parameters, const char *file, unsigned long line, isc_mem_t *mctx, const dns_dyndbctx_t *dctx) { isc_result_t result; dyndb_implementation_t *implementation = NULL; REQUIRE(DNS_DYNDBCTX_VALID(dctx)); REQUIRE(name != NULL); RUNTIME_CHECK(isc_once_do(&once, dyndb_initialize) == ISC_R_SUCCESS); LOCK(&dyndb_lock); /* duplicate instance names are not allowed */ if (impfind(name) != NULL) { CHECK(ISC_R_EXISTS); } CHECK(load_library(mctx, libname, name, &implementation)); CHECK(implementation->register_func(mctx, name, parameters, file, line, dctx, &implementation->inst)); APPEND(dyndb_implementations, implementation, link); result = ISC_R_SUCCESS; cleanup: if (result != ISC_R_SUCCESS) { if (implementation != NULL) { unload_library(&implementation); } } UNLOCK(&dyndb_lock); return (result); } void dns_dyndb_cleanup(bool exiting) { dyndb_implementation_t *elem; dyndb_implementation_t *prev; RUNTIME_CHECK(isc_once_do(&once, dyndb_initialize) == ISC_R_SUCCESS); LOCK(&dyndb_lock); elem = TAIL(dyndb_implementations); while (elem != NULL) { prev = PREV(elem, link); UNLINK(dyndb_implementations, elem, link); isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB, ISC_LOG_INFO, "unloading DynDB instance '%s'", elem->name); elem->destroy_func(&elem->inst); ENSURE(elem->inst == NULL); unload_library(&elem); elem = prev; } UNLOCK(&dyndb_lock); if (exiting) { isc_mutex_destroy(&dyndb_lock); } } isc_result_t dns_dyndb_createctx(isc_mem_t *mctx, const void *hashinit, isc_log_t *lctx, dns_view_t *view, dns_zonemgr_t *zmgr, isc_task_t *task, isc_timermgr_t *tmgr, dns_dyndbctx_t **dctxp) { dns_dyndbctx_t *dctx; REQUIRE(dctxp != NULL && *dctxp == NULL); dctx = isc_mem_get(mctx, sizeof(*dctx)); memset(dctx, 0, sizeof(*dctx)); if (view != NULL) { dns_view_attach(view, &dctx->view); } if (zmgr != NULL) { dns_zonemgr_attach(zmgr, &dctx->zmgr); } if (task != NULL) { isc_task_attach(task, &dctx->task); } dctx->timermgr = tmgr; dctx->hashinit = hashinit; dctx->lctx = lctx; dctx->memdebug = &isc_mem_debugging; isc_mem_attach(mctx, &dctx->mctx); dctx->magic = DNS_DYNDBCTX_MAGIC; *dctxp = dctx; return (ISC_R_SUCCESS); } void dns_dyndb_destroyctx(dns_dyndbctx_t **dctxp) { dns_dyndbctx_t *dctx; REQUIRE(dctxp != NULL && DNS_DYNDBCTX_VALID(*dctxp)); dctx = *dctxp; *dctxp = NULL; dctx->magic = 0; if (dctx->view != NULL) { dns_view_detach(&dctx->view); } if (dctx->zmgr != NULL) { dns_zonemgr_detach(&dctx->zmgr); } if (dctx->task != NULL) { isc_task_detach(&dctx->task); } dctx->timermgr = NULL; dctx->lctx = NULL; isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(*dctx)); }