1 /* $NetBSD: dlz_wildcard_dynamic.c,v 1.3 2014/12/10 04:37:55 christos Exp $ */
4 * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
5 * Copyright (C) 2012 Vadim Goncharov, Russia, vadim_nuclight@mail.ru.
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the
9 * above copyright notice and this permission notice appear in all
12 * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
13 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
14 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
15 * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
16 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
17 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
18 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
19 * USE OR PERFORMANCE OF THIS SOFTWARE.
21 * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
22 * conceived and contributed by Rob Butler.
24 * Permission to use, copy, modify, and distribute this software for any
25 * purpose with or without fee is hereby granted, provided that the
26 * above copyright notice and this permission notice appear in all
29 * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
30 * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
32 * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
33 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
34 * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
35 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
36 * USE OR PERFORMANCE OF THIS SOFTWARE.
40 * Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
41 * Copyright (C) 1999-2001 Internet Software Consortium.
43 * Permission to use, copy, modify, and/or distribute this software for any
44 * purpose with or without fee is hereby granted, provided that the above
45 * copyright notice and this permission notice appear in all copies.
47 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
48 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
49 * AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
50 * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
51 * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
52 * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
53 * PERFORMANCE OF THIS SOFTWARE.
57 * This provides the externally loadable wildcard DLZ module.
65 #include <dlz_minimal.h>
71 #define DE_CONST(konst, var) \
73 union { const void *k; void *v; } _u; \
76 } while (/*CONSTCOND*/0)
78 /* fnmatch() return values. */
79 #define FNM_NOMATCH 1 /* Match failed. */
81 /* fnmatch() flags. */
82 #define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */
83 #define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */
84 #define FNM_PERIOD 0x04 /* Period must be matched by period. */
85 #define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */
86 #define FNM_CASEFOLD 0x10 /* Case insensitive search. */
87 #define FNM_IGNORECASE FNM_CASEFOLD
88 #define FNM_FILE_NAME FNM_PATHNAME
91 * Our data structures.
94 typedef struct named_rr nrr_t
;
95 typedef DLZ_LIST(nrr_t
) rr_list_t
;
97 typedef struct config_data
{
105 /* Helper functions from the dlz_dlopen driver */
107 dns_sdlz_putrr_t
*putrr
;
108 dns_sdlz_putnamedrr_t
*putnamedrr
;
109 dns_dlz_writeablezone_t
*writeable_zone
;
117 DLZ_LINK(nrr_t
) link
;
124 rangematch(const char *, char, int, char **);
127 fnmatch(const char *pattern
, const char *string
, int flags
);
130 b9_add_helper(struct config_data
*cd
, const char *helper_name
, void *ptr
);
133 shortest_match(const char *pattern
, const char *string
);
136 dlz_allnodes(const char *zone
, void *dbdata
, dns_sdlzallnodes_t
*allnodes
) {
137 config_data_t
*cd
= (config_data_t
*) dbdata
;
139 char *querystring
= NULL
;
143 DE_CONST(zone
, cd
->zone
);
145 /* Write info message to log */
146 cd
->log(ISC_LOG_DEBUG(1),
147 "dlz_wildcard allnodes called for zone '%s'", zone
);
149 result
= ISC_R_FAILURE
;
151 nrec
= DLZ_LIST_HEAD(cd
->rrs_list
);
152 while (nrec
!= NULL
) {
153 cd
->record
= nrec
->name
;
155 querystring
= build_querystring(nrec
->data
);
157 if (querystring
== NULL
) {
158 result
= ISC_R_NOMEMORY
;
162 cd
->log(ISC_LOG_DEBUG(2),
163 "dlz_wildcard allnodes entry num %d: calling "
164 "putnamedrr(name=%s type=%s ttl=%d qs=%s)",
165 i
++, nrec
->name
, nrec
->type
, nrec
->ttl
, querystring
);
167 result
= cd
->putnamedrr(allnodes
, nrec
->name
, nrec
->type
,
168 nrec
->ttl
, querystring
);
169 if (result
!= ISC_R_SUCCESS
)
172 nrec
= DLZ_LIST_NEXT(nrec
, link
);
178 if (querystring
!= NULL
)
185 dlz_allowzonexfr(void *dbdata
, const char *name
, const char *client
) {
186 config_data_t
*cd
= (config_data_t
*) dbdata
;
190 /* Write info message to log */
191 cd
->log(ISC_LOG_DEBUG(1),
192 "dlz_wildcard allowzonexfr called for client '%s'", client
);
194 if (fnmatch(cd
->axfr_pattern
, client
, FNM_CASEFOLD
) == 0)
195 return (ISC_R_SUCCESS
);
197 return (ISC_R_NOTFOUND
);
200 #if DLZ_DLOPEN_VERSION < 3
202 dlz_findzonedb(void *dbdata
, const char *name
)
205 dlz_findzonedb(void *dbdata
, const char *name
,
206 dns_clientinfomethods_t
*methods
,
207 dns_clientinfo_t
*clientinfo
)
210 config_data_t
*cd
= (config_data_t
*) dbdata
;
213 #if DLZ_DLOPEN_VERSION >= 3
218 p
= shortest_match(cd
->zone_pattern
, name
);
220 return (ISC_R_NOTFOUND
);
222 /* Write info message to log */
223 cd
->log(ISC_LOG_DEBUG(1),
224 "dlz_wildcard findzonedb matched '%s'", p
);
226 return (ISC_R_SUCCESS
);
229 #if DLZ_DLOPEN_VERSION == 1
231 dlz_lookup(const char *zone
, const char *name
,
232 void *dbdata
, dns_sdlzlookup_t
*lookup
)
235 dlz_lookup(const char *zone
, const char *name
,
236 void *dbdata
, dns_sdlzlookup_t
*lookup
,
237 dns_clientinfomethods_t
*methods
,
238 dns_clientinfo_t
*clientinfo
)
242 config_data_t
*cd
= (config_data_t
*) dbdata
;
243 char *querystring
= NULL
;
247 isc_boolean_t origin
= ISC_TRUE
;
249 #if DLZ_DLOPEN_VERSION >= 2
254 p
= shortest_match(cd
->zone_pattern
, zone
);
256 return (ISC_R_NOTFOUND
);
258 DE_CONST(name
, cd
->record
);
259 DE_CONST(p
, cd
->zone
);
261 if ((p
!= zone
) && (strcmp(name
, "@") == 0 || strcmp(name
, zone
) == 0))
263 size_t len
= p
- zone
;
264 namebuf
= malloc(len
);
265 strncpy(namebuf
, zone
, len
- 1);
266 namebuf
[len
- 1] = '\0';
267 cd
->record
= namebuf
;
269 } else if (p
== zone
)
272 /* Write info message to log */
273 cd
->log(ISC_LOG_DEBUG(1),
274 "dlz_wildcard_dynamic: lookup for '%s' in '%s': "
275 "trying '%s' in '%s'",
276 name
, zone
, cd
->record
, cd
->zone
);
278 result
= ISC_R_NOTFOUND
;
279 nrec
= DLZ_LIST_HEAD(cd
->rrs_list
);
280 while (nrec
!= NULL
) {
281 nrr_t
*next
= DLZ_LIST_NEXT(nrec
, link
);
282 if (strcmp(cd
->record
, nrec
->name
) == 0) {
283 /* We handle authority data in dlz_authority() */
284 if (strcmp(nrec
->type
, "SOA") == 0 ||
285 strcmp(nrec
->type
, "NS") == 0)
291 querystring
= build_querystring(nrec
->data
);
292 if (querystring
== NULL
) {
293 result
= ISC_R_NOMEMORY
;
297 result
= cd
->putrr(lookup
, nrec
->type
,
298 nrec
->ttl
, querystring
);
299 if (result
!= ISC_R_SUCCESS
)
302 result
= ISC_R_SUCCESS
;
314 if (querystring
!= NULL
)
321 dlz_authority(const char *zone
, void *dbdata
, dns_sdlzlookup_t
*lookup
) {
323 config_data_t
*cd
= (config_data_t
*) dbdata
;
324 char *querystring
= NULL
;
326 const char *p
, *name
= "@";
328 p
= shortest_match(cd
->zone_pattern
, zone
);
330 return (ISC_R_NOTFOUND
);
332 DE_CONST(p
, cd
->zone
);
334 /* Write info message to log */
335 cd
->log(ISC_LOG_DEBUG(1),
336 "dlz_wildcard_dynamic: authority for '%s'", zone
);
338 result
= ISC_R_NOTFOUND
;
339 nrec
= DLZ_LIST_HEAD(cd
->rrs_list
);
340 while (nrec
!= NULL
) {
341 isc_boolean_t origin
;
342 if (strcmp("@", nrec
->name
) == 0) {
343 isc_result_t presult
;
345 querystring
= build_querystring(nrec
->data
);
346 if (querystring
== NULL
) {
347 result
= ISC_R_NOMEMORY
;
351 presult
= cd
->putrr(lookup
, nrec
->type
,
352 nrec
->ttl
, querystring
);
353 if (presult
!= ISC_R_SUCCESS
) {
358 result
= ISC_R_SUCCESS
;
363 nrec
= DLZ_LIST_NEXT(nrec
, link
);
369 if (querystring
!= NULL
)
376 destroy_rrlist(config_data_t
*cd
) {
379 nrec
= DLZ_LIST_HEAD(cd
->rrs_list
);
381 while (nrec
!= NULL
) {
384 destroy_querylist(&trec
->data
);
386 if (trec
->name
!= NULL
)
388 if (trec
->type
!= NULL
)
390 trec
->name
= trec
->type
= NULL
;
392 /* Get the next record, before we destroy this one. */
393 nrec
= DLZ_LIST_NEXT(nrec
, link
);
400 dlz_create(const char *dlzname
, unsigned int argc
, char *argv
[],
408 const char *helper_name
;
411 if (argc
< 8 || argc
% 4 != 0)
412 return (ISC_R_FAILURE
);
414 cd
= calloc(1, sizeof(config_data_t
));
416 return (ISC_R_NOMEMORY
);
417 memset(cd
, 0, sizeof(config_data_t
));
419 /* Fill in the helper functions */
420 va_start(ap
, dbdata
);
421 while ((helper_name
= va_arg(ap
, const char*)) != NULL
)
422 b9_add_helper(cd
, helper_name
, va_arg(ap
, void*));
426 * Write info message to log
428 cd
->log(ISC_LOG_INFO
,
429 "Loading '%s' using DLZ_wildcard driver. "
430 "Zone: %s, AXFR allowed for: %s, $TTL: %s",
431 dlzname
, argv
[1], argv
[2], argv
[3]);
433 /* initialize the records list here to simplify cleanup */
434 DLZ_LIST_INIT(cd
->rrs_list
);
436 cd
->zone_pattern
= strdup(argv
[1]);
437 if (cd
->zone_pattern
== NULL
)
440 cd
->axfr_pattern
= strdup(argv
[2]);
441 if (cd
->axfr_pattern
== NULL
)
444 def_ttl
= strtol(argv
[3], &endp
, 10);
445 if (*endp
!= '\0' || def_ttl
< 0) {
447 cd
->log(ISC_LOG_ERROR
, "default TTL invalid, using 3600");
450 for (i
= 4; i
< argc
; i
+= 4) {
451 result
= ISC_R_NOMEMORY
;
453 trec
= malloc(sizeof(nrr_t
));
457 memset(trec
, 0, sizeof(nrr_t
));
459 /* Initialize the record link */
460 DLZ_LINK_INIT(trec
, link
);
461 /* Append the record to the list */
462 DLZ_LIST_APPEND(cd
->rrs_list
, trec
, link
);
464 trec
->name
= strdup(argv
[i
]);
465 if (trec
->name
== NULL
)
468 trec
->type
= strdup(argv
[i
+ 2]);
469 if (trec
->type
== NULL
)
472 trec
->ttl
= strtol(argv
[i
+ 1], &endp
, 10);
473 if (argv
[i
+ 1][0] == '\0' || *endp
!= '\0' || trec
->ttl
< 0)
476 result
= build_querylist(argv
[i
+ 3], &cd
->zone
,
477 &cd
->record
, &cd
->client
,
478 &trec
->data
, 0, cd
->log
);
479 /* If unsuccessful, log err msg and cleanup */
480 if (result
!= ISC_R_SUCCESS
) {
481 cd
->log(ISC_LOG_ERROR
,
482 "Could not build RR data list at argv[%d]",
490 return (ISC_R_SUCCESS
);
496 if (cd
->zone_pattern
!= NULL
)
497 free(cd
->zone_pattern
);
498 if (cd
->axfr_pattern
!= NULL
)
499 free(cd
->axfr_pattern
);
506 dlz_destroy(void *dbdata
) {
507 config_data_t
*cd
= (config_data_t
*) dbdata
;
510 * Write debugging message to log
512 cd
->log(ISC_LOG_DEBUG(2), "Unloading DLZ_wildcard driver.");
516 free(cd
->zone_pattern
);
517 free(cd
->axfr_pattern
);
523 * Return the version of the API
526 dlz_version(unsigned int *flags
) {
528 /* XXX: ok to set DNS_SDLZFLAG_THREADSAFE here? */
529 return (DLZ_DLOPEN_VERSION
);
533 * Register a helper function from the bind9 dlz_dlopen driver
536 b9_add_helper(struct config_data
*cd
, const char *helper_name
, void *ptr
) {
537 if (strcmp(helper_name
, "log") == 0)
538 cd
->log
= (log_t
*)ptr
;
539 if (strcmp(helper_name
, "putrr") == 0)
540 cd
->putrr
= (dns_sdlz_putrr_t
*)ptr
;
541 if (strcmp(helper_name
, "putnamedrr") == 0)
542 cd
->putnamedrr
= (dns_sdlz_putnamedrr_t
*)ptr
;
543 if (strcmp(helper_name
, "writeable_zone") == 0)
544 cd
->writeable_zone
= (dns_dlz_writeablezone_t
*)ptr
;
548 shortest_match(const char *pattern
, const char *string
) {
549 const char *p
= string
;
550 if (pattern
== NULL
|| p
== NULL
|| *p
== '\0')
554 while (p
-- > string
) {
556 if (fnmatch(pattern
, p
+ 1, FNM_CASEFOLD
) == 0)
560 if (fnmatch(pattern
, string
, FNM_CASEFOLD
) == 0)
567 * The helper functions stolen from the FreeBSD kernel (sys/libkern/fnmatch.c).
569 * Why don't we use fnmatch(3) from libc? Because it is not thread-safe, and
570 * it is not thread-safe because it supports multibyte characters. But here,
571 * in BIND, we want to be thread-safe and don't need multibyte - DNS names are
576 #define RANGE_MATCH 1
577 #define RANGE_NOMATCH 0
578 #define RANGE_ERROR (-1)
581 fnmatch(const char *pattern
, const char *string
, int flags
) {
582 const char *stringstart
;
586 for (stringstart
= string
;;)
587 switch (c
= *pattern
++) {
589 if ((flags
& FNM_LEADING_DIR
) && *string
== '/')
591 return (*string
== EOS
? 0 : FNM_NOMATCH
);
594 return (FNM_NOMATCH
);
595 if (*string
== '/' && (flags
& FNM_PATHNAME
))
596 return (FNM_NOMATCH
);
597 if (*string
== '.' && (flags
& FNM_PERIOD
) &&
598 (string
== stringstart
||
599 ((flags
& FNM_PATHNAME
) && *(string
- 1) == '/')))
600 return (FNM_NOMATCH
);
605 /* Collapse multiple stars. */
609 if (*string
== '.' && (flags
& FNM_PERIOD
) &&
610 (string
== stringstart
||
611 ((flags
& FNM_PATHNAME
) && *(string
- 1) == '/')))
612 return (FNM_NOMATCH
);
614 /* Optimize for pattern with * at end or before /. */
616 if (flags
& FNM_PATHNAME
)
617 return ((flags
& FNM_LEADING_DIR
) ||
618 index(string
, '/') == NULL
?
622 else if (c
== '/' && flags
& FNM_PATHNAME
) {
623 if ((string
= index(string
, '/')) == NULL
)
624 return (FNM_NOMATCH
);
628 /* General case, use recursion. */
629 while ((test
= *string
) != EOS
) {
630 if (!fnmatch(pattern
, string
,
631 flags
& ~FNM_PERIOD
))
633 if (test
== '/' && flags
& FNM_PATHNAME
)
637 return (FNM_NOMATCH
);
640 return (FNM_NOMATCH
);
641 if (*string
== '/' && (flags
& FNM_PATHNAME
))
642 return (FNM_NOMATCH
);
643 if (*string
== '.' && (flags
& FNM_PERIOD
) &&
644 (string
== stringstart
||
645 ((flags
& FNM_PATHNAME
) && *(string
- 1) == '/')))
646 return (FNM_NOMATCH
);
648 switch (rangematch(pattern
, *string
, flags
, &newp
)) {
655 return (FNM_NOMATCH
);
660 if (!(flags
& FNM_NOESCAPE
)) {
661 if ((c
= *pattern
++) == EOS
) {
671 else if ((flags
& FNM_CASEFOLD
) &&
672 (tolower((unsigned char)c
) ==
673 tolower((unsigned char)*string
)))
676 return (FNM_NOMATCH
);
684 rangematch(const char *pattern
, char test
, int flags
, char **newp
) {
689 * A bracket expression starting with an unquoted circumflex
690 * character produces unspecified results (IEEE 1003.2-1992,
691 * 3.13.2). This implementation treats it like '!', for
692 * consistency with the regular expression syntax.
693 * J.T. Conklin (conklin@ngai.kaleida.com)
695 if ( (negate
= (*pattern
== '!' || *pattern
== '^')) )
698 if (flags
& FNM_CASEFOLD
)
699 test
= tolower((unsigned char)test
);
702 * A right bracket shall lose its special meaning and represent
703 * itself in a bracket expression if it occurs first in the list.
709 if (c
== '\\' && !(flags
& FNM_NOESCAPE
))
712 return (RANGE_ERROR
);
714 if (c
== '/' && (flags
& FNM_PATHNAME
))
715 return (RANGE_NOMATCH
);
717 if (flags
& FNM_CASEFOLD
)
718 c
= tolower((unsigned char)c
);
721 && (c2
= *(pattern
+1)) != EOS
&& c2
!= ']') {
723 if (c2
== '\\' && !(flags
& FNM_NOESCAPE
))
726 return (RANGE_ERROR
);
728 if (flags
& FNM_CASEFOLD
)
729 c2
= tolower((unsigned char)c2
);
731 if (c
<= test
&& test
<= c2
)
733 } else if (c
== test
)
735 } while ((c
= *pattern
++) != ']');
737 *newp
= (char *)(uintptr_t)pattern
;
738 return (ok
== negate
? RANGE_NOMATCH
: RANGE_MATCH
);