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 #include <string.h>
13 #include <uv.h>
14 
15 #include <isc/buffer.h>
16 #include <isc/mem.h>
17 #include <isc/mutex.h>
18 #include <isc/once.h>
19 #include <isc/region.h>
20 #include <isc/result.h>
21 #include <isc/task.h>
22 #include <isc/types.h>
23 #include <isc/util.h>
24 
25 #include <dns/dyndb.h>
26 #include <dns/log.h>
27 #include <dns/types.h>
28 #include <dns/view.h>
29 #include <dns/zone.h>
30 
31 #define CHECK(op)                            \
32 	do {                                 \
33 		result = (op);               \
34 		if (result != ISC_R_SUCCESS) \
35 			goto cleanup;        \
36 	} while (0)
37 
38 typedef struct dyndb_implementation dyndb_implementation_t;
39 struct dyndb_implementation {
40 	isc_mem_t *mctx;
41 	uv_lib_t handle;
42 	dns_dyndb_register_t *register_func;
43 	dns_dyndb_destroy_t *destroy_func;
44 	char *name;
45 	void *inst;
46 	LINK(dyndb_implementation_t) link;
47 };
48 
49 /*
50  * List of dyndb implementations. Locked by dyndb_lock.
51  *
52  * These are stored here so they can be cleaned up on shutdown.
53  * (The order in which they are stored is not important.)
54  */
55 static LIST(dyndb_implementation_t) dyndb_implementations;
56 
57 /* Locks dyndb_implementations. */
58 static isc_mutex_t dyndb_lock;
59 static isc_once_t once = ISC_ONCE_INIT;
60 
61 static void
dyndb_initialize(void)62 dyndb_initialize(void) {
63 	isc_mutex_init(&dyndb_lock);
64 	INIT_LIST(dyndb_implementations);
65 }
66 
67 static dyndb_implementation_t *
impfind(const char * name)68 impfind(const char *name) {
69 	dyndb_implementation_t *imp;
70 
71 	for (imp = ISC_LIST_HEAD(dyndb_implementations); imp != NULL;
72 	     imp = ISC_LIST_NEXT(imp, link))
73 	{
74 		if (strcasecmp(name, imp->name) == 0) {
75 			return (imp);
76 		}
77 	}
78 	return (NULL);
79 }
80 
81 static isc_result_t
load_symbol(uv_lib_t * handle,const char * filename,const char * symbol_name,void ** symbolp)82 load_symbol(uv_lib_t *handle, const char *filename, const char *symbol_name,
83 	    void **symbolp) {
84 	void *symbol;
85 	int r;
86 
87 	REQUIRE(handle != NULL);
88 	REQUIRE(symbolp != NULL && *symbolp == NULL);
89 
90 	r = uv_dlsym(handle, symbol_name, &symbol);
91 	if (r != 0) {
92 		const char *errmsg = uv_dlerror(handle);
93 		if (errmsg == NULL) {
94 			errmsg = "returned function pointer is NULL";
95 		}
96 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
97 			      DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR,
98 			      "failed to lookup symbol %s in "
99 			      "DynDB module '%s': %s",
100 			      symbol_name, filename, errmsg);
101 		return (ISC_R_FAILURE);
102 	}
103 
104 	*symbolp = symbol;
105 
106 	return (ISC_R_SUCCESS);
107 }
108 
109 static void
110 unload_library(dyndb_implementation_t **impp);
111 
112 static isc_result_t
load_library(isc_mem_t * mctx,const char * filename,const char * instname,dyndb_implementation_t ** impp)113 load_library(isc_mem_t *mctx, const char *filename, const char *instname,
114 	     dyndb_implementation_t **impp) {
115 	isc_result_t result;
116 	dyndb_implementation_t *imp = NULL;
117 	dns_dyndb_version_t *version_func = NULL;
118 	int version;
119 	int r;
120 
121 	REQUIRE(impp != NULL && *impp == NULL);
122 
123 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB,
124 		      ISC_LOG_INFO, "loading DynDB instance '%s' driver '%s'",
125 		      instname, filename);
126 
127 	imp = isc_mem_get(mctx, sizeof(*imp));
128 	memset(imp, 0, sizeof(*imp));
129 	isc_mem_attach(mctx, &imp->mctx);
130 
131 	imp->name = isc_mem_strdup(imp->mctx, instname);
132 
133 	INIT_LINK(imp, link);
134 
135 	r = uv_dlopen(filename, &imp->handle);
136 	if (r != 0) {
137 		const char *errmsg = uv_dlerror(&imp->handle);
138 		if (errmsg == NULL) {
139 			errmsg = "unknown error";
140 		}
141 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
142 			      DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR,
143 			      "failed to dlopen() DynDB instance '%s' driver "
144 			      "'%s': %s",
145 			      instname, filename, errmsg);
146 		CHECK(ISC_R_FAILURE);
147 	}
148 
149 	CHECK(load_symbol(&imp->handle, filename, "dyndb_version",
150 			  (void **)&version_func));
151 
152 	version = version_func(NULL);
153 	if (version < (DNS_DYNDB_VERSION - DNS_DYNDB_AGE) ||
154 	    version > DNS_DYNDB_VERSION)
155 	{
156 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
157 			      DNS_LOGMODULE_DYNDB, ISC_LOG_ERROR,
158 			      "driver API version mismatch: %d/%d", version,
159 			      DNS_DYNDB_VERSION);
160 		CHECK(ISC_R_FAILURE);
161 	}
162 
163 	CHECK(load_symbol(&imp->handle, filename, "dyndb_init",
164 			  (void **)&imp->register_func));
165 	CHECK(load_symbol(&imp->handle, filename, "dyndb_destroy",
166 			  (void **)&imp->destroy_func));
167 
168 	*impp = imp;
169 
170 	return (ISC_R_SUCCESS);
171 
172 cleanup:
173 	isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB,
174 		      ISC_LOG_ERROR,
175 		      "failed to dynamically load DynDB instance '%s' driver "
176 		      "'%s': %s",
177 		      instname, filename, isc_result_totext(result));
178 
179 	unload_library(&imp);
180 
181 	return (result);
182 }
183 
184 static void
unload_library(dyndb_implementation_t ** impp)185 unload_library(dyndb_implementation_t **impp) {
186 	dyndb_implementation_t *imp;
187 
188 	REQUIRE(impp != NULL && *impp != NULL);
189 
190 	imp = *impp;
191 	*impp = NULL;
192 
193 	/*
194 	 * This is a resource leak, but there is nothing we can currently do
195 	 * about it due to how configuration loading/reloading is designed.
196 	 */
197 	/* uv_dlclose(&imp->handle); */
198 	isc_mem_free(imp->mctx, imp->name);
199 	isc_mem_putanddetach(&imp->mctx, imp, sizeof(*imp));
200 }
201 
202 isc_result_t
dns_dyndb_load(const char * libname,const char * name,const char * parameters,const char * file,unsigned long line,isc_mem_t * mctx,const dns_dyndbctx_t * dctx)203 dns_dyndb_load(const char *libname, const char *name, const char *parameters,
204 	       const char *file, unsigned long line, isc_mem_t *mctx,
205 	       const dns_dyndbctx_t *dctx) {
206 	isc_result_t result;
207 	dyndb_implementation_t *implementation = NULL;
208 
209 	REQUIRE(DNS_DYNDBCTX_VALID(dctx));
210 	REQUIRE(name != NULL);
211 
212 	RUNTIME_CHECK(isc_once_do(&once, dyndb_initialize) == ISC_R_SUCCESS);
213 
214 	LOCK(&dyndb_lock);
215 
216 	/* duplicate instance names are not allowed */
217 	if (impfind(name) != NULL) {
218 		CHECK(ISC_R_EXISTS);
219 	}
220 
221 	CHECK(load_library(mctx, libname, name, &implementation));
222 	CHECK(implementation->register_func(mctx, name, parameters, file, line,
223 					    dctx, &implementation->inst));
224 
225 	APPEND(dyndb_implementations, implementation, link);
226 	result = ISC_R_SUCCESS;
227 
228 cleanup:
229 	if (result != ISC_R_SUCCESS) {
230 		if (implementation != NULL) {
231 			unload_library(&implementation);
232 		}
233 	}
234 
235 	UNLOCK(&dyndb_lock);
236 	return (result);
237 }
238 
239 void
dns_dyndb_cleanup(bool exiting)240 dns_dyndb_cleanup(bool exiting) {
241 	dyndb_implementation_t *elem;
242 	dyndb_implementation_t *prev;
243 
244 	RUNTIME_CHECK(isc_once_do(&once, dyndb_initialize) == ISC_R_SUCCESS);
245 
246 	LOCK(&dyndb_lock);
247 	elem = TAIL(dyndb_implementations);
248 	while (elem != NULL) {
249 		prev = PREV(elem, link);
250 		UNLINK(dyndb_implementations, elem, link);
251 		isc_log_write(dns_lctx, DNS_LOGCATEGORY_DATABASE,
252 			      DNS_LOGMODULE_DYNDB, ISC_LOG_INFO,
253 			      "unloading DynDB instance '%s'", elem->name);
254 		elem->destroy_func(&elem->inst);
255 		ENSURE(elem->inst == NULL);
256 		unload_library(&elem);
257 		elem = prev;
258 	}
259 	UNLOCK(&dyndb_lock);
260 
261 	if (exiting) {
262 		isc_mutex_destroy(&dyndb_lock);
263 	}
264 }
265 
266 isc_result_t
dns_dyndb_createctx(isc_mem_t * mctx,const void * hashinit,isc_log_t * lctx,dns_view_t * view,dns_zonemgr_t * zmgr,isc_task_t * task,isc_timermgr_t * tmgr,dns_dyndbctx_t ** dctxp)267 dns_dyndb_createctx(isc_mem_t *mctx, const void *hashinit, isc_log_t *lctx,
268 		    dns_view_t *view, dns_zonemgr_t *zmgr, isc_task_t *task,
269 		    isc_timermgr_t *tmgr, dns_dyndbctx_t **dctxp) {
270 	dns_dyndbctx_t *dctx;
271 
272 	REQUIRE(dctxp != NULL && *dctxp == NULL);
273 
274 	dctx = isc_mem_get(mctx, sizeof(*dctx));
275 
276 	memset(dctx, 0, sizeof(*dctx));
277 	if (view != NULL) {
278 		dns_view_attach(view, &dctx->view);
279 	}
280 	if (zmgr != NULL) {
281 		dns_zonemgr_attach(zmgr, &dctx->zmgr);
282 	}
283 	if (task != NULL) {
284 		isc_task_attach(task, &dctx->task);
285 	}
286 	dctx->timermgr = tmgr;
287 	dctx->hashinit = hashinit;
288 	dctx->lctx = lctx;
289 	dctx->refvar = &isc_bind9;
290 
291 	isc_mem_attach(mctx, &dctx->mctx);
292 	dctx->magic = DNS_DYNDBCTX_MAGIC;
293 
294 	*dctxp = dctx;
295 
296 	return (ISC_R_SUCCESS);
297 }
298 
299 void
dns_dyndb_destroyctx(dns_dyndbctx_t ** dctxp)300 dns_dyndb_destroyctx(dns_dyndbctx_t **dctxp) {
301 	dns_dyndbctx_t *dctx;
302 
303 	REQUIRE(dctxp != NULL && DNS_DYNDBCTX_VALID(*dctxp));
304 
305 	dctx = *dctxp;
306 	*dctxp = NULL;
307 
308 	dctx->magic = 0;
309 
310 	if (dctx->view != NULL) {
311 		dns_view_detach(&dctx->view);
312 	}
313 	if (dctx->zmgr != NULL) {
314 		dns_zonemgr_detach(&dctx->zmgr);
315 	}
316 	if (dctx->task != NULL) {
317 		isc_task_detach(&dctx->task);
318 	}
319 	dctx->timermgr = NULL;
320 	dctx->lctx = NULL;
321 
322 	isc_mem_putanddetach(&dctx->mctx, dctx, sizeof(*dctx));
323 }
324