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