hooks.c revision 1.1.1.8 1 /* $NetBSD: hooks.c,v 1.1.1.8 2024/02/21 21:54:47 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, ©->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