xref: /netbsd-src/external/mpl/bind/dist/lib/ns/hooks.c (revision 82d56013d7b633d116a93943de88e08335357a7c)
1 /*	$NetBSD: hooks.c,v 1.7 2021/04/05 11:27:04 rillig 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 (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