Home | History | Annotate | Line # | Download | only in ns
hooks.c revision 1.6
      1 /*	$NetBSD: hooks.c,v 1.6 2021/02/19 16:42:22 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 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 (/*CONSTCOND*/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 **)&register_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 **)&register_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, &copy->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