Home | History | Annotate | Line # | Download | only in ns
hooks.c revision 1.1.1.10
      1 /*	$NetBSD: hooks.c,v 1.1.1.10 2026/01/29 18:19:49 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 #include <isc/errno.h>
     23 #include <isc/list.h>
     24 #include <isc/log.h>
     25 #include <isc/mem.h>
     26 #include <isc/mutex.h>
     27 #include <isc/result.h>
     28 #include <isc/types.h>
     29 #include <isc/util.h>
     30 #include <isc/uv.h>
     31 
     32 #include <dns/view.h>
     33 
     34 #include <ns/hooks.h>
     35 #include <ns/log.h>
     36 #include <ns/query.h>
     37 
     38 struct ns_plugin {
     39 	isc_mem_t *mctx;
     40 	uv_lib_t handle;
     41 	void *inst;
     42 	char *modpath;
     43 	ns_plugin_check_t *check_func;
     44 	ns_plugin_register_t *register_func;
     45 	ns_plugin_destroy_t *destroy_func;
     46 	LINK(ns_plugin_t) link;
     47 };
     48 
     49 static ns_hooklist_t default_hooktable[NS_HOOKPOINTS_COUNT];
     50 ns_hooktable_t *ns__hook_table = &default_hooktable;
     51 
     52 isc_result_t
     53 ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) {
     54 	int result;
     55 
     56 	/*
     57 	 * On Unix systems, differentiate between paths and filenames.
     58 	 */
     59 	if (strchr(src, '/') != NULL) {
     60 		/*
     61 		 * 'src' is an absolute or relative path.  Copy it verbatim.
     62 		 */
     63 		result = snprintf(dst, dstsize, "%s", src);
     64 	} else {
     65 		/*
     66 		 * 'src' is a filename.  Prepend default plugin directory path.
     67 		 */
     68 		result = snprintf(dst, dstsize, "%s/%s", NAMED_PLUGINDIR, src);
     69 	}
     70 
     71 	if (result < 0) {
     72 		return isc_errno_toresult(errno);
     73 	} else if ((size_t)result >= dstsize) {
     74 		return ISC_R_NOSPACE;
     75 	} else {
     76 		return ISC_R_SUCCESS;
     77 	}
     78 }
     79 
     80 static isc_result_t
     81 load_symbol(uv_lib_t *handle, const char *modpath, const char *symbol_name,
     82 	    void **symbolp) {
     83 	void *symbol = NULL;
     84 	int r;
     85 
     86 	REQUIRE(handle != NULL);
     87 	REQUIRE(symbolp != NULL && *symbolp == NULL);
     88 
     89 	r = uv_dlsym(handle, symbol_name, &symbol);
     90 	if (r != 0) {
     91 		const char *errmsg = uv_dlerror(handle);
     92 		if (errmsg == NULL) {
     93 			errmsg = "returned function pointer is NULL";
     94 		}
     95 		isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
     96 			      NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
     97 			      "failed to look up symbol %s in "
     98 			      "plugin '%s': %s",
     99 			      symbol_name, modpath, errmsg);
    100 		return ISC_R_FAILURE;
    101 	}
    102 
    103 	*symbolp = symbol;
    104 
    105 	return ISC_R_SUCCESS;
    106 }
    107 
    108 static void
    109 unload_plugin(ns_plugin_t **pluginp);
    110 
    111 static isc_result_t
    112 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) {
    113 	isc_result_t result;
    114 	ns_plugin_t *plugin = NULL;
    115 	ns_plugin_version_t *version_func = NULL;
    116 	int version;
    117 	int r;
    118 
    119 	REQUIRE(pluginp != NULL && *pluginp == NULL);
    120 
    121 	plugin = isc_mem_get(mctx, sizeof(*plugin));
    122 	*plugin = (ns_plugin_t){
    123 		.modpath = isc_mem_strdup(mctx, modpath),
    124 	};
    125 
    126 	isc_mem_attach(mctx, &plugin->mctx);
    127 
    128 	ISC_LINK_INIT(plugin, link);
    129 
    130 	r = uv_dlopen(modpath, &plugin->handle);
    131 	if (r != 0) {
    132 		const char *errmsg = uv_dlerror(&plugin->handle);
    133 		if (errmsg == NULL) {
    134 			errmsg = "unknown error";
    135 		}
    136 		isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
    137 			      NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
    138 			      "failed to dlopen() plugin '%s': %s", modpath,
    139 			      errmsg);
    140 		CHECK(ISC_R_FAILURE);
    141 	}
    142 
    143 	CHECK(load_symbol(&plugin->handle, modpath, "plugin_version",
    144 			  (void **)&version_func));
    145 
    146 	version = version_func();
    147 	if (version < (NS_PLUGIN_VERSION - NS_PLUGIN_AGE) ||
    148 	    version > NS_PLUGIN_VERSION)
    149 	{
    150 		isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
    151 			      NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
    152 			      "plugin API version mismatch: %d/%d", version,
    153 			      NS_PLUGIN_VERSION);
    154 		CHECK(ISC_R_FAILURE);
    155 	}
    156 
    157 	CHECK(load_symbol(&plugin->handle, modpath, "plugin_check",
    158 			  (void **)&plugin->check_func));
    159 	CHECK(load_symbol(&plugin->handle, modpath, "plugin_register",
    160 			  (void **)&plugin->register_func));
    161 	CHECK(load_symbol(&plugin->handle, modpath, "plugin_destroy",
    162 			  (void **)&plugin->destroy_func));
    163 
    164 	*pluginp = plugin;
    165 
    166 	return ISC_R_SUCCESS;
    167 
    168 cleanup:
    169 	isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
    170 		      ISC_LOG_ERROR,
    171 		      "failed to dynamically load plugin '%s': %s", modpath,
    172 		      isc_result_totext(result));
    173 
    174 	unload_plugin(&plugin);
    175 
    176 	return result;
    177 }
    178 
    179 static void
    180 unload_plugin(ns_plugin_t **pluginp) {
    181 	ns_plugin_t *plugin = NULL;
    182 
    183 	REQUIRE(pluginp != NULL && *pluginp != NULL);
    184 
    185 	plugin = *pluginp;
    186 	*pluginp = NULL;
    187 
    188 	isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
    189 		      ISC_LOG_DEBUG(1), "unloading plugin '%s'",
    190 		      plugin->modpath);
    191 
    192 	if (plugin->inst != NULL) {
    193 		plugin->destroy_func(&plugin->inst);
    194 	}
    195 
    196 	uv_dlclose(&plugin->handle);
    197 	isc_mem_free(plugin->mctx, plugin->modpath);
    198 	isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin));
    199 }
    200 
    201 isc_result_t
    202 ns_plugin_register(const char *modpath, const char *parameters, const void *cfg,
    203 		   const char *cfg_file, unsigned long cfg_line,
    204 		   isc_mem_t *mctx, isc_log_t *lctx, void *actx,
    205 		   dns_view_t *view) {
    206 	isc_result_t result;
    207 	ns_plugin_t *plugin = NULL;
    208 
    209 	REQUIRE(mctx != NULL);
    210 	REQUIRE(lctx != NULL);
    211 	REQUIRE(view != NULL);
    212 
    213 	isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
    214 		      ISC_LOG_INFO, "loading plugin '%s'", modpath);
    215 
    216 	CHECK(load_plugin(mctx, modpath, &plugin));
    217 
    218 	isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL, NS_LOGMODULE_HOOKS,
    219 		      ISC_LOG_INFO, "registering plugin '%s'", modpath);
    220 
    221 	CHECK(plugin->check_func(parameters, cfg, cfg_file, cfg_line, mctx,
    222 				 lctx, actx));
    223 
    224 	CHECK(plugin->register_func(parameters, cfg, cfg_file, cfg_line, mctx,
    225 				    lctx, actx, view->hooktable,
    226 				    &plugin->inst));
    227 
    228 	ISC_LIST_APPEND(*(ns_plugins_t *)view->plugins, plugin, link);
    229 
    230 cleanup:
    231 	if (result != ISC_R_SUCCESS && plugin != NULL) {
    232 		unload_plugin(&plugin);
    233 	}
    234 
    235 	return result;
    236 }
    237 
    238 isc_result_t
    239 ns_plugin_check(const char *modpath, const char *parameters, const void *cfg,
    240 		const char *cfg_file, unsigned long cfg_line, isc_mem_t *mctx,
    241 		isc_log_t *lctx, void *actx) {
    242 	isc_result_t result;
    243 	ns_plugin_t *plugin = NULL;
    244 
    245 	CHECK(load_plugin(mctx, modpath, &plugin));
    246 
    247 	result = plugin->check_func(parameters, cfg, cfg_file, cfg_line, mctx,
    248 				    lctx, actx);
    249 
    250 cleanup:
    251 	if (plugin != NULL) {
    252 		unload_plugin(&plugin);
    253 	}
    254 
    255 	return result;
    256 }
    257 
    258 void
    259 ns_hooktable_init(ns_hooktable_t *hooktable) {
    260 	int i;
    261 
    262 	for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) {
    263 		ISC_LIST_INIT((*hooktable)[i]);
    264 	}
    265 }
    266 
    267 isc_result_t
    268 ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep) {
    269 	ns_hooktable_t *hooktable = NULL;
    270 
    271 	REQUIRE(tablep != NULL && *tablep == NULL);
    272 
    273 	hooktable = isc_mem_get(mctx, sizeof(*hooktable));
    274 
    275 	ns_hooktable_init(hooktable);
    276 
    277 	*tablep = hooktable;
    278 
    279 	return ISC_R_SUCCESS;
    280 }
    281 
    282 void
    283 ns_hooktable_free(isc_mem_t *mctx, void **tablep) {
    284 	ns_hooktable_t *table = NULL;
    285 	ns_hook_t *hook = NULL, *next = NULL;
    286 	int i = 0;
    287 
    288 	REQUIRE(tablep != NULL && *tablep != NULL);
    289 
    290 	table = *tablep;
    291 	*tablep = NULL;
    292 
    293 	for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) {
    294 		for (hook = ISC_LIST_HEAD((*table)[i]); hook != NULL;
    295 		     hook = next)
    296 		{
    297 			next = ISC_LIST_NEXT(hook, link);
    298 			ISC_LIST_UNLINK((*table)[i], hook, link);
    299 			if (hook->mctx != NULL) {
    300 				isc_mem_putanddetach(&hook->mctx, hook,
    301 						     sizeof(*hook));
    302 			}
    303 		}
    304 	}
    305 
    306 	isc_mem_put(mctx, table, sizeof(*table));
    307 }
    308 
    309 void
    310 ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx,
    311 	    ns_hookpoint_t hookpoint, const ns_hook_t *hook) {
    312 	ns_hook_t *copy = NULL;
    313 
    314 	REQUIRE(hooktable != NULL);
    315 	REQUIRE(mctx != NULL);
    316 	REQUIRE(hookpoint < NS_HOOKPOINTS_COUNT);
    317 	REQUIRE(hook != NULL);
    318 
    319 	copy = isc_mem_get(mctx, sizeof(*copy));
    320 	*copy = (ns_hook_t){
    321 		.action = hook->action,
    322 		.action_data = hook->action_data,
    323 	};
    324 	isc_mem_attach(mctx, &copy->mctx);
    325 
    326 	ISC_LINK_INIT(copy, link);
    327 	ISC_LIST_APPEND((*hooktable)[hookpoint], copy, link);
    328 }
    329 
    330 void
    331 ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp) {
    332 	ns_plugins_t *plugins = NULL;
    333 
    334 	REQUIRE(listp != NULL && *listp == NULL);
    335 
    336 	plugins = isc_mem_get(mctx, sizeof(*plugins));
    337 	*plugins = (ns_plugins_t){ 0 };
    338 	ISC_LIST_INIT(*plugins);
    339 
    340 	*listp = plugins;
    341 }
    342 
    343 void
    344 ns_plugins_free(isc_mem_t *mctx, void **listp) {
    345 	ns_plugins_t *list = NULL;
    346 	ns_plugin_t *plugin = NULL, *next = NULL;
    347 
    348 	REQUIRE(listp != NULL && *listp != NULL);
    349 
    350 	list = *listp;
    351 	*listp = NULL;
    352 
    353 	for (plugin = ISC_LIST_HEAD(*list); plugin != NULL; plugin = next) {
    354 		next = ISC_LIST_NEXT(plugin, link);
    355 		ISC_LIST_UNLINK(*list, plugin, link);
    356 		unload_plugin(&plugin);
    357 	}
    358 
    359 	isc_mem_put(mctx, list, sizeof(*list));
    360 }
    361