1 /* $NetBSD: hooks.c,v 1.12 2026/01/29 18:37:56 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * SPDX-License-Identifier: MPL-2.0 7 * 8 * This Source Code Form is subject to the terms of the Mozilla Public 9 * License, v. 2.0. If a copy of the MPL was not distributed with this 10 * file, you can obtain one at https://mozilla.org/MPL/2.0/. 11 * 12 * See the COPYRIGHT file distributed with this work for additional 13 * information regarding copyright ownership. 14 */ 15 16 /*! \file */ 17 18 #include <errno.h> 19 #include <stdio.h> 20 #include <string.h> 21 22 #include <isc/errno.h> 23 #include <isc/list.h> 24 #include <isc/log.h> 25 #include <isc/mem.h> 26 #include <isc/mutex.h> 27 #include <isc/result.h> 28 #include <isc/types.h> 29 #include <isc/util.h> 30 #include <isc/uv.h> 31 32 #include <dns/view.h> 33 34 #include <ns/hooks.h> 35 #include <ns/log.h> 36 #include <ns/query.h> 37 38 struct ns_plugin { 39 isc_mem_t *mctx; 40 uv_lib_t handle; 41 void *inst; 42 char *modpath; 43 ns_plugin_check_t *check_func; 44 ns_plugin_register_t *register_func; 45 ns_plugin_destroy_t *destroy_func; 46 LINK(ns_plugin_t) link; 47 }; 48 49 static ns_hooklist_t default_hooktable[NS_HOOKPOINTS_COUNT]; 50 ns_hooktable_t *ns__hook_table = &default_hooktable; 51 52 isc_result_t 53 ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) { 54 int result; 55 56 /* 57 * On Unix systems, differentiate between paths and filenames. 58 */ 59 if (strchr(src, '/') != NULL) { 60 /* 61 * 'src' is an absolute or relative path. Copy it verbatim. 62 */ 63 result = snprintf(dst, dstsize, "%s", src); 64 } else { 65 /* 66 * 'src' is a filename. Prepend default plugin directory path. 67 */ 68 result = snprintf(dst, dstsize, "%s/%s", NAMED_PLUGINDIR, src); 69 } 70 71 if (result < 0) { 72 return isc_errno_toresult(errno); 73 } else if ((size_t)result >= dstsize) { 74 return ISC_R_NOSPACE; 75 } else { 76 return ISC_R_SUCCESS; 77 } 78 } 79 80 static isc_result_t 81 load_symbol(uv_lib_t *handle, const char *modpath, const char *symbol_name, 82 void **symbolp) { 83 void *symbol = NULL; 84 int r; 85 86 REQUIRE(handle != NULL); 87 REQUIRE(symbolp != NULL && *symbolp == NULL); 88 89 r = uv_dlsym(handle, symbol_name, &symbol); 90 if (r != 0) { 91 const char *errmsg = uv_dlerror(handle); 92 if (errmsg == NULL) { 93 errmsg = "returned function pointer is NULL"; 94 } 95 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 96 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 97 "failed to look up symbol %s in " 98 "plugin '%s': %s", 99 symbol_name, modpath, errmsg); 100 return ISC_R_FAILURE; 101 } 102 103 *symbolp = symbol; 104 105 return ISC_R_SUCCESS; 106 } 107 108 static void 109 unload_plugin(ns_plugin_t **pluginp); 110 111 static isc_result_t 112 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) { 113 isc_result_t result; 114 ns_plugin_t *plugin = NULL; 115 ns_plugin_version_t *version_func = NULL; 116 int version; 117 int r; 118 119 REQUIRE(pluginp != NULL && *pluginp == NULL); 120 121 plugin = isc_mem_get(mctx, sizeof(*plugin)); 122 *plugin = (ns_plugin_t){ 123 .modpath = isc_mem_strdup(mctx, modpath), 124 }; 125 126 isc_mem_attach(mctx, &plugin->mctx); 127 128 ISC_LINK_INIT(plugin, link); 129 130 r = uv_dlopen(modpath, &plugin->handle); 131 if (r != 0) { 132 const char *errmsg = uv_dlerror(&plugin->handle); 133 if (errmsg == NULL) { 134 errmsg = "unknown error"; 135 } 136 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 137 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 138 "failed to dlopen() plugin '%s': %s", modpath, 139 errmsg); 140 CHECK(ISC_R_FAILURE); 141 } 142 143 CHECK(load_symbol(&plugin->handle, modpath, "plugin_version", 144 (void **)&version_func)); 145 146 version = version_func(); 147 if (version < (NS_PLUGIN_VERSION - NS_PLUGIN_AGE) || 148 version > NS_PLUGIN_VERSION) 149 { 150 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 151 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 152 "plugin API version mismatch: %d/%d", version, 153 NS_PLUGIN_VERSION); 154 CHECK(ISC_R_FAILURE); 155 } 156 157 CHECK(load_symbol(&plugin->handle, modpath, "plugin_check", 158 (void **)&plugin->check_func)); 159 CHECK(load_symbol(&plugin->handle, modpath, "plugin_register", 160 (void **)&plugin->register_func)); 161 CHECK(load_symbol(&plugin->handle, modpath, "plugin_destroy", 162 (void **)&plugin->destroy_func)); 163 164 *pluginp = plugin; 165 166 return ISC_R_SUCCESS; 167 168 cleanup: 169 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 170 ISC_LOG_ERROR, 171 "failed to dynamically load plugin '%s': %s", modpath, 172 isc_result_totext(result)); 173 174 unload_plugin(&plugin); 175 176 return result; 177 } 178 179 static void 180 unload_plugin(ns_plugin_t **pluginp) { 181 ns_plugin_t *plugin = NULL; 182 183 REQUIRE(pluginp != NULL && *pluginp != NULL); 184 185 plugin = *pluginp; 186 *pluginp = NULL; 187 188 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 189 ISC_LOG_DEBUG(1), "unloading plugin '%s'", 190 plugin->modpath); 191 192 if (plugin->inst != NULL) { 193 plugin->destroy_func(&plugin->inst); 194 } 195 196 uv_dlclose(&plugin->handle); 197 isc_mem_free(plugin->mctx, plugin->modpath); 198 isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin)); 199 } 200 201 isc_result_t 202 ns_plugin_register(const char *modpath, const char *parameters, const void *cfg, 203 const char *cfg_file, unsigned long cfg_line, 204 isc_mem_t *mctx, isc_log_t *lctx, void *actx, 205 dns_view_t *view) { 206 isc_result_t result; 207 ns_plugin_t *plugin = NULL; 208 209 REQUIRE(mctx != NULL); 210 REQUIRE(lctx != NULL); 211 REQUIRE(view != NULL); 212 213 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 214 ISC_LOG_INFO, "loading plugin '%s'", modpath); 215 216 CHECK(load_plugin(mctx, modpath, &plugin)); 217 218 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 219 ISC_LOG_INFO, "registering plugin '%s'", modpath); 220 221 CHECK(plugin->check_func(parameters, cfg, cfg_file, cfg_line, mctx, 222 lctx, actx)); 223 224 CHECK(plugin->register_func(parameters, cfg, cfg_file, cfg_line, mctx, 225 lctx, actx, view->hooktable, 226 &plugin->inst)); 227 228 ISC_LIST_APPEND(*(ns_plugins_t *)view->plugins, plugin, link); 229 230 cleanup: 231 if (result != ISC_R_SUCCESS && plugin != NULL) { 232 unload_plugin(&plugin); 233 } 234 235 return result; 236 } 237 238 isc_result_t 239 ns_plugin_check(const char *modpath, const char *parameters, const void *cfg, 240 const char *cfg_file, unsigned long cfg_line, isc_mem_t *mctx, 241 isc_log_t *lctx, void *actx) { 242 isc_result_t result; 243 ns_plugin_t *plugin = NULL; 244 245 CHECK(load_plugin(mctx, modpath, &plugin)); 246 247 result = plugin->check_func(parameters, cfg, cfg_file, cfg_line, mctx, 248 lctx, actx); 249 250 cleanup: 251 if (plugin != NULL) { 252 unload_plugin(&plugin); 253 } 254 255 return result; 256 } 257 258 void 259 ns_hooktable_init(ns_hooktable_t *hooktable) { 260 int i; 261 262 for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) { 263 ISC_LIST_INIT((*hooktable)[i]); 264 } 265 } 266 267 isc_result_t 268 ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep) { 269 ns_hooktable_t *hooktable = NULL; 270 271 REQUIRE(tablep != NULL && *tablep == NULL); 272 273 hooktable = isc_mem_get(mctx, sizeof(*hooktable)); 274 275 ns_hooktable_init(hooktable); 276 277 *tablep = hooktable; 278 279 return ISC_R_SUCCESS; 280 } 281 282 void 283 ns_hooktable_free(isc_mem_t *mctx, void **tablep) { 284 ns_hooktable_t *table = NULL; 285 ns_hook_t *hook = NULL, *next = NULL; 286 int i = 0; 287 288 REQUIRE(tablep != NULL && *tablep != NULL); 289 290 table = *tablep; 291 *tablep = NULL; 292 293 for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) { 294 for (hook = ISC_LIST_HEAD((*table)[i]); hook != NULL; 295 hook = next) 296 { 297 next = ISC_LIST_NEXT(hook, link); 298 ISC_LIST_UNLINK((*table)[i], hook, link); 299 if (hook->mctx != NULL) { 300 isc_mem_putanddetach(&hook->mctx, hook, 301 sizeof(*hook)); 302 } 303 } 304 } 305 306 isc_mem_put(mctx, table, sizeof(*table)); 307 } 308 309 void 310 ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx, 311 ns_hookpoint_t hookpoint, const ns_hook_t *hook) { 312 ns_hook_t *copy = NULL; 313 314 REQUIRE(hooktable != NULL); 315 REQUIRE(mctx != NULL); 316 REQUIRE(hookpoint < NS_HOOKPOINTS_COUNT); 317 REQUIRE(hook != NULL); 318 319 copy = isc_mem_get(mctx, sizeof(*copy)); 320 *copy = (ns_hook_t){ 321 .action = hook->action, 322 .action_data = hook->action_data, 323 }; 324 isc_mem_attach(mctx, ©->mctx); 325 326 ISC_LINK_INIT(copy, link); 327 ISC_LIST_APPEND((*hooktable)[hookpoint], copy, link); 328 } 329 330 void 331 ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp) { 332 ns_plugins_t *plugins = NULL; 333 334 REQUIRE(listp != NULL && *listp == NULL); 335 336 plugins = isc_mem_get(mctx, sizeof(*plugins)); 337 *plugins = (ns_plugins_t){ 0 }; 338 ISC_LIST_INIT(*plugins); 339 340 *listp = plugins; 341 } 342 343 void 344 ns_plugins_free(isc_mem_t *mctx, void **listp) { 345 ns_plugins_t *list = NULL; 346 ns_plugin_t *plugin = NULL, *next = NULL; 347 348 REQUIRE(listp != NULL && *listp != NULL); 349 350 list = *listp; 351 *listp = NULL; 352 353 for (plugin = ISC_LIST_HEAD(*list); plugin != NULL; plugin = next) { 354 next = ISC_LIST_NEXT(plugin, link); 355 ISC_LIST_UNLINK(*list, plugin, link); 356 unload_plugin(&plugin); 357 } 358 359 isc_mem_put(mctx, list, sizeof(*list)); 360 } 361