1*bcda20f6Schristos /* $NetBSD: hooks.c,v 1.11 2025/01/26 16:25:45 christos Exp $ */ 266331fe0Schristos 366331fe0Schristos /* 466331fe0Schristos * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 566331fe0Schristos * 68596601aSchristos * SPDX-License-Identifier: MPL-2.0 78596601aSchristos * 866331fe0Schristos * This Source Code Form is subject to the terms of the Mozilla Public 966331fe0Schristos * License, v. 2.0. If a copy of the MPL was not distributed with this 10fce770bdSchristos * file, you can obtain one at https://mozilla.org/MPL/2.0/. 1166331fe0Schristos * 1266331fe0Schristos * See the COPYRIGHT file distributed with this work for additional 1366331fe0Schristos * information regarding copyright ownership. 1466331fe0Schristos */ 1566331fe0Schristos 1666331fe0Schristos /*! \file */ 1766331fe0Schristos 18684ad4e5Schristos #include <errno.h> 19684ad4e5Schristos #include <stdio.h> 2066331fe0Schristos #include <string.h> 2166331fe0Schristos 22684ad4e5Schristos #include <isc/errno.h> 2366331fe0Schristos #include <isc/list.h> 2466331fe0Schristos #include <isc/log.h> 2566331fe0Schristos #include <isc/mem.h> 2666331fe0Schristos #include <isc/mutex.h> 2766331fe0Schristos #include <isc/result.h> 2866331fe0Schristos #include <isc/types.h> 295606745fSchristos #include <isc/util.h> 30*bcda20f6Schristos #include <isc/uv.h> 3166331fe0Schristos 3266331fe0Schristos #include <dns/view.h> 3366331fe0Schristos 3466331fe0Schristos #include <ns/hooks.h> 3566331fe0Schristos #include <ns/log.h> 3666331fe0Schristos #include <ns/query.h> 3766331fe0Schristos 3866331fe0Schristos #define CHECK(op) \ 3966331fe0Schristos do { \ 4066331fe0Schristos result = (op); \ 4166331fe0Schristos if (result != ISC_R_SUCCESS) { \ 4266331fe0Schristos goto cleanup; \ 4366331fe0Schristos } \ 4453cc4e50Srillig } while (0) 4566331fe0Schristos 4666331fe0Schristos struct ns_plugin { 4766331fe0Schristos isc_mem_t *mctx; 48bb5aa156Schristos uv_lib_t handle; 4966331fe0Schristos void *inst; 5066331fe0Schristos char *modpath; 5166331fe0Schristos ns_plugin_check_t *check_func; 5266331fe0Schristos ns_plugin_register_t *register_func; 5366331fe0Schristos ns_plugin_destroy_t *destroy_func; 5466331fe0Schristos LINK(ns_plugin_t) link; 5566331fe0Schristos }; 5666331fe0Schristos 5766331fe0Schristos static ns_hooklist_t default_hooktable[NS_HOOKPOINTS_COUNT]; 58bb5aa156Schristos ns_hooktable_t *ns__hook_table = &default_hooktable; 5966331fe0Schristos 60684ad4e5Schristos isc_result_t 61684ad4e5Schristos ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) { 62684ad4e5Schristos int result; 63684ad4e5Schristos 64684ad4e5Schristos /* 65684ad4e5Schristos * On Unix systems, differentiate between paths and filenames. 66684ad4e5Schristos */ 67684ad4e5Schristos if (strchr(src, '/') != NULL) { 68684ad4e5Schristos /* 69684ad4e5Schristos * 'src' is an absolute or relative path. Copy it verbatim. 70684ad4e5Schristos */ 71684ad4e5Schristos result = snprintf(dst, dstsize, "%s", src); 72684ad4e5Schristos } else { 73684ad4e5Schristos /* 74684ad4e5Schristos * 'src' is a filename. Prepend default plugin directory path. 75684ad4e5Schristos */ 76684ad4e5Schristos result = snprintf(dst, dstsize, "%s/%s", NAMED_PLUGINDIR, src); 77684ad4e5Schristos } 78684ad4e5Schristos 79684ad4e5Schristos if (result < 0) { 80*bcda20f6Schristos return isc_errno_toresult(errno); 81684ad4e5Schristos } else if ((size_t)result >= dstsize) { 82*bcda20f6Schristos return ISC_R_NOSPACE; 83684ad4e5Schristos } else { 84*bcda20f6Schristos return ISC_R_SUCCESS; 85684ad4e5Schristos } 86684ad4e5Schristos } 87684ad4e5Schristos 8866331fe0Schristos static isc_result_t 89bb5aa156Schristos load_symbol(uv_lib_t *handle, const char *modpath, const char *symbol_name, 905606745fSchristos void **symbolp) { 9166331fe0Schristos void *symbol = NULL; 92bb5aa156Schristos int r; 9366331fe0Schristos 9466331fe0Schristos REQUIRE(handle != NULL); 9566331fe0Schristos REQUIRE(symbolp != NULL && *symbolp == NULL); 9666331fe0Schristos 97bb5aa156Schristos r = uv_dlsym(handle, symbol_name, &symbol); 98bb5aa156Schristos if (r != 0) { 99bb5aa156Schristos const char *errmsg = uv_dlerror(handle); 10066331fe0Schristos if (errmsg == NULL) { 10166331fe0Schristos errmsg = "returned function pointer is NULL"; 10266331fe0Schristos } 10366331fe0Schristos isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 10466331fe0Schristos NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 10566331fe0Schristos "failed to look up symbol %s in " 10666331fe0Schristos "plugin '%s': %s", 10766331fe0Schristos symbol_name, modpath, errmsg); 108*bcda20f6Schristos return ISC_R_FAILURE; 10966331fe0Schristos } 11066331fe0Schristos 11166331fe0Schristos *symbolp = symbol; 11266331fe0Schristos 113*bcda20f6Schristos return ISC_R_SUCCESS; 11466331fe0Schristos } 11566331fe0Schristos 116bb5aa156Schristos static void 117bb5aa156Schristos unload_plugin(ns_plugin_t **pluginp); 118bb5aa156Schristos 11966331fe0Schristos static isc_result_t 12066331fe0Schristos load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) { 12166331fe0Schristos isc_result_t result; 12266331fe0Schristos ns_plugin_t *plugin = NULL; 12366331fe0Schristos ns_plugin_version_t *version_func = NULL; 124bb5aa156Schristos int version; 125bb5aa156Schristos int r; 12666331fe0Schristos 12766331fe0Schristos REQUIRE(pluginp != NULL && *pluginp == NULL); 12866331fe0Schristos 129bb5aa156Schristos plugin = isc_mem_get(mctx, sizeof(*plugin)); 130*bcda20f6Schristos *plugin = (ns_plugin_t){ 131*bcda20f6Schristos .modpath = isc_mem_strdup(mctx, modpath), 132*bcda20f6Schristos }; 13366331fe0Schristos 134*bcda20f6Schristos isc_mem_attach(mctx, &plugin->mctx); 135bb5aa156Schristos 136bb5aa156Schristos ISC_LINK_INIT(plugin, link); 137bb5aa156Schristos 138bb5aa156Schristos r = uv_dlopen(modpath, &plugin->handle); 139bb5aa156Schristos if (r != 0) { 140bb5aa156Schristos const char *errmsg = uv_dlerror(&plugin->handle); 14166331fe0Schristos if (errmsg == NULL) { 14266331fe0Schristos errmsg = "unknown error"; 14366331fe0Schristos } 14466331fe0Schristos isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 14566331fe0Schristos NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 1465606745fSchristos "failed to dlopen() plugin '%s': %s", modpath, 1475606745fSchristos errmsg); 148bb5aa156Schristos CHECK(ISC_R_FAILURE); 14966331fe0Schristos } 15066331fe0Schristos 151bb5aa156Schristos CHECK(load_symbol(&plugin->handle, modpath, "plugin_version", 15266331fe0Schristos (void **)&version_func)); 15366331fe0Schristos 15466331fe0Schristos version = version_func(); 15566331fe0Schristos if (version < (NS_PLUGIN_VERSION - NS_PLUGIN_AGE) || 15666331fe0Schristos version > NS_PLUGIN_VERSION) 15766331fe0Schristos { 15866331fe0Schristos isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 15966331fe0Schristos NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 1605606745fSchristos "plugin API version mismatch: %d/%d", version, 1615606745fSchristos NS_PLUGIN_VERSION); 16266331fe0Schristos CHECK(ISC_R_FAILURE); 16366331fe0Schristos } 16466331fe0Schristos 165bb5aa156Schristos CHECK(load_symbol(&plugin->handle, modpath, "plugin_check", 166bb5aa156Schristos (void **)&plugin->check_func)); 167bb5aa156Schristos CHECK(load_symbol(&plugin->handle, modpath, "plugin_register", 168bb5aa156Schristos (void **)&plugin->register_func)); 169bb5aa156Schristos CHECK(load_symbol(&plugin->handle, modpath, "plugin_destroy", 170bb5aa156Schristos (void **)&plugin->destroy_func)); 17166331fe0Schristos 17266331fe0Schristos *pluginp = plugin; 17366331fe0Schristos 174*bcda20f6Schristos return ISC_R_SUCCESS; 17566331fe0Schristos 17666331fe0Schristos cleanup: 177bb5aa156Schristos isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 178bb5aa156Schristos ISC_LOG_ERROR, 179bb5aa156Schristos "failed to dynamically load plugin '%s': %s", modpath, 1805606745fSchristos isc_result_totext(result)); 18166331fe0Schristos 182bb5aa156Schristos unload_plugin(&plugin); 18366331fe0Schristos 184*bcda20f6Schristos return result; 18566331fe0Schristos } 18666331fe0Schristos 18766331fe0Schristos static void 18866331fe0Schristos unload_plugin(ns_plugin_t **pluginp) { 18966331fe0Schristos ns_plugin_t *plugin = NULL; 19066331fe0Schristos 19166331fe0Schristos REQUIRE(pluginp != NULL && *pluginp != NULL); 19266331fe0Schristos 19366331fe0Schristos plugin = *pluginp; 19466331fe0Schristos *pluginp = NULL; 19566331fe0Schristos 1965606745fSchristos isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 1975606745fSchristos ISC_LOG_DEBUG(1), "unloading plugin '%s'", 1985606745fSchristos plugin->modpath); 19966331fe0Schristos 20066331fe0Schristos if (plugin->inst != NULL) { 20166331fe0Schristos plugin->destroy_func(&plugin->inst); 20266331fe0Schristos } 20366331fe0Schristos 204bb5aa156Schristos uv_dlclose(&plugin->handle); 20566331fe0Schristos isc_mem_free(plugin->mctx, plugin->modpath); 20666331fe0Schristos isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin)); 20766331fe0Schristos } 20866331fe0Schristos 20966331fe0Schristos isc_result_t 2105606745fSchristos ns_plugin_register(const char *modpath, const char *parameters, const void *cfg, 2115606745fSchristos const char *cfg_file, unsigned long cfg_line, 21266331fe0Schristos isc_mem_t *mctx, isc_log_t *lctx, void *actx, 2135606745fSchristos dns_view_t *view) { 21466331fe0Schristos isc_result_t result; 21566331fe0Schristos ns_plugin_t *plugin = NULL; 21666331fe0Schristos 21766331fe0Schristos REQUIRE(mctx != NULL); 21866331fe0Schristos REQUIRE(lctx != NULL); 21966331fe0Schristos REQUIRE(view != NULL); 22066331fe0Schristos 2215606745fSchristos isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 2225606745fSchristos ISC_LOG_INFO, "loading plugin '%s'", modpath); 22366331fe0Schristos 22466331fe0Schristos CHECK(load_plugin(mctx, modpath, &plugin)); 22566331fe0Schristos 2265606745fSchristos isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 2275606745fSchristos ISC_LOG_INFO, "registering plugin '%s'", modpath); 22866331fe0Schristos 2295606745fSchristos CHECK(plugin->register_func(parameters, cfg, cfg_file, cfg_line, mctx, 2305606745fSchristos lctx, actx, view->hooktable, 23166331fe0Schristos &plugin->inst)); 23266331fe0Schristos 23366331fe0Schristos ISC_LIST_APPEND(*(ns_plugins_t *)view->plugins, plugin, link); 23466331fe0Schristos 23566331fe0Schristos cleanup: 23666331fe0Schristos if (result != ISC_R_SUCCESS && plugin != NULL) { 23766331fe0Schristos unload_plugin(&plugin); 23866331fe0Schristos } 23966331fe0Schristos 240*bcda20f6Schristos return result; 24166331fe0Schristos } 24266331fe0Schristos 24366331fe0Schristos isc_result_t 2445606745fSchristos ns_plugin_check(const char *modpath, const char *parameters, const void *cfg, 2455606745fSchristos const char *cfg_file, unsigned long cfg_line, isc_mem_t *mctx, 2465606745fSchristos isc_log_t *lctx, void *actx) { 24766331fe0Schristos isc_result_t result; 24866331fe0Schristos ns_plugin_t *plugin = NULL; 24966331fe0Schristos 25066331fe0Schristos CHECK(load_plugin(mctx, modpath, &plugin)); 25166331fe0Schristos 2525606745fSchristos result = plugin->check_func(parameters, cfg, cfg_file, cfg_line, mctx, 2535606745fSchristos lctx, actx); 25466331fe0Schristos 25566331fe0Schristos cleanup: 25666331fe0Schristos if (plugin != NULL) { 25766331fe0Schristos unload_plugin(&plugin); 25866331fe0Schristos } 25966331fe0Schristos 260*bcda20f6Schristos return result; 26166331fe0Schristos } 26266331fe0Schristos 26366331fe0Schristos void 26466331fe0Schristos ns_hooktable_init(ns_hooktable_t *hooktable) { 26566331fe0Schristos int i; 26666331fe0Schristos 26766331fe0Schristos for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) { 26866331fe0Schristos ISC_LIST_INIT((*hooktable)[i]); 26966331fe0Schristos } 27066331fe0Schristos } 27166331fe0Schristos 27266331fe0Schristos isc_result_t 27366331fe0Schristos ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep) { 27466331fe0Schristos ns_hooktable_t *hooktable = NULL; 27566331fe0Schristos 27666331fe0Schristos REQUIRE(tablep != NULL && *tablep == NULL); 27766331fe0Schristos 27866331fe0Schristos hooktable = isc_mem_get(mctx, sizeof(*hooktable)); 27966331fe0Schristos 28066331fe0Schristos ns_hooktable_init(hooktable); 28166331fe0Schristos 28266331fe0Schristos *tablep = hooktable; 28366331fe0Schristos 284*bcda20f6Schristos return ISC_R_SUCCESS; 28566331fe0Schristos } 28666331fe0Schristos 28766331fe0Schristos void 28866331fe0Schristos ns_hooktable_free(isc_mem_t *mctx, void **tablep) { 28966331fe0Schristos ns_hooktable_t *table = NULL; 29066331fe0Schristos ns_hook_t *hook = NULL, *next = NULL; 29166331fe0Schristos int i = 0; 29266331fe0Schristos 29366331fe0Schristos REQUIRE(tablep != NULL && *tablep != NULL); 29466331fe0Schristos 29566331fe0Schristos table = *tablep; 29666331fe0Schristos *tablep = NULL; 29766331fe0Schristos 29866331fe0Schristos for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) { 2995606745fSchristos for (hook = ISC_LIST_HEAD((*table)[i]); hook != NULL; 300903adeddSchristos hook = next) 301903adeddSchristos { 30266331fe0Schristos next = ISC_LIST_NEXT(hook, link); 30366331fe0Schristos ISC_LIST_UNLINK((*table)[i], hook, link); 30466331fe0Schristos if (hook->mctx != NULL) { 3055606745fSchristos isc_mem_putanddetach(&hook->mctx, hook, 3065606745fSchristos sizeof(*hook)); 30766331fe0Schristos } 30866331fe0Schristos } 30966331fe0Schristos } 31066331fe0Schristos 31166331fe0Schristos isc_mem_put(mctx, table, sizeof(*table)); 31266331fe0Schristos } 31366331fe0Schristos 31466331fe0Schristos void 31566331fe0Schristos ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx, 3165606745fSchristos ns_hookpoint_t hookpoint, const ns_hook_t *hook) { 31766331fe0Schristos ns_hook_t *copy = NULL; 31866331fe0Schristos 31966331fe0Schristos REQUIRE(hooktable != NULL); 32066331fe0Schristos REQUIRE(mctx != NULL); 32166331fe0Schristos REQUIRE(hookpoint < NS_HOOKPOINTS_COUNT); 32266331fe0Schristos REQUIRE(hook != NULL); 32366331fe0Schristos 32466331fe0Schristos copy = isc_mem_get(mctx, sizeof(*copy)); 325*bcda20f6Schristos *copy = (ns_hook_t){ 326*bcda20f6Schristos .action = hook->action, 327*bcda20f6Schristos .action_data = hook->action_data, 328*bcda20f6Schristos }; 32966331fe0Schristos isc_mem_attach(mctx, ©->mctx); 33066331fe0Schristos 33166331fe0Schristos ISC_LINK_INIT(copy, link); 33266331fe0Schristos ISC_LIST_APPEND((*hooktable)[hookpoint], copy, link); 33366331fe0Schristos } 33466331fe0Schristos 33566331fe0Schristos void 33666331fe0Schristos ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp) { 33766331fe0Schristos ns_plugins_t *plugins = NULL; 33866331fe0Schristos 33966331fe0Schristos REQUIRE(listp != NULL && *listp == NULL); 34066331fe0Schristos 34166331fe0Schristos plugins = isc_mem_get(mctx, sizeof(*plugins)); 342*bcda20f6Schristos *plugins = (ns_plugins_t){ 0 }; 34366331fe0Schristos ISC_LIST_INIT(*plugins); 34466331fe0Schristos 34566331fe0Schristos *listp = plugins; 34666331fe0Schristos } 34766331fe0Schristos 34866331fe0Schristos void 34966331fe0Schristos ns_plugins_free(isc_mem_t *mctx, void **listp) { 35066331fe0Schristos ns_plugins_t *list = NULL; 35166331fe0Schristos ns_plugin_t *plugin = NULL, *next = NULL; 35266331fe0Schristos 35366331fe0Schristos REQUIRE(listp != NULL && *listp != NULL); 35466331fe0Schristos 35566331fe0Schristos list = *listp; 35666331fe0Schristos *listp = NULL; 35766331fe0Schristos 3585606745fSchristos for (plugin = ISC_LIST_HEAD(*list); plugin != NULL; plugin = next) { 35966331fe0Schristos next = ISC_LIST_NEXT(plugin, link); 36066331fe0Schristos ISC_LIST_UNLINK(*list, plugin, link); 36166331fe0Schristos unload_plugin(&plugin); 36266331fe0Schristos } 36366331fe0Schristos 36466331fe0Schristos isc_mem_put(mctx, list, sizeof(*list)); 36566331fe0Schristos } 366