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