Home | History | Annotate | Line # | Download | only in ns
hooks.c revision 1.8
      1 /*	$NetBSD: hooks.c,v 1.8 2022/09/23 12:15:36 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 **)&register_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 **)&register_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 			next = ISC_LIST_NEXT(hook, link);
    483 			ISC_LIST_UNLINK((*table)[i], hook, link);
    484 			if (hook->mctx != NULL) {
    485 				isc_mem_putanddetach(&hook->mctx, hook,
    486 						     sizeof(*hook));
    487 			}
    488 		}
    489 	}
    490 
    491 	isc_mem_put(mctx, table, sizeof(*table));
    492 }
    493 
    494 void
    495 ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx,
    496 	    ns_hookpoint_t hookpoint, const ns_hook_t *hook) {
    497 	ns_hook_t *copy = NULL;
    498 
    499 	REQUIRE(hooktable != NULL);
    500 	REQUIRE(mctx != NULL);
    501 	REQUIRE(hookpoint < NS_HOOKPOINTS_COUNT);
    502 	REQUIRE(hook != NULL);
    503 
    504 	copy = isc_mem_get(mctx, sizeof(*copy));
    505 	memset(copy, 0, sizeof(*copy));
    506 
    507 	copy->action = hook->action;
    508 	copy->action_data = hook->action_data;
    509 	isc_mem_attach(mctx, &copy->mctx);
    510 
    511 	ISC_LINK_INIT(copy, link);
    512 	ISC_LIST_APPEND((*hooktable)[hookpoint], copy, link);
    513 }
    514 
    515 void
    516 ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp) {
    517 	ns_plugins_t *plugins = NULL;
    518 
    519 	REQUIRE(listp != NULL && *listp == NULL);
    520 
    521 	plugins = isc_mem_get(mctx, sizeof(*plugins));
    522 	memset(plugins, 0, sizeof(*plugins));
    523 	ISC_LIST_INIT(*plugins);
    524 
    525 	*listp = plugins;
    526 }
    527 
    528 void
    529 ns_plugins_free(isc_mem_t *mctx, void **listp) {
    530 	ns_plugins_t *list = NULL;
    531 	ns_plugin_t *plugin = NULL, *next = NULL;
    532 
    533 	REQUIRE(listp != NULL && *listp != NULL);
    534 
    535 	list = *listp;
    536 	*listp = NULL;
    537 
    538 	for (plugin = ISC_LIST_HEAD(*list); plugin != NULL; plugin = next) {
    539 		next = ISC_LIST_NEXT(plugin, link);
    540 		ISC_LIST_UNLINK(*list, plugin, link);
    541 		unload_plugin(&plugin);
    542 	}
    543 
    544 	isc_mem_put(mctx, list, sizeof(*list));
    545 }
    546