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