1 /* $NetBSD: hooks.c,v 1.9 2023/01/25 21:43:32 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 #if HAVE_DLFCN_H 23 #include <dlfcn.h> 24 #elif _WIN32 25 #include <windows.h> 26 #endif /* if HAVE_DLFCN_H */ 27 28 #include <isc/errno.h> 29 #include <isc/list.h> 30 #include <isc/log.h> 31 #include <isc/mem.h> 32 #include <isc/mutex.h> 33 #include <isc/platform.h> 34 #include <isc/print.h> 35 #include <isc/result.h> 36 #include <isc/types.h> 37 #include <isc/util.h> 38 39 #include <dns/view.h> 40 41 #include <ns/hooks.h> 42 #include <ns/log.h> 43 #include <ns/query.h> 44 45 #define CHECK(op) \ 46 do { \ 47 result = (op); \ 48 if (result != ISC_R_SUCCESS) { \ 49 goto cleanup; \ 50 } \ 51 } while (0) 52 53 struct ns_plugin { 54 isc_mem_t *mctx; 55 void *handle; 56 void *inst; 57 char *modpath; 58 ns_plugin_check_t *check_func; 59 ns_plugin_register_t *register_func; 60 ns_plugin_destroy_t *destroy_func; 61 LINK(ns_plugin_t) link; 62 }; 63 64 static ns_hooklist_t default_hooktable[NS_HOOKPOINTS_COUNT]; 65 LIBNS_EXTERNAL_DATA ns_hooktable_t *ns__hook_table = &default_hooktable; 66 67 isc_result_t 68 ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) { 69 int result; 70 71 #ifndef WIN32 72 /* 73 * On Unix systems, differentiate between paths and filenames. 74 */ 75 if (strchr(src, '/') != NULL) { 76 /* 77 * 'src' is an absolute or relative path. Copy it verbatim. 78 */ 79 result = snprintf(dst, dstsize, "%s", src); 80 } else { 81 /* 82 * 'src' is a filename. Prepend default plugin directory path. 83 */ 84 result = snprintf(dst, dstsize, "%s/%s", NAMED_PLUGINDIR, src); 85 } 86 #else /* ifndef WIN32 */ 87 /* 88 * On Windows, always copy 'src' do 'dst'. 89 */ 90 result = snprintf(dst, dstsize, "%s", src); 91 #endif /* ifndef WIN32 */ 92 93 if (result < 0) { 94 return (isc_errno_toresult(errno)); 95 } else if ((size_t)result >= dstsize) { 96 return (ISC_R_NOSPACE); 97 } else { 98 return (ISC_R_SUCCESS); 99 } 100 } 101 102 #if HAVE_DLFCN_H && HAVE_DLOPEN 103 static isc_result_t 104 load_symbol(void *handle, const char *modpath, const char *symbol_name, 105 void **symbolp) { 106 void *symbol = NULL; 107 108 REQUIRE(handle != NULL); 109 REQUIRE(symbolp != NULL && *symbolp == NULL); 110 111 /* 112 * Clear any pre-existing error conditions before running dlsym(). 113 * (In this case, we expect dlsym() to return non-NULL values 114 * and will always return an error if it returns NULL, but 115 * this ensures that we'll report the correct error condition 116 * if there is one.) 117 */ 118 dlerror(); 119 symbol = dlsym(handle, symbol_name); 120 if (symbol == NULL) { 121 const char *errmsg = dlerror(); 122 if (errmsg == NULL) { 123 errmsg = "returned function pointer is NULL"; 124 } 125 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 126 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 127 "failed to look up symbol %s in " 128 "plugin '%s': %s", 129 symbol_name, modpath, errmsg); 130 return (ISC_R_FAILURE); 131 } 132 133 *symbolp = symbol; 134 135 return (ISC_R_SUCCESS); 136 } 137 138 static isc_result_t 139 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) { 140 isc_result_t result; 141 void *handle = NULL; 142 ns_plugin_t *plugin = NULL; 143 ns_plugin_check_t *check_func = NULL; 144 ns_plugin_register_t *register_func = NULL; 145 ns_plugin_destroy_t *destroy_func = NULL; 146 ns_plugin_version_t *version_func = NULL; 147 int version, flags; 148 149 REQUIRE(pluginp != NULL && *pluginp == NULL); 150 151 flags = RTLD_LAZY | RTLD_LOCAL; 152 #if defined(RTLD_DEEPBIND) && !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__ 153 flags |= RTLD_DEEPBIND; 154 #endif /* if defined(RTLD_DEEPBIND) && !__SANITIZE_ADDRESS__ && \ 155 !__SANITIZE_THREAD__ */ 156 157 handle = dlopen(modpath, flags); 158 if (handle == NULL) { 159 const char *errmsg = dlerror(); 160 if (errmsg == NULL) { 161 errmsg = "unknown error"; 162 } 163 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 164 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 165 "failed to dlopen() plugin '%s': %s", modpath, 166 errmsg); 167 return (ISC_R_FAILURE); 168 } 169 170 CHECK(load_symbol(handle, modpath, "plugin_version", 171 (void **)&version_func)); 172 173 version = version_func(); 174 if (version < (NS_PLUGIN_VERSION - NS_PLUGIN_AGE) || 175 version > NS_PLUGIN_VERSION) 176 { 177 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 178 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 179 "plugin API version mismatch: %d/%d", version, 180 NS_PLUGIN_VERSION); 181 CHECK(ISC_R_FAILURE); 182 } 183 184 CHECK(load_symbol(handle, modpath, "plugin_check", 185 (void **)&check_func)); 186 CHECK(load_symbol(handle, modpath, "plugin_register", 187 (void **)®ister_func)); 188 CHECK(load_symbol(handle, modpath, "plugin_destroy", 189 (void **)&destroy_func)); 190 191 plugin = isc_mem_get(mctx, sizeof(*plugin)); 192 memset(plugin, 0, sizeof(*plugin)); 193 isc_mem_attach(mctx, &plugin->mctx); 194 plugin->handle = handle; 195 plugin->modpath = isc_mem_strdup(plugin->mctx, modpath); 196 plugin->check_func = check_func; 197 plugin->register_func = register_func; 198 plugin->destroy_func = destroy_func; 199 200 ISC_LINK_INIT(plugin, link); 201 202 *pluginp = plugin; 203 plugin = NULL; 204 205 cleanup: 206 if (result != ISC_R_SUCCESS) { 207 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 208 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 209 "failed to dynamically load " 210 "plugin '%s': %s", 211 modpath, isc_result_totext(result)); 212 213 if (plugin != NULL) { 214 isc_mem_putanddetach(&plugin->mctx, plugin, 215 sizeof(*plugin)); 216 } 217 218 (void)dlclose(handle); 219 } 220 221 return (result); 222 } 223 224 static void 225 unload_plugin(ns_plugin_t **pluginp) { 226 ns_plugin_t *plugin = NULL; 227 228 REQUIRE(pluginp != NULL && *pluginp != NULL); 229 230 plugin = *pluginp; 231 *pluginp = NULL; 232 233 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 234 ISC_LOG_DEBUG(1), "unloading plugin '%s'", 235 plugin->modpath); 236 237 if (plugin->inst != NULL) { 238 plugin->destroy_func(&plugin->inst); 239 } 240 if (plugin->handle != NULL) { 241 (void)dlclose(plugin->handle); 242 } 243 if (plugin->modpath != NULL) { 244 isc_mem_free(plugin->mctx, plugin->modpath); 245 } 246 247 isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin)); 248 } 249 #elif _WIN32 250 static isc_result_t 251 load_symbol(HMODULE handle, const char *modpath, const char *symbol_name, 252 void **symbolp) { 253 void *symbol = NULL; 254 255 REQUIRE(handle != NULL); 256 REQUIRE(symbolp != NULL && *symbolp == NULL); 257 258 symbol = GetProcAddress(handle, symbol_name); 259 if (symbol == NULL) { 260 int errstatus = GetLastError(); 261 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 262 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 263 "failed to look up symbol %s in " 264 "plugin '%s': %d", 265 symbol_name, modpath, errstatus); 266 return (ISC_R_FAILURE); 267 } 268 269 *symbolp = symbol; 270 271 return (ISC_R_SUCCESS); 272 } 273 274 static isc_result_t 275 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) { 276 isc_result_t result; 277 HMODULE handle; 278 ns_plugin_t *plugin = NULL; 279 ns_plugin_register_t *register_func = NULL; 280 ns_plugin_destroy_t *destroy_func = NULL; 281 ns_plugin_version_t *version_func = NULL; 282 int version; 283 284 REQUIRE(pluginp != NULL && *pluginp == NULL); 285 286 handle = LoadLibraryA(modpath); 287 if (handle == NULL) { 288 CHECK(ISC_R_FAILURE); 289 } 290 291 CHECK(load_symbol(handle, modpath, "plugin_version", 292 (void **)&version_func)); 293 294 version = version_func(); 295 if (version < (NS_PLUGIN_VERSION - NS_PLUGIN_AGE) || 296 version > NS_PLUGIN_VERSION) 297 { 298 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 299 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 300 "plugin API version mismatch: %d/%d", version, 301 NS_PLUGIN_VERSION); 302 CHECK(ISC_R_FAILURE); 303 } 304 305 CHECK(load_symbol(handle, modpath, "plugin_register", 306 (void **)®ister_func)); 307 CHECK(load_symbol(handle, modpath, "plugin_destroy", 308 (void **)&destroy_func)); 309 310 plugin = isc_mem_get(mctx, sizeof(*plugin)); 311 memset(plugin, 0, sizeof(*plugin)); 312 isc_mem_attach(mctx, &plugin->mctx); 313 plugin->handle = handle; 314 plugin->modpath = isc_mem_strdup(plugin->mctx, modpath); 315 plugin->register_func = register_func; 316 plugin->destroy_func = destroy_func; 317 318 ISC_LINK_INIT(plugin, link); 319 320 *pluginp = plugin; 321 plugin = NULL; 322 323 cleanup: 324 if (result != ISC_R_SUCCESS) { 325 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 326 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 327 "failed to dynamically load " 328 "plugin '%s': %d (%s)", 329 modpath, GetLastError(), 330 isc_result_totext(result)); 331 332 if (plugin != NULL) { 333 isc_mem_putanddetach(&plugin->mctx, plugin, 334 sizeof(*plugin)); 335 } 336 337 if (handle != NULL) { 338 FreeLibrary(handle); 339 } 340 } 341 342 return (result); 343 } 344 345 static void 346 unload_plugin(ns_plugin_t **pluginp) { 347 ns_plugin_t *plugin = NULL; 348 349 REQUIRE(pluginp != NULL && *pluginp != NULL); 350 351 plugin = *pluginp; 352 *pluginp = NULL; 353 354 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 355 ISC_LOG_DEBUG(1), "unloading plugin '%s'", 356 plugin->modpath); 357 358 if (plugin->inst != NULL) { 359 plugin->destroy_func(&plugin->inst); 360 } 361 if (plugin->handle != NULL) { 362 FreeLibrary(plugin->handle); 363 } 364 365 if (plugin->modpath != NULL) { 366 isc_mem_free(plugin->mctx, plugin->modpath); 367 } 368 369 isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin)); 370 } 371 #else /* HAVE_DLFCN_H || _WIN32 */ 372 static isc_result_t 373 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) { 374 UNUSED(mctx); 375 UNUSED(modpath); 376 UNUSED(pluginp); 377 378 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 379 ISC_LOG_ERROR, "plugin support is not implemented"); 380 381 return (ISC_R_NOTIMPLEMENTED); 382 } 383 384 static void 385 unload_plugin(ns_plugin_t **pluginp) { 386 UNUSED(pluginp); 387 } 388 #endif /* HAVE_DLFCN_H */ 389 390 isc_result_t 391 ns_plugin_register(const char *modpath, const char *parameters, const void *cfg, 392 const char *cfg_file, unsigned long cfg_line, 393 isc_mem_t *mctx, isc_log_t *lctx, void *actx, 394 dns_view_t *view) { 395 isc_result_t result; 396 ns_plugin_t *plugin = NULL; 397 398 REQUIRE(mctx != NULL); 399 REQUIRE(lctx != NULL); 400 REQUIRE(view != NULL); 401 402 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 403 ISC_LOG_INFO, "loading plugin '%s'", modpath); 404 405 CHECK(load_plugin(mctx, modpath, &plugin)); 406 407 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS, 408 ISC_LOG_INFO, "registering plugin '%s'", modpath); 409 410 CHECK(plugin->register_func(parameters, cfg, cfg_file, cfg_line, mctx, 411 lctx, actx, view->hooktable, 412 &plugin->inst)); 413 414 ISC_LIST_APPEND(*(ns_plugins_t *)view->plugins, plugin, link); 415 416 cleanup: 417 if (result != ISC_R_SUCCESS && plugin != NULL) { 418 unload_plugin(&plugin); 419 } 420 421 return (result); 422 } 423 424 isc_result_t 425 ns_plugin_check(const char *modpath, const char *parameters, const void *cfg, 426 const char *cfg_file, unsigned long cfg_line, isc_mem_t *mctx, 427 isc_log_t *lctx, void *actx) { 428 isc_result_t result; 429 ns_plugin_t *plugin = NULL; 430 431 CHECK(load_plugin(mctx, modpath, &plugin)); 432 433 result = plugin->check_func(parameters, cfg, cfg_file, cfg_line, mctx, 434 lctx, actx); 435 436 cleanup: 437 if (plugin != NULL) { 438 unload_plugin(&plugin); 439 } 440 441 return (result); 442 } 443 444 void 445 ns_hooktable_init(ns_hooktable_t *hooktable) { 446 int i; 447 448 for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) { 449 ISC_LIST_INIT((*hooktable)[i]); 450 } 451 } 452 453 isc_result_t 454 ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep) { 455 ns_hooktable_t *hooktable = NULL; 456 457 REQUIRE(tablep != NULL && *tablep == NULL); 458 459 hooktable = isc_mem_get(mctx, sizeof(*hooktable)); 460 461 ns_hooktable_init(hooktable); 462 463 *tablep = hooktable; 464 465 return (ISC_R_SUCCESS); 466 } 467 468 void 469 ns_hooktable_free(isc_mem_t *mctx, void **tablep) { 470 ns_hooktable_t *table = NULL; 471 ns_hook_t *hook = NULL, *next = NULL; 472 int i = 0; 473 474 REQUIRE(tablep != NULL && *tablep != NULL); 475 476 table = *tablep; 477 *tablep = NULL; 478 479 for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) { 480 for (hook = ISC_LIST_HEAD((*table)[i]); hook != NULL; 481 hook = next) 482 { 483 next = ISC_LIST_NEXT(hook, link); 484 ISC_LIST_UNLINK((*table)[i], hook, link); 485 if (hook->mctx != NULL) { 486 isc_mem_putanddetach(&hook->mctx, hook, 487 sizeof(*hook)); 488 } 489 } 490 } 491 492 isc_mem_put(mctx, table, sizeof(*table)); 493 } 494 495 void 496 ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx, 497 ns_hookpoint_t hookpoint, const ns_hook_t *hook) { 498 ns_hook_t *copy = NULL; 499 500 REQUIRE(hooktable != NULL); 501 REQUIRE(mctx != NULL); 502 REQUIRE(hookpoint < NS_HOOKPOINTS_COUNT); 503 REQUIRE(hook != NULL); 504 505 copy = isc_mem_get(mctx, sizeof(*copy)); 506 memset(copy, 0, sizeof(*copy)); 507 508 copy->action = hook->action; 509 copy->action_data = hook->action_data; 510 isc_mem_attach(mctx, ©->mctx); 511 512 ISC_LINK_INIT(copy, link); 513 ISC_LIST_APPEND((*hooktable)[hookpoint], copy, link); 514 } 515 516 void 517 ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp) { 518 ns_plugins_t *plugins = NULL; 519 520 REQUIRE(listp != NULL && *listp == NULL); 521 522 plugins = isc_mem_get(mctx, sizeof(*plugins)); 523 memset(plugins, 0, sizeof(*plugins)); 524 ISC_LIST_INIT(*plugins); 525 526 *listp = plugins; 527 } 528 529 void 530 ns_plugins_free(isc_mem_t *mctx, void **listp) { 531 ns_plugins_t *list = NULL; 532 ns_plugin_t *plugin = NULL, *next = NULL; 533 534 REQUIRE(listp != NULL && *listp != NULL); 535 536 list = *listp; 537 *listp = NULL; 538 539 for (plugin = ISC_LIST_HEAD(*list); plugin != NULL; plugin = next) { 540 next = ISC_LIST_NEXT(plugin, link); 541 ISC_LIST_UNLINK(*list, plugin, link); 542 unload_plugin(&plugin); 543 } 544 545 isc_mem_put(mctx, list, sizeof(*list)); 546 } 547