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