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