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