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