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