xref: /netbsd-src/external/mpl/bind/dist/lib/ns/hooks.c (revision ccd9df534e375a4366c5b55f23782053c7a98d82)
1 /*	$NetBSD: hooks.c,v 1.10 2024/02/21 22:52:46 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 #include <uv.h>
22 
23 #include <isc/errno.h>
24 #include <isc/list.h>
25 #include <isc/log.h>
26 #include <isc/mem.h>
27 #include <isc/mutex.h>
28 #include <isc/print.h>
29 #include <isc/result.h>
30 #include <isc/types.h>
31 #include <isc/util.h>
32 
33 #include <dns/view.h>
34 
35 #include <ns/hooks.h>
36 #include <ns/log.h>
37 #include <ns/query.h>
38 
39 #define CHECK(op)                              \
40 	do {                                   \
41 		result = (op);                 \
42 		if (result != ISC_R_SUCCESS) { \
43 			goto cleanup;          \
44 		}                              \
45 	} while (0)
46 
47 struct ns_plugin {
48 	isc_mem_t *mctx;
49 	uv_lib_t handle;
50 	void *inst;
51 	char *modpath;
52 	ns_plugin_check_t *check_func;
53 	ns_plugin_register_t *register_func;
54 	ns_plugin_destroy_t *destroy_func;
55 	LINK(ns_plugin_t) link;
56 };
57 
58 static ns_hooklist_t default_hooktable[NS_HOOKPOINTS_COUNT];
59 ns_hooktable_t *ns__hook_table = &default_hooktable;
60 
61 isc_result_t
62 ns_plugin_expandpath(const char *src, char *dst, size_t dstsize) {
63 	int result;
64 
65 	/*
66 	 * On Unix systems, differentiate between paths and filenames.
67 	 */
68 	if (strchr(src, '/') != NULL) {
69 		/*
70 		 * 'src' is an absolute or relative path.  Copy it verbatim.
71 		 */
72 		result = snprintf(dst, dstsize, "%s", src);
73 	} else {
74 		/*
75 		 * 'src' is a filename.  Prepend default plugin directory path.
76 		 */
77 		result = snprintf(dst, dstsize, "%s/%s", NAMED_PLUGINDIR, src);
78 	}
79 
80 	if (result < 0) {
81 		return (isc_errno_toresult(errno));
82 	} else if ((size_t)result >= dstsize) {
83 		return (ISC_R_NOSPACE);
84 	} else {
85 		return (ISC_R_SUCCESS);
86 	}
87 }
88 
89 static isc_result_t
90 load_symbol(uv_lib_t *handle, const char *modpath, const char *symbol_name,
91 	    void **symbolp) {
92 	void *symbol = NULL;
93 	int r;
94 
95 	REQUIRE(handle != NULL);
96 	REQUIRE(symbolp != NULL && *symbolp == NULL);
97 
98 	r = uv_dlsym(handle, symbol_name, &symbol);
99 	if (r != 0) {
100 		const char *errmsg = uv_dlerror(handle);
101 		if (errmsg == NULL) {
102 			errmsg = "returned function pointer is NULL";
103 		}
104 		isc_log_write(ns_lctx, NS_LOGCATEGORY_GENERAL,
105 			      NS_LOGMODULE_HOOKS, ISC_LOG_ERROR,
106 			      "failed to look up symbol %s in "
107 			      "plugin '%s': %s",
108 			      symbol_name, modpath, errmsg);
109 		return (ISC_R_FAILURE);
110 	}
111 
112 	*symbolp = symbol;
113 
114 	return (ISC_R_SUCCESS);
115 }
116 
117 static void
118 unload_plugin(ns_plugin_t **pluginp);
119 
120 static isc_result_t
121 load_plugin(isc_mem_t *mctx, const char *modpath, ns_plugin_t **pluginp) {
122 	isc_result_t result;
123 	ns_plugin_t *plugin = NULL;
124 	ns_plugin_version_t *version_func = NULL;
125 	int version;
126 	int r;
127 
128 	REQUIRE(pluginp != NULL && *pluginp == NULL);
129 
130 	plugin = isc_mem_get(mctx, sizeof(*plugin));
131 	memset(plugin, 0, sizeof(*plugin));
132 	isc_mem_attach(mctx, &plugin->mctx);
133 
134 	plugin->modpath = isc_mem_strdup(plugin->mctx, modpath);
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 	memset(copy, 0, sizeof(*copy));
326 
327 	copy->action = hook->action;
328 	copy->action_data = hook->action_data;
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 	memset(plugins, 0, sizeof(*plugins));
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