1 /* $NetBSD: hooks.c,v 1.4 2019/04/28 00:01:15 christos 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 http://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 <config.h> 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 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/print.h> 34 #include <isc/result.h> 35 #include <isc/platform.h> 36 #include <isc/util.h> 37 #include <isc/types.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 87 /* 88 * On Windows, always copy 'src' do 'dst'. 89 */ 90 result = snprintf(dst, dstsize, "%s", src); 91 #endif 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, 105 const char *symbol_name, void **symbolp) 106 { 107 void *symbol = NULL; 108 109 REQUIRE(handle != NULL); 110 REQUIRE(symbolp != NULL && *symbolp == NULL); 111 112 /* 113 * Clear any pre-existing error conditions before running dlsym(). 114 * (In this case, we expect dlsym() to return non-NULL values 115 * and will always return an error if it returns NULL, but 116 * this ensures that we'll report the correct error condition 117 * if there is one.) 118 */ 119 dlerror(); 120 symbol = dlsym(handle, symbol_name); 121 if (symbol == NULL) { 122 const char *errmsg = dlerror(); 123 if (errmsg == NULL) { 124 errmsg = "returned function pointer is NULL"; 125 } 126 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 127 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 128 "failed to look up symbol %s in " 129 "plugin '%s': %s", 130 symbol_name, modpath, errmsg); 131 return (ISC_R_FAILURE); 132 } 133 134 *symbolp = symbol; 135 136 return (ISC_R_SUCCESS); 137 } 138 139 static isc_result_t 140 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) { 141 isc_result_t result; 142 void *handle = NULL; 143 ns_plugin_t *plugin = NULL; 144 ns_plugin_check_t *check_func = NULL; 145 ns_plugin_register_t *register_func = NULL; 146 ns_plugin_destroy_t *destroy_func = NULL; 147 ns_plugin_version_t *version_func = NULL; 148 int version, flags; 149 150 REQUIRE(pluginp != NULL && *pluginp == NULL); 151 152 flags = RTLD_LAZY | RTLD_LOCAL; 153 #if defined(RTLD_DEEPBIND) && !__SANITIZE_ADDRESS__ 154 flags |= RTLD_DEEPBIND; 155 #endif 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", 166 modpath, 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", 180 version, 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", modpath, 211 isc_result_totext(result)); 212 213 if (plugin != NULL) { 214 isc_mem_putanddetach(&plugin->mctx, plugin, 215 sizeof(*plugin)); 216 } 217 218 if (handle != NULL) { 219 (void) dlclose(handle); 220 } 221 } 222 223 return (result); 224 } 225 226 static void 227 unload_plugin(ns_plugin_t **pluginp) { 228 ns_plugin_t *plugin = NULL; 229 230 REQUIRE(pluginp != NULL && *pluginp != NULL); 231 232 plugin = *pluginp; 233 *pluginp = NULL; 234 235 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 236 NS_LOGMODULE_HOOKS, ISC_LOG_DEBUG(1), 237 "unloading plugin '%s'", plugin->modpath); 238 239 if (plugin->inst != NULL) { 240 plugin->destroy_func(&plugin->inst); 241 } 242 if (plugin->handle != NULL) { 243 (void) dlclose(plugin->handle); 244 } 245 if (plugin->modpath != NULL) { 246 isc_mem_free(plugin->mctx, plugin->modpath); 247 } 248 249 isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin)); 250 } 251 #elif _WIN32 252 static isc_result_t 253 load_symbol(HMODULE handle, const char *modpath, 254 const char *symbol_name, void **symbolp) 255 { 256 void *symbol = NULL; 257 258 REQUIRE(handle != NULL); 259 REQUIRE(symbolp != NULL && *symbolp == NULL); 260 261 symbol = GetProcAddress(handle, symbol_name); 262 if (symbol == NULL) { 263 int errstatus = GetLastError(); 264 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 265 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 266 "failed to look up symbol %s in " 267 "plugin '%s': %d", 268 symbol_name, modpath, errstatus); 269 return (ISC_R_FAILURE); 270 } 271 272 *symbolp = symbol; 273 274 return (ISC_R_SUCCESS); 275 } 276 277 static isc_result_t 278 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) { 279 isc_result_t result; 280 HMODULE handle; 281 ns_plugin_t *plugin = NULL; 282 ns_plugin_register_t *register_func = NULL; 283 ns_plugin_destroy_t *destroy_func = NULL; 284 ns_plugin_version_t *version_func = NULL; 285 int version; 286 287 REQUIRE(pluginp != NULL && *pluginp == NULL); 288 289 handle = LoadLibraryA(modpath); 290 if (handle == NULL) { 291 CHECK(ISC_R_FAILURE); 292 } 293 294 CHECK(load_symbol(handle, modpath, "plugin_version", 295 (void **)&version_func)); 296 297 version = version_func(); 298 if (version < (NS_PLUGIN_VERSION - NS_PLUGIN_AGE) || 299 version > NS_PLUGIN_VERSION) 300 { 301 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 302 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 303 "plugin API version mismatch: %d/%d", 304 version, NS_PLUGIN_VERSION); 305 CHECK(ISC_R_FAILURE); 306 } 307 308 CHECK(load_symbol(handle, modpath, "plugin_register", 309 (void **)®ister_func)); 310 CHECK(load_symbol(handle, modpath, "plugin_destroy", 311 (void **)&destroy_func)); 312 313 plugin = isc_mem_get(mctx, sizeof(*plugin)); 314 memset(plugin, 0, sizeof(*plugin)); 315 isc_mem_attach(mctx, &plugin->mctx); 316 plugin->handle = handle; 317 plugin->modpath = isc_mem_strdup(plugin->mctx, modpath); 318 plugin->register_func = register_func; 319 plugin->destroy_func = destroy_func; 320 321 ISC_LINK_INIT(plugin, link); 322 323 *pluginp = plugin; 324 plugin = NULL; 325 326 cleanup: 327 if (result != ISC_R_SUCCESS) { 328 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 329 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 330 "failed to dynamically load " 331 "plugin '%s': %d (%s)", modpath, 332 GetLastError(), isc_result_totext(result)); 333 334 if (plugin != NULL) { 335 isc_mem_putanddetach(&plugin->mctx, plugin, 336 sizeof(*plugin)); 337 } 338 339 if (handle != NULL) { 340 FreeLibrary(handle); 341 } 342 } 343 344 return (result); 345 } 346 347 static void 348 unload_plugin(ns_plugin_t **pluginp) { 349 ns_plugin_t *plugin = NULL; 350 351 REQUIRE(pluginp != NULL && *pluginp != NULL); 352 353 plugin = *pluginp; 354 *pluginp = NULL; 355 356 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 357 NS_LOGMODULE_HOOKS, ISC_LOG_DEBUG(1), 358 "unloading plugin '%s'", plugin->modpath); 359 360 if (plugin->inst != NULL) { 361 plugin->destroy_func(&plugin->inst); 362 } 363 if (plugin->handle != NULL) { 364 FreeLibrary(plugin->handle); 365 } 366 367 if (plugin->modpath != NULL) { 368 isc_mem_free(plugin->mctx, plugin->modpath); 369 } 370 371 isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin)); 372 } 373 #else /* HAVE_DLFCN_H || _WIN32 */ 374 static isc_result_t 375 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) { 376 UNUSED(mctx); 377 UNUSED(modpath); 378 UNUSED(pluginp); 379 380 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 381 NS_LOGMODULE_HOOKS, ISC_LOG_ERROR, 382 "plugin support is not implemented"); 383 384 return (ISC_R_NOTIMPLEMENTED); 385 } 386 387 static void 388 unload_plugin(ns_plugin_t **pluginp) { 389 UNUSED(pluginp); 390 } 391 #endif /* HAVE_DLFCN_H */ 392 393 isc_result_t 394 ns_plugin_register(const char *modpath, const char *parameters, 395 const void *cfg, const char *cfg_file, 396 unsigned long cfg_line, 397 isc_mem_t *mctx, isc_log_t *lctx, void *actx, 398 dns_view_t *view) 399 { 400 isc_result_t result; 401 ns_plugin_t *plugin = NULL; 402 403 REQUIRE(mctx != NULL); 404 REQUIRE(lctx != NULL); 405 REQUIRE(view != NULL); 406 407 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 408 NS_LOGMODULE_HOOKS, ISC_LOG_INFO, 409 "loading plugin '%s'", modpath); 410 411 CHECK(load_plugin(mctx, modpath, &plugin)); 412 413 isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, 414 NS_LOGMODULE_HOOKS, ISC_LOG_INFO, 415 "registering plugin '%s'", modpath); 416 417 CHECK(plugin->register_func(parameters, cfg, cfg_file, cfg_line, 418 mctx, lctx, actx, view->hooktable, 419 &plugin->inst)); 420 421 ISC_LIST_APPEND(*(ns_plugins_t *)view->plugins, plugin, link); 422 423 cleanup: 424 if (result != ISC_R_SUCCESS && plugin != NULL) { 425 unload_plugin(&plugin); 426 } 427 428 return (result); 429 } 430 431 isc_result_t 432 ns_plugin_check(const char *modpath, const char *parameters, 433 const void *cfg, const char *cfg_file, unsigned long cfg_line, 434 isc_mem_t *mctx, isc_log_t *lctx, void *actx) 435 { 436 isc_result_t result; 437 ns_plugin_t *plugin = NULL; 438 439 CHECK(load_plugin(mctx, modpath, &plugin)); 440 441 result = plugin->check_func(parameters, cfg, cfg_file, cfg_line, 442 mctx, lctx, actx); 443 444 cleanup: 445 if (plugin != NULL) { 446 unload_plugin(&plugin); 447 } 448 449 return (result); 450 } 451 452 void 453 ns_hooktable_init(ns_hooktable_t *hooktable) { 454 int i; 455 456 for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) { 457 ISC_LIST_INIT((*hooktable)[i]); 458 } 459 } 460 461 isc_result_t 462 ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep) { 463 ns_hooktable_t *hooktable = NULL; 464 465 REQUIRE(tablep != NULL && *tablep == NULL); 466 467 hooktable = isc_mem_get(mctx, sizeof(*hooktable)); 468 469 ns_hooktable_init(hooktable); 470 471 *tablep = hooktable; 472 473 return (ISC_R_SUCCESS); 474 } 475 476 void 477 ns_hooktable_free(isc_mem_t *mctx, void **tablep) { 478 ns_hooktable_t *table = NULL; 479 ns_hook_t *hook = NULL, *next = NULL; 480 int i = 0; 481 482 REQUIRE(tablep != NULL && *tablep != NULL); 483 484 table = *tablep; 485 *tablep = NULL; 486 487 for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) { 488 for (hook = ISC_LIST_HEAD((*table)[i]); 489 hook != NULL; 490 hook = next) 491 { 492 next = ISC_LIST_NEXT(hook, link); 493 ISC_LIST_UNLINK((*table)[i], hook, link); 494 if (hook->mctx != NULL) { 495 isc_mem_putanddetach(&hook->mctx, 496 hook, sizeof(*hook)); 497 } 498 } 499 } 500 501 isc_mem_put(mctx, table, sizeof(*table)); 502 } 503 504 void 505 ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx, 506 ns_hookpoint_t hookpoint, const ns_hook_t *hook) 507 { 508 ns_hook_t *copy = NULL; 509 510 REQUIRE(hooktable != NULL); 511 REQUIRE(mctx != NULL); 512 REQUIRE(hookpoint < NS_HOOKPOINTS_COUNT); 513 REQUIRE(hook != NULL); 514 515 copy = isc_mem_get(mctx, sizeof(*copy)); 516 memset(copy, 0, sizeof(*copy)); 517 518 copy->action = hook->action; 519 copy->action_data = hook->action_data; 520 isc_mem_attach(mctx, ©->mctx); 521 522 ISC_LINK_INIT(copy, link); 523 ISC_LIST_APPEND((*hooktable)[hookpoint], copy, link); 524 } 525 526 void 527 ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp) { 528 ns_plugins_t *plugins = NULL; 529 530 REQUIRE(listp != NULL && *listp == NULL); 531 532 plugins = isc_mem_get(mctx, sizeof(*plugins)); 533 memset(plugins, 0, sizeof(*plugins)); 534 ISC_LIST_INIT(*plugins); 535 536 *listp = plugins; 537 } 538 539 void 540 ns_plugins_free(isc_mem_t *mctx, void **listp) { 541 ns_plugins_t *list = NULL; 542 ns_plugin_t *plugin = NULL, *next = NULL; 543 544 REQUIRE(listp != NULL && *listp != NULL); 545 546 list = *listp; 547 *listp = NULL; 548 549 for (plugin = ISC_LIST_HEAD(*list); 550 plugin != NULL; 551 plugin = next) 552 { 553 next = ISC_LIST_NEXT(plugin, link); 554 ISC_LIST_UNLINK(*list, plugin, link); 555 unload_plugin(&plugin); 556 } 557 558 isc_mem_put(mctx, list, sizeof(*list)); 559 } 560