1 /*
2 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
3 *
4 * SPDX-License-Identifier: MPL-2.0
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * See the COPYRIGHT file distributed with this work for additional
11 * information regarding copyright ownership.
12 */
13
14 #include <inttypes.h>
15 #include <stdbool.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #if HAVE_DLFCN_H
20 #include <dlfcn.h>
21 #endif /* if HAVE_DLFCN_H */
22
23 #include <isc/mem.h>
24 #include <isc/print.h>
25 #include <isc/result.h>
26 #include <isc/util.h>
27
28 #include <dns/dlz_dlopen.h>
29 #include <dns/log.h>
30 #include <dns/result.h>
31
32 #include <dlz/dlz_dlopen_driver.h>
33 #include <named/globals.h>
34
35 #ifdef ISC_DLZ_DLOPEN
36 static dns_sdlzimplementation_t *dlz_dlopen = NULL;
37
38 typedef struct dlopen_data {
39 isc_mem_t *mctx;
40 char *dl_path;
41 char *dlzname;
42 void *dl_handle;
43 void *dbdata;
44 unsigned int flags;
45 isc_mutex_t lock;
46 int version;
47 bool in_configure;
48
49 dlz_dlopen_version_t *dlz_version;
50 dlz_dlopen_create_t *dlz_create;
51 dlz_dlopen_findzonedb_t *dlz_findzonedb;
52 dlz_dlopen_lookup_t *dlz_lookup;
53 dlz_dlopen_authority_t *dlz_authority;
54 dlz_dlopen_allnodes_t *dlz_allnodes;
55 dlz_dlopen_allowzonexfr_t *dlz_allowzonexfr;
56 dlz_dlopen_newversion_t *dlz_newversion;
57 dlz_dlopen_closeversion_t *dlz_closeversion;
58 dlz_dlopen_configure_t *dlz_configure;
59 dlz_dlopen_ssumatch_t *dlz_ssumatch;
60 dlz_dlopen_addrdataset_t *dlz_addrdataset;
61 dlz_dlopen_subrdataset_t *dlz_subrdataset;
62 dlz_dlopen_delrdataset_t *dlz_delrdataset;
63 dlz_dlopen_destroy_t *dlz_destroy;
64 } dlopen_data_t;
65
66 /* Modules can choose whether they are lock-safe or not. */
67 #define MAYBE_LOCK(cd) \
68 do { \
69 if ((cd->flags & DNS_SDLZFLAG_THREADSAFE) == 0 && \
70 !cd->in_configure) \
71 LOCK(&cd->lock); \
72 } while (0)
73
74 #define MAYBE_UNLOCK(cd) \
75 do { \
76 if ((cd->flags & DNS_SDLZFLAG_THREADSAFE) == 0 && \
77 !cd->in_configure) \
78 UNLOCK(&cd->lock); \
79 } while (0)
80
81 /*
82 * Log a message at the given level.
83 */
84 static void
dlopen_log(int level,const char * fmt,...)85 dlopen_log(int level, const char *fmt, ...) {
86 va_list ap;
87 va_start(ap, fmt);
88 isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DLZ,
89 ISC_LOG_DEBUG(level), fmt, ap);
90 va_end(ap);
91 }
92
93 /*
94 * SDLZ methods
95 */
96
97 static isc_result_t
dlopen_dlz_allnodes(const char * zone,void * driverarg,void * dbdata,dns_sdlzallnodes_t * allnodes)98 dlopen_dlz_allnodes(const char *zone, void *driverarg, void *dbdata,
99 dns_sdlzallnodes_t *allnodes) {
100 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
101 isc_result_t result;
102
103 UNUSED(driverarg);
104
105 if (cd->dlz_allnodes == NULL) {
106 return (ISC_R_NOPERM);
107 }
108
109 MAYBE_LOCK(cd);
110 result = cd->dlz_allnodes(zone, cd->dbdata, allnodes);
111 MAYBE_UNLOCK(cd);
112 return (result);
113 }
114
115 static isc_result_t
dlopen_dlz_allowzonexfr(void * driverarg,void * dbdata,const char * name,const char * client)116 dlopen_dlz_allowzonexfr(void *driverarg, void *dbdata, const char *name,
117 const char *client) {
118 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
119 isc_result_t result;
120
121 UNUSED(driverarg);
122
123 if (cd->dlz_allowzonexfr == NULL) {
124 return (ISC_R_NOPERM);
125 }
126
127 MAYBE_LOCK(cd);
128 result = cd->dlz_allowzonexfr(cd->dbdata, name, client);
129 MAYBE_UNLOCK(cd);
130 return (result);
131 }
132
133 static isc_result_t
dlopen_dlz_authority(const char * zone,void * driverarg,void * dbdata,dns_sdlzlookup_t * lookup)134 dlopen_dlz_authority(const char *zone, void *driverarg, void *dbdata,
135 dns_sdlzlookup_t *lookup) {
136 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
137 isc_result_t result;
138
139 UNUSED(driverarg);
140
141 if (cd->dlz_authority == NULL) {
142 return (ISC_R_NOTIMPLEMENTED);
143 }
144
145 MAYBE_LOCK(cd);
146 result = cd->dlz_authority(zone, cd->dbdata, lookup);
147 MAYBE_UNLOCK(cd);
148 return (result);
149 }
150
151 static isc_result_t
dlopen_dlz_findzonedb(void * driverarg,void * dbdata,const char * name,dns_clientinfomethods_t * methods,dns_clientinfo_t * clientinfo)152 dlopen_dlz_findzonedb(void *driverarg, void *dbdata, const char *name,
153 dns_clientinfomethods_t *methods,
154 dns_clientinfo_t *clientinfo) {
155 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
156 isc_result_t result;
157
158 UNUSED(driverarg);
159
160 MAYBE_LOCK(cd);
161 result = cd->dlz_findzonedb(cd->dbdata, name, methods, clientinfo);
162 MAYBE_UNLOCK(cd);
163 return (result);
164 }
165
166 static isc_result_t
dlopen_dlz_lookup(const char * zone,const char * name,void * driverarg,void * dbdata,dns_sdlzlookup_t * lookup,dns_clientinfomethods_t * methods,dns_clientinfo_t * clientinfo)167 dlopen_dlz_lookup(const char *zone, const char *name, void *driverarg,
168 void *dbdata, dns_sdlzlookup_t *lookup,
169 dns_clientinfomethods_t *methods,
170 dns_clientinfo_t *clientinfo) {
171 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
172 isc_result_t result;
173
174 UNUSED(driverarg);
175
176 MAYBE_LOCK(cd);
177 result = cd->dlz_lookup(zone, name, cd->dbdata, lookup, methods,
178 clientinfo);
179 MAYBE_UNLOCK(cd);
180 return (result);
181 }
182
183 /*
184 * Load a symbol from the library
185 */
186 static void *
dl_load_symbol(dlopen_data_t * cd,const char * symbol,bool mandatory)187 dl_load_symbol(dlopen_data_t *cd, const char *symbol, bool mandatory) {
188 void *ptr = dlsym(cd->dl_handle, symbol);
189 if (ptr == NULL && mandatory) {
190 dlopen_log(ISC_LOG_ERROR,
191 "dlz_dlopen: library '%s' is missing "
192 "required symbol '%s'",
193 cd->dl_path, symbol);
194 }
195 return (ptr);
196 }
197
198 /*
199 * Called at startup for each dlopen zone in named.conf
200 */
201 static isc_result_t
dlopen_dlz_create(const char * dlzname,unsigned int argc,char * argv[],void * driverarg,void ** dbdata)202 dlopen_dlz_create(const char *dlzname, unsigned int argc, char *argv[],
203 void *driverarg, void **dbdata) {
204 dlopen_data_t *cd;
205 isc_mem_t *mctx = NULL;
206 isc_result_t result = ISC_R_FAILURE;
207 int dlopen_flags = 0;
208
209 UNUSED(driverarg);
210
211 if (argc < 2) {
212 dlopen_log(ISC_LOG_ERROR,
213 "dlz_dlopen driver for '%s' needs a path to "
214 "the shared library",
215 dlzname);
216 return (ISC_R_FAILURE);
217 }
218
219 isc_mem_create(&mctx);
220
221 cd = isc_mem_get(mctx, sizeof(*cd));
222 memset(cd, 0, sizeof(*cd));
223
224 cd->mctx = mctx;
225
226 cd->dl_path = isc_mem_strdup(cd->mctx, argv[1]);
227
228 cd->dlzname = isc_mem_strdup(cd->mctx, dlzname);
229
230 /* Initialize the lock */
231 isc_mutex_init(&cd->lock);
232
233 /* Open the library */
234 dlopen_flags = RTLD_NOW | RTLD_GLOBAL;
235
236 #if defined(RTLD_DEEPBIND) && !__SANITIZE_ADDRESS__ && !__SANITIZE_THREAD__
237 /*
238 * If RTLD_DEEPBIND is available then use it. This can avoid
239 * issues with a module using a different version of a system
240 * library than one that bind9 uses. For example, bind9 may link
241 * to MIT kerberos, but the module may use Heimdal. If we don't
242 * use RTLD_DEEPBIND then we could end up with Heimdal functions
243 * calling MIT functions, which leads to bizarre results (usually
244 * a segfault).
245 */
246 dlopen_flags |= RTLD_DEEPBIND;
247 #endif /* if defined(RTLD_DEEPBIND) && !__SANITIZE_ADDRESS__ && \
248 !__SANITIZE_THREAD__ */
249
250 cd->dl_handle = dlopen(cd->dl_path, dlopen_flags);
251 if (cd->dl_handle == NULL) {
252 dlopen_log(ISC_LOG_ERROR,
253 "dlz_dlopen failed to open library '%s' - %s",
254 cd->dl_path, dlerror());
255 result = ISC_R_FAILURE;
256 goto failed;
257 }
258
259 /* Find the symbols */
260 cd->dlz_version =
261 (dlz_dlopen_version_t *)dl_load_symbol(cd, "dlz_version", true);
262 cd->dlz_create = (dlz_dlopen_create_t *)dl_load_symbol(cd, "dlz_create",
263 true);
264 cd->dlz_lookup = (dlz_dlopen_lookup_t *)dl_load_symbol(cd, "dlz_lookup",
265 true);
266 cd->dlz_findzonedb = (dlz_dlopen_findzonedb_t *)dl_load_symbol(
267 cd, "dlz_findzonedb", true);
268
269 if (cd->dlz_create == NULL || cd->dlz_version == NULL ||
270 cd->dlz_lookup == NULL || cd->dlz_findzonedb == NULL)
271 {
272 /* We're missing a required symbol */
273 result = ISC_R_FAILURE;
274 goto failed;
275 }
276
277 cd->dlz_allowzonexfr = (dlz_dlopen_allowzonexfr_t *)dl_load_symbol(
278 cd, "dlz_allowzonexfr", false);
279 cd->dlz_allnodes = (dlz_dlopen_allnodes_t *)dl_load_symbol(
280 cd, "dlz_allnodes", (cd->dlz_allowzonexfr != NULL));
281 cd->dlz_authority = (dlz_dlopen_authority_t *)dl_load_symbol(
282 cd, "dlz_authority", false);
283 cd->dlz_newversion = (dlz_dlopen_newversion_t *)dl_load_symbol(
284 cd, "dlz_newversion", false);
285 cd->dlz_closeversion = (dlz_dlopen_closeversion_t *)dl_load_symbol(
286 cd, "dlz_closeversion", (cd->dlz_newversion != NULL));
287 cd->dlz_configure = (dlz_dlopen_configure_t *)dl_load_symbol(
288 cd, "dlz_configure", false);
289 cd->dlz_ssumatch = (dlz_dlopen_ssumatch_t *)dl_load_symbol(
290 cd, "dlz_ssumatch", false);
291 cd->dlz_addrdataset = (dlz_dlopen_addrdataset_t *)dl_load_symbol(
292 cd, "dlz_addrdataset", false);
293 cd->dlz_subrdataset = (dlz_dlopen_subrdataset_t *)dl_load_symbol(
294 cd, "dlz_subrdataset", false);
295 cd->dlz_delrdataset = (dlz_dlopen_delrdataset_t *)dl_load_symbol(
296 cd, "dlz_delrdataset", false);
297 cd->dlz_destroy = (dlz_dlopen_destroy_t *)dl_load_symbol(
298 cd, "dlz_destroy", false);
299
300 /* Check the version of the API is the same */
301 cd->version = cd->dlz_version(&cd->flags);
302 if (cd->version < (DLZ_DLOPEN_VERSION - DLZ_DLOPEN_AGE) ||
303 cd->version > DLZ_DLOPEN_VERSION)
304 {
305 dlopen_log(ISC_LOG_ERROR,
306 "dlz_dlopen: %s: incorrect driver API version %d, "
307 "requires %d",
308 cd->dl_path, cd->version, DLZ_DLOPEN_VERSION);
309 result = ISC_R_FAILURE;
310 goto failed;
311 }
312
313 /*
314 * Call the library's create function. Note that this is an
315 * extended version of dlz create, with the addition of
316 * named function pointers for helper functions that the
317 * driver will need. This avoids the need for the backend to
318 * link the BIND9 libraries
319 */
320 MAYBE_LOCK(cd);
321 result = cd->dlz_create(dlzname, argc - 1, argv + 1, &cd->dbdata, "log",
322 dlopen_log, "putrr", dns_sdlz_putrr,
323 "putnamedrr", dns_sdlz_putnamedrr,
324 "writeable_zone", dns_dlz_writeablezone, NULL);
325 MAYBE_UNLOCK(cd);
326 if (result != ISC_R_SUCCESS) {
327 goto failed;
328 }
329
330 *dbdata = cd;
331
332 return (ISC_R_SUCCESS);
333
334 failed:
335 dlopen_log(ISC_LOG_ERROR, "dlz_dlopen of '%s' failed", dlzname);
336 if (cd->dl_path != NULL) {
337 isc_mem_free(mctx, cd->dl_path);
338 }
339 if (cd->dlzname != NULL) {
340 isc_mem_free(mctx, cd->dlzname);
341 }
342 if (dlopen_flags != 0) {
343 isc_mutex_destroy(&cd->lock);
344 }
345 #ifdef HAVE_DLCLOSE
346 if (cd->dl_handle) {
347 dlclose(cd->dl_handle);
348 }
349 #endif /* ifdef HAVE_DLCLOSE */
350 isc_mem_put(mctx, cd, sizeof(*cd));
351 isc_mem_destroy(&mctx);
352 return (result);
353 }
354
355 /*
356 * Called when bind is shutting down
357 */
358 static void
dlopen_dlz_destroy(void * driverarg,void * dbdata)359 dlopen_dlz_destroy(void *driverarg, void *dbdata) {
360 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
361 isc_mem_t *mctx;
362
363 UNUSED(driverarg);
364
365 if (cd->dlz_destroy) {
366 MAYBE_LOCK(cd);
367 cd->dlz_destroy(cd->dbdata);
368 MAYBE_UNLOCK(cd);
369 }
370
371 if (cd->dl_path) {
372 isc_mem_free(cd->mctx, cd->dl_path);
373 }
374 if (cd->dlzname) {
375 isc_mem_free(cd->mctx, cd->dlzname);
376 }
377
378 #ifdef HAVE_DLCLOSE
379 if (cd->dl_handle) {
380 dlclose(cd->dl_handle);
381 }
382 #endif /* ifdef HAVE_DLCLOSE */
383
384 isc_mutex_destroy(&cd->lock);
385
386 mctx = cd->mctx;
387 isc_mem_put(mctx, cd, sizeof(*cd));
388 isc_mem_destroy(&mctx);
389 }
390
391 /*
392 * Called to start a transaction
393 */
394 static isc_result_t
dlopen_dlz_newversion(const char * zone,void * driverarg,void * dbdata,void ** versionp)395 dlopen_dlz_newversion(const char *zone, void *driverarg, void *dbdata,
396 void **versionp) {
397 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
398 isc_result_t result;
399
400 UNUSED(driverarg);
401
402 if (cd->dlz_newversion == NULL) {
403 return (ISC_R_NOTIMPLEMENTED);
404 }
405
406 MAYBE_LOCK(cd);
407 result = cd->dlz_newversion(zone, cd->dbdata, versionp);
408 MAYBE_UNLOCK(cd);
409 return (result);
410 }
411
412 /*
413 * Called to end a transaction
414 */
415 static void
dlopen_dlz_closeversion(const char * zone,bool commit,void * driverarg,void * dbdata,void ** versionp)416 dlopen_dlz_closeversion(const char *zone, bool commit, void *driverarg,
417 void *dbdata, void **versionp) {
418 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
419
420 UNUSED(driverarg);
421
422 if (cd->dlz_newversion == NULL) {
423 *versionp = NULL;
424 return;
425 }
426
427 MAYBE_LOCK(cd);
428 cd->dlz_closeversion(zone, commit, cd->dbdata, versionp);
429 MAYBE_UNLOCK(cd);
430 }
431
432 /*
433 * Called on startup to configure any writeable zones
434 */
435 static isc_result_t
dlopen_dlz_configure(dns_view_t * view,dns_dlzdb_t * dlzdb,void * driverarg,void * dbdata)436 dlopen_dlz_configure(dns_view_t *view, dns_dlzdb_t *dlzdb, void *driverarg,
437 void *dbdata) {
438 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
439 isc_result_t result;
440
441 UNUSED(driverarg);
442
443 if (cd->dlz_configure == NULL) {
444 return (ISC_R_SUCCESS);
445 }
446
447 MAYBE_LOCK(cd);
448 cd->in_configure = true;
449 result = cd->dlz_configure(view, dlzdb, cd->dbdata);
450 cd->in_configure = false;
451 MAYBE_UNLOCK(cd);
452
453 return (result);
454 }
455
456 /*
457 * Check for authority to change a name.
458 */
459 static bool
dlopen_dlz_ssumatch(const char * signer,const char * name,const char * tcpaddr,const char * type,const char * key,uint32_t keydatalen,unsigned char * keydata,void * driverarg,void * dbdata)460 dlopen_dlz_ssumatch(const char *signer, const char *name, const char *tcpaddr,
461 const char *type, const char *key, uint32_t keydatalen,
462 unsigned char *keydata, void *driverarg, void *dbdata) {
463 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
464 bool ret;
465
466 UNUSED(driverarg);
467
468 if (cd->dlz_ssumatch == NULL) {
469 return (false);
470 }
471
472 MAYBE_LOCK(cd);
473 ret = cd->dlz_ssumatch(signer, name, tcpaddr, type, key, keydatalen,
474 keydata, cd->dbdata);
475 MAYBE_UNLOCK(cd);
476
477 return (ret);
478 }
479
480 /*
481 * Add an rdataset.
482 */
483 static isc_result_t
dlopen_dlz_addrdataset(const char * name,const char * rdatastr,void * driverarg,void * dbdata,void * version)484 dlopen_dlz_addrdataset(const char *name, const char *rdatastr, void *driverarg,
485 void *dbdata, void *version) {
486 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
487 isc_result_t result;
488
489 UNUSED(driverarg);
490
491 if (cd->dlz_addrdataset == NULL) {
492 return (ISC_R_NOTIMPLEMENTED);
493 }
494
495 MAYBE_LOCK(cd);
496 result = cd->dlz_addrdataset(name, rdatastr, cd->dbdata, version);
497 MAYBE_UNLOCK(cd);
498
499 return (result);
500 }
501
502 /*
503 * Subtract an rdataset.
504 */
505 static isc_result_t
dlopen_dlz_subrdataset(const char * name,const char * rdatastr,void * driverarg,void * dbdata,void * version)506 dlopen_dlz_subrdataset(const char *name, const char *rdatastr, void *driverarg,
507 void *dbdata, void *version) {
508 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
509 isc_result_t result;
510
511 UNUSED(driverarg);
512
513 if (cd->dlz_subrdataset == NULL) {
514 return (ISC_R_NOTIMPLEMENTED);
515 }
516
517 MAYBE_LOCK(cd);
518 result = cd->dlz_subrdataset(name, rdatastr, cd->dbdata, version);
519 MAYBE_UNLOCK(cd);
520
521 return (result);
522 }
523
524 /*
525 * Delete a rdataset.
526 */
527 static isc_result_t
dlopen_dlz_delrdataset(const char * name,const char * type,void * driverarg,void * dbdata,void * version)528 dlopen_dlz_delrdataset(const char *name, const char *type, void *driverarg,
529 void *dbdata, void *version) {
530 dlopen_data_t *cd = (dlopen_data_t *)dbdata;
531 isc_result_t result;
532
533 UNUSED(driverarg);
534
535 if (cd->dlz_delrdataset == NULL) {
536 return (ISC_R_NOTIMPLEMENTED);
537 }
538
539 MAYBE_LOCK(cd);
540 result = cd->dlz_delrdataset(name, type, cd->dbdata, version);
541 MAYBE_UNLOCK(cd);
542
543 return (result);
544 }
545
546 static dns_sdlzmethods_t dlz_dlopen_methods = {
547 dlopen_dlz_create, dlopen_dlz_destroy, dlopen_dlz_findzonedb,
548 dlopen_dlz_lookup, dlopen_dlz_authority, dlopen_dlz_allnodes,
549 dlopen_dlz_allowzonexfr, dlopen_dlz_newversion, dlopen_dlz_closeversion,
550 dlopen_dlz_configure, dlopen_dlz_ssumatch, dlopen_dlz_addrdataset,
551 dlopen_dlz_subrdataset, dlopen_dlz_delrdataset
552 };
553 #endif /* ifdef ISC_DLZ_DLOPEN */
554
555 /*
556 * Register driver with BIND
557 */
558 isc_result_t
dlz_dlopen_init(isc_mem_t * mctx)559 dlz_dlopen_init(isc_mem_t *mctx) {
560 #ifndef ISC_DLZ_DLOPEN
561 UNUSED(mctx);
562 return (ISC_R_NOTIMPLEMENTED);
563 #else /* ifndef ISC_DLZ_DLOPEN */
564 isc_result_t result;
565
566 dlopen_log(2, "Registering DLZ_dlopen driver");
567
568 result = dns_sdlzregister("dlopen", &dlz_dlopen_methods, NULL,
569 DNS_SDLZFLAG_RELATIVEOWNER |
570 DNS_SDLZFLAG_RELATIVERDATA |
571 DNS_SDLZFLAG_THREADSAFE,
572 mctx, &dlz_dlopen);
573
574 if (result != ISC_R_SUCCESS) {
575 UNEXPECTED_ERROR(__FILE__, __LINE__,
576 "dns_sdlzregister() failed: %s",
577 isc_result_totext(result));
578 result = ISC_R_UNEXPECTED;
579 }
580
581 return (result);
582 #endif /* ifndef ISC_DLZ_DLOPEN */
583 }
584
585 /*
586 * Unregister the driver
587 */
588 void
dlz_dlopen_clear(void)589 dlz_dlopen_clear(void) {
590 #ifdef ISC_DLZ_DLOPEN
591 dlopen_log(2, "Unregistering DLZ_dlopen driver");
592 if (dlz_dlopen != NULL) {
593 dns_sdlzunregister(&dlz_dlopen);
594 }
595 #endif /* ifdef ISC_DLZ_DLOPEN */
596 }
597