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