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, &copy->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