xref: /netbsd-src/external/mpl/bind/dist/lib/ns/hooks.c (revision e6c7e151de239c49d2e38720a061ed9d1fa99309)
1 /*	$NetBSD: hooks.c,v 1.4 2019/04/28 00:01:15 christos 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 http://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 <config.h>
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
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/print.h>
34 #include <isc/result.h>
35 #include <isc/platform.h>
36 #include <isc/util.h>
37 #include <isc/types.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
87 	/*
88 	 * On Windows, always copy 'src' do 'dst'.
89 	 */
90 	result = snprintf(dst, dstsize, "%s", src);
91 #endif
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,
105 	    const char *symbol_name, void **symbolp)
106 {
107 	void *symbol = NULL;
108 
109 	REQUIRE(handle != NULL);
110 	REQUIRE(symbolp != NULL && *symbolp == NULL);
111 
112 	/*
113 	 * Clear any pre-existing error conditions before running dlsym().
114 	 * (In this case, we expect dlsym() to return non-NULL values
115 	 * and will always return an error if it returns NULL, but
116 	 * this ensures that we'll report the correct error condition
117 	 * if there is one.)
118 	 */
119 	dlerror();
120 	symbol = dlsym(handle, symbol_name);
121 	if (symbol == NULL) {
122 		const char *errmsg = dlerror();
123 		if (errmsg == NULL) {
124 			errmsg = "returned function pointer is NULL";
125 		}
126 		isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
127 			      NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
128 			      "failed to look up symbol %s in "
129 			      "plugin '%s': %s",
130 			      symbol_name, modpath, errmsg);
131 		return (ISC_R_FAILURE);
132 	}
133 
134 	*symbolp = symbol;
135 
136 	return (ISC_R_SUCCESS);
137 }
138 
139 static isc_result_t
140 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) {
141 	isc_result_t result;
142 	void *handle = NULL;
143 	ns_plugin_t *plugin = NULL;
144 	ns_plugin_check_t *check_func = NULL;
145 	ns_plugin_register_t *register_func = NULL;
146 	ns_plugin_destroy_t *destroy_func = NULL;
147 	ns_plugin_version_t *version_func = NULL;
148 	int version, flags;
149 
150 	REQUIRE(pluginp != NULL && *pluginp == NULL);
151 
152 	flags = RTLD_LAZY | RTLD_LOCAL;
153 #if defined(RTLD_DEEPBIND) && !__SANITIZE_ADDRESS__
154 	flags |= RTLD_DEEPBIND;
155 #endif
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",
166 			      modpath, 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",
180 			      version, 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", modpath,
211 			      isc_result_totext(result));
212 
213 		if (plugin != NULL) {
214 			isc_mem_putanddetach(&plugin->mctx, plugin,
215 					     sizeof(*plugin));
216 		}
217 
218 		if (handle != NULL) {
219 			(void) dlclose(handle);
220 		}
221 	}
222 
223 	return (result);
224 }
225 
226 static void
227 unload_plugin(ns_plugin_t **pluginp) {
228 	ns_plugin_t *plugin = NULL;
229 
230 	REQUIRE(pluginp != NULL && *pluginp != NULL);
231 
232 	plugin = *pluginp;
233 	*pluginp = NULL;
234 
235 	isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
236 		      NS_LOGMODULE_HOOKS, ISC_LOG_DEBUG(1),
237 		      "unloading plugin '%s'", plugin->modpath);
238 
239 	if (plugin->inst != NULL) {
240 		plugin->destroy_func(&plugin->inst);
241 	}
242 	if (plugin->handle != NULL) {
243 		(void) dlclose(plugin->handle);
244 	}
245 	if (plugin->modpath != NULL) {
246 		isc_mem_free(plugin->mctx, plugin->modpath);
247 	}
248 
249 	isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin));
250 }
251 #elif _WIN32
252 static isc_result_t
253 load_symbol(HMODULE handle, const char *modpath,
254 	    const char *symbol_name, void **symbolp)
255 {
256 	void *symbol = NULL;
257 
258 	REQUIRE(handle != NULL);
259 	REQUIRE(symbolp != NULL && *symbolp == NULL);
260 
261 	symbol = GetProcAddress(handle, symbol_name);
262 	if (symbol == NULL) {
263 		int errstatus = GetLastError();
264 		isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
265 			      NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
266 			      "failed to look up symbol %s in "
267 			      "plugin '%s': %d",
268 			      symbol_name, modpath, errstatus);
269 		return (ISC_R_FAILURE);
270 	}
271 
272 	*symbolp = symbol;
273 
274 	return (ISC_R_SUCCESS);
275 }
276 
277 static isc_result_t
278 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) {
279 	isc_result_t result;
280 	HMODULE handle;
281 	ns_plugin_t *plugin = NULL;
282 	ns_plugin_register_t *register_func = NULL;
283 	ns_plugin_destroy_t *destroy_func = NULL;
284 	ns_plugin_version_t *version_func = NULL;
285 	int version;
286 
287 	REQUIRE(pluginp != NULL && *pluginp == NULL);
288 
289 	handle = LoadLibraryA(modpath);
290 	if (handle == NULL) {
291 		CHECK(ISC_R_FAILURE);
292 	}
293 
294 	CHECK(load_symbol(handle, modpath, "plugin_version",
295 			  (void **)&version_func));
296 
297 	version = version_func();
298 	if (version < (NS_PLUGIN_VERSION - NS_PLUGIN_AGE) ||
299 	    version > NS_PLUGIN_VERSION)
300 	{
301 		isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
302 			      NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
303 			      "plugin API version mismatch: %d/%d",
304 			      version, NS_PLUGIN_VERSION);
305 		CHECK(ISC_R_FAILURE);
306 	}
307 
308 	CHECK(load_symbol(handle, modpath, "plugin_register",
309 			  (void **)&register_func));
310 	CHECK(load_symbol(handle, modpath, "plugin_destroy",
311 			  (void **)&destroy_func));
312 
313 	plugin = isc_mem_get(mctx, sizeof(*plugin));
314 	memset(plugin, 0, sizeof(*plugin));
315 	isc_mem_attach(mctx, &plugin->mctx);
316 	plugin->handle = handle;
317 	plugin->modpath = isc_mem_strdup(plugin->mctx, modpath);
318 	plugin->register_func = register_func;
319 	plugin->destroy_func = destroy_func;
320 
321 	ISC_LINK_INIT(plugin, link);
322 
323 	*pluginp = plugin;
324 	plugin = NULL;
325 
326 cleanup:
327 	if (result != ISC_R_SUCCESS) {
328 		isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
329 			      NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
330 			      "failed to dynamically load "
331 			      "plugin '%s': %d (%s)", modpath,
332 			      GetLastError(), isc_result_totext(result));
333 
334 		if (plugin != NULL) {
335 			isc_mem_putanddetach(&plugin->mctx, plugin,
336 					     sizeof(*plugin));
337 		}
338 
339 		if (handle != NULL) {
340 			FreeLibrary(handle);
341 		}
342 	}
343 
344 	return (result);
345 }
346 
347 static void
348 unload_plugin(ns_plugin_t **pluginp) {
349 	ns_plugin_t *plugin = NULL;
350 
351 	REQUIRE(pluginp != NULL && *pluginp != NULL);
352 
353 	plugin = *pluginp;
354 	*pluginp = NULL;
355 
356 	isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
357 		      NS_LOGMODULE_HOOKS, ISC_LOG_DEBUG(1),
358 		      "unloading plugin '%s'", plugin->modpath);
359 
360 	if (plugin->inst != NULL) {
361 		plugin->destroy_func(&plugin->inst);
362 	}
363 	if (plugin->handle != NULL) {
364 		FreeLibrary(plugin->handle);
365 	}
366 
367 	if (plugin->modpath != NULL) {
368 		isc_mem_free(plugin->mctx, plugin->modpath);
369 	}
370 
371 	isc_mem_putanddetach(&plugin->mctx, plugin, sizeof(*plugin));
372 }
373 #else	/* HAVE_DLFCN_H || _WIN32 */
374 static isc_result_t
375 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) {
376 	UNUSED(mctx);
377 	UNUSED(modpath);
378 	UNUSED(pluginp);
379 
380 	isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
381 		      NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
382 		      "plugin support is not implemented");
383 
384 	return (ISC_R_NOTIMPLEMENTED);
385 }
386 
387 static void
388 unload_plugin(ns_plugin_t **pluginp) {
389 	UNUSED(pluginp);
390 }
391 #endif	/* HAVE_DLFCN_H */
392 
393 isc_result_t
394 ns_plugin_register(const char *modpath, const char *parameters,
395 		   const void *cfg, const char *cfg_file,
396 		   unsigned long cfg_line,
397 		   isc_mem_t *mctx, isc_log_t *lctx, void *actx,
398 		   dns_view_t *view)
399 {
400 	isc_result_t result;
401 	ns_plugin_t *plugin = NULL;
402 
403 	REQUIRE(mctx != NULL);
404 	REQUIRE(lctx != NULL);
405 	REQUIRE(view != NULL);
406 
407 	isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
408 		      NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
409 		      "loading plugin '%s'", modpath);
410 
411 	CHECK(load_plugin(mctx, modpath, &plugin));
412 
413 	isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
414 		      NS_LOGMODULE_HOOKS, ISC_LOG_INFO,
415 		      "registering plugin '%s'", modpath);
416 
417 	CHECK(plugin->register_func(parameters, cfg, cfg_file, cfg_line,
418 				    mctx, lctx, actx, view->hooktable,
419 				    &plugin->inst));
420 
421 	ISC_LIST_APPEND(*(ns_plugins_t *)view->plugins, plugin, link);
422 
423 cleanup:
424 	if (result != ISC_R_SUCCESS && plugin != NULL) {
425 		unload_plugin(&plugin);
426 	}
427 
428 	return (result);
429 }
430 
431 isc_result_t
432 ns_plugin_check(const char *modpath, const char *parameters,
433 		const void *cfg, const char *cfg_file, unsigned long cfg_line,
434 		isc_mem_t *mctx, isc_log_t *lctx, void *actx)
435 {
436 	isc_result_t result;
437 	ns_plugin_t *plugin = NULL;
438 
439 	CHECK(load_plugin(mctx, modpath, &plugin));
440 
441 	result = plugin->check_func(parameters, cfg, cfg_file, cfg_line,
442 				  mctx, lctx, actx);
443 
444 cleanup:
445 	if (plugin != NULL) {
446 		unload_plugin(&plugin);
447 	}
448 
449 	return (result);
450 }
451 
452 void
453 ns_hooktable_init(ns_hooktable_t *hooktable) {
454 	int i;
455 
456 	for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) {
457 		ISC_LIST_INIT((*hooktable)[i]);
458 	}
459 }
460 
461 isc_result_t
462 ns_hooktable_create(isc_mem_t *mctx, ns_hooktable_t **tablep) {
463 	ns_hooktable_t *hooktable = NULL;
464 
465 	REQUIRE(tablep != NULL && *tablep == NULL);
466 
467 	hooktable = isc_mem_get(mctx, sizeof(*hooktable));
468 
469 	ns_hooktable_init(hooktable);
470 
471 	*tablep = hooktable;
472 
473 	return (ISC_R_SUCCESS);
474 }
475 
476 void
477 ns_hooktable_free(isc_mem_t *mctx, void **tablep) {
478 	ns_hooktable_t *table = NULL;
479 	ns_hook_t *hook = NULL, *next = NULL;
480 	int i = 0;
481 
482 	REQUIRE(tablep != NULL && *tablep != NULL);
483 
484 	table = *tablep;
485 	*tablep = NULL;
486 
487 	for (i = 0; i < NS_HOOKPOINTS_COUNT; i++) {
488 		for (hook = ISC_LIST_HEAD((*table)[i]);
489 		     hook != NULL;
490 		     hook = next)
491 		{
492 			next = ISC_LIST_NEXT(hook, link);
493 			ISC_LIST_UNLINK((*table)[i], hook, link);
494 			if (hook->mctx != NULL) {
495 				isc_mem_putanddetach(&hook->mctx,
496 						     hook, sizeof(*hook));
497 			}
498 		}
499 	}
500 
501 	isc_mem_put(mctx, table, sizeof(*table));
502 }
503 
504 void
505 ns_hook_add(ns_hooktable_t *hooktable, isc_mem_t *mctx,
506 	    ns_hookpoint_t hookpoint, const ns_hook_t *hook)
507 {
508 	ns_hook_t *copy = NULL;
509 
510 	REQUIRE(hooktable != NULL);
511 	REQUIRE(mctx != NULL);
512 	REQUIRE(hookpoint < NS_HOOKPOINTS_COUNT);
513 	REQUIRE(hook != NULL);
514 
515 	copy = isc_mem_get(mctx, sizeof(*copy));
516 	memset(copy, 0, sizeof(*copy));
517 
518 	copy->action = hook->action;
519 	copy->action_data = hook->action_data;
520 	isc_mem_attach(mctx, &copy->mctx);
521 
522 	ISC_LINK_INIT(copy, link);
523 	ISC_LIST_APPEND((*hooktable)[hookpoint], copy, link);
524 }
525 
526 void
527 ns_plugins_create(isc_mem_t *mctx, ns_plugins_t **listp) {
528 	ns_plugins_t *plugins = NULL;
529 
530 	REQUIRE(listp != NULL && *listp == NULL);
531 
532 	plugins = isc_mem_get(mctx, sizeof(*plugins));
533 	memset(plugins, 0, sizeof(*plugins));
534 	ISC_LIST_INIT(*plugins);
535 
536 	*listp = plugins;
537 }
538 
539 void
540 ns_plugins_free(isc_mem_t *mctx, void **listp) {
541 	ns_plugins_t *list = NULL;
542 	ns_plugin_t *plugin = NULL, *next = NULL;
543 
544 	REQUIRE(listp != NULL && *listp != NULL);
545 
546 	list = *listp;
547 	*listp = NULL;
548 
549 	for (plugin = ISC_LIST_HEAD(*list);
550 	     plugin != NULL;
551 	     plugin = next)
552 	{
553 		next = ISC_LIST_NEXT(plugin, link);
554 		ISC_LIST_UNLINK(*list, plugin, link);
555 		unload_plugin(&plugin);
556 	}
557 
558 	isc_mem_put(mctx, list, sizeof(*list));
559 }
560