diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 07:24:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 07:24:22 +0000 |
commit | 45d6379135504814ab723b57f0eb8be23393a51d (patch) | |
tree | d4f2ec4acca824a8446387a758b0ce4238a4dffa /contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c | |
parent | Initial commit. (diff) | |
download | bind9-upstream.tar.xz bind9-upstream.zip |
Adding upstream version 1:9.16.44.upstream/1%9.16.44upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c')
-rw-r--r-- | contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c | 773 |
1 files changed, 773 insertions, 0 deletions
diff --git a/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c b/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c new file mode 100644 index 0000000..95221a1 --- /dev/null +++ b/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c @@ -0,0 +1,773 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 and ISC + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + */ + +/* + * Copyright (C) Stichting NLnet, Netherlands, stichting@nlnet.nl. + * Copyright (C) Vadim Goncharov, Russia, vadim_nuclight@mail.ru. + * + * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was + * conceived and contributed by Rob Butler. + * + * Permission to use, copy, modify, and distribute this software for any purpose + * with or without fee is hereby granted, provided that the above copyright + * notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL STICHTING NLNET BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN + * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This provides the externally loadable wildcard DLZ module. + */ + +#include <ctype.h> +#include <inttypes.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <dlz_dbi.h> +#include <dlz_list.h> +#include <dlz_minimal.h> + +#define DE_CONST(konst, var) \ + do { \ + union { \ + const void *k; \ + void *v; \ + } _u; \ + _u.k = konst; \ + var = _u.v; \ + } while (0) + +/* fnmatch() return values. */ +#define FNM_NOMATCH 1 /* Match failed. */ + +/* fnmatch() flags. */ +#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ +#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ +#define FNM_PERIOD 0x04 /* Period must be matched by period. */ +#define FNM_LEADING_DIR 0x08 /* Ignore /<tail> after Imatch. */ +#define FNM_CASEFOLD 0x10 /* Case insensitive search. */ +#define FNM_IGNORECASE FNM_CASEFOLD +#define FNM_FILE_NAME FNM_PATHNAME + +/* + * Our data structures. + */ + +typedef struct named_rr nrr_t; +typedef DLZ_LIST(nrr_t) rr_list_t; + +typedef struct config_data { + char *zone_pattern; + char *axfr_pattern; + rr_list_t rrs_list; + char *zone; + char *record; + char *client; + + /* Helper functions from the dlz_dlopen driver */ + log_t *log; + dns_sdlz_putrr_t *putrr; + dns_sdlz_putnamedrr_t *putnamedrr; + dns_dlz_writeablezone_t *writeable_zone; +} config_data_t; + +struct named_rr { + char *name; + char *type; + int ttl; + query_list_t *data; + DLZ_LINK(nrr_t) link; +}; + +/* + * Forward references + */ +static int +rangematch(const char *, char, int, char **); + +static int +fnmatch(const char *pattern, const char *string, int flags); + +static void +b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr); + +static const char * +shortest_match(const char *pattern, const char *string); + +isc_result_t +dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) { + config_data_t *cd = (config_data_t *)dbdata; + isc_result_t result; + char *querystring = NULL; + nrr_t *nrec; + int i = 0; + + DE_CONST(zone, cd->zone); + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), "dlz_wildcard allnodes called for zone '%s'", + zone); + + result = ISC_R_FAILURE; + + nrec = DLZ_LIST_HEAD(cd->rrs_list); + while (nrec != NULL) { + cd->record = nrec->name; + + querystring = build_querystring(nrec->data); + + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto done; + } + + cd->log(ISC_LOG_DEBUG(2), + "dlz_wildcard allnodes entry num %d: calling " + "putnamedrr(name=%s type=%s ttl=%d qs=%s)", + i++, nrec->name, nrec->type, nrec->ttl, querystring); + + result = cd->putnamedrr(allnodes, nrec->name, nrec->type, + nrec->ttl, querystring); + if (result != ISC_R_SUCCESS) { + goto done; + } + + nrec = DLZ_LIST_NEXT(nrec, link); + } + +done: + cd->zone = NULL; + + if (querystring != NULL) { + free(querystring); + } + + return (result); +} + +isc_result_t +dlz_allowzonexfr(void *dbdata, const char *name, const char *client) { + config_data_t *cd = (config_data_t *)dbdata; + + UNUSED(name); + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), + "dlz_wildcard allowzonexfr called for client '%s'", client); + + if (fnmatch(cd->axfr_pattern, client, FNM_CASEFOLD) == 0) { + return (ISC_R_SUCCESS); + } else { + return (ISC_R_NOTFOUND); + } +} + +#if DLZ_DLOPEN_VERSION < 3 +isc_result_t +dlz_findzonedb(void *dbdata, const char *name) +#else /* if DLZ_DLOPEN_VERSION < 3 */ +isc_result_t +dlz_findzonedb(void *dbdata, const char *name, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif /* if DLZ_DLOPEN_VERSION < 3 */ +{ + config_data_t *cd = (config_data_t *)dbdata; + const char *p; + +#if DLZ_DLOPEN_VERSION >= 3 + UNUSED(methods); + UNUSED(clientinfo); +#endif /* if DLZ_DLOPEN_VERSION >= 3 */ + + p = shortest_match(cd->zone_pattern, name); + if (p == NULL) { + return (ISC_R_NOTFOUND); + } + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), "dlz_wildcard findzonedb matched '%s'", p); + + return (ISC_R_SUCCESS); +} + +#if DLZ_DLOPEN_VERSION == 1 +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup) +#else /* if DLZ_DLOPEN_VERSION == 1 */ +isc_result_t +dlz_lookup(const char *zone, const char *name, void *dbdata, + dns_sdlzlookup_t *lookup, dns_clientinfomethods_t *methods, + dns_clientinfo_t *clientinfo) +#endif /* if DLZ_DLOPEN_VERSION == 1 */ +{ + isc_result_t result; + config_data_t *cd = (config_data_t *)dbdata; + char *querystring = NULL; + const char *p; + char *namebuf; + nrr_t *nrec; + +#if DLZ_DLOPEN_VERSION >= 2 + UNUSED(methods); + UNUSED(clientinfo); +#endif /* if DLZ_DLOPEN_VERSION >= 2 */ + + p = shortest_match(cd->zone_pattern, zone); + if (p == NULL) { + return (ISC_R_NOTFOUND); + } + + DE_CONST(name, cd->record); + DE_CONST(p, cd->zone); + + if ((p != zone) && (strcmp(name, "@") == 0 || strcmp(name, zone) == 0)) + { + size_t len = p - zone; + namebuf = malloc(len); + if (namebuf == NULL) { + return (ISC_R_NOMEMORY); + } + strncpy(namebuf, zone, len - 1); + namebuf[len - 1] = '\0'; + cd->record = namebuf; + } else if (p == zone) { + cd->record = (char *)"@"; + } + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), + "dlz_wildcard_dynamic: lookup for '%s' in '%s': " + "trying '%s' in '%s'", + name, zone, cd->record, cd->zone); + + result = ISC_R_NOTFOUND; + nrec = DLZ_LIST_HEAD(cd->rrs_list); + while (nrec != NULL) { + nrr_t *next = DLZ_LIST_NEXT(nrec, link); + if (strcmp(cd->record, nrec->name) == 0) { + /* We handle authority data in dlz_authority() */ + if (strcmp(nrec->type, "SOA") == 0 || + strcmp(nrec->type, "NS") == 0) + { + nrec = next; + continue; + } + + querystring = build_querystring(nrec->data); + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto done; + } + + result = cd->putrr(lookup, nrec->type, nrec->ttl, + querystring); + if (result != ISC_R_SUCCESS) { + goto done; + } + + result = ISC_R_SUCCESS; + + free(querystring); + querystring = NULL; + } + nrec = next; + } + +done: + cd->zone = NULL; + cd->record = NULL; + + if (querystring != NULL) { + free(querystring); + } + + return (result); +} + +isc_result_t +dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) { + isc_result_t result; + config_data_t *cd = (config_data_t *)dbdata; + char *querystring = NULL; + nrr_t *nrec; + const char *p; + + p = shortest_match(cd->zone_pattern, zone); + if (p == NULL) { + return (ISC_R_NOTFOUND); + } + + DE_CONST(p, cd->zone); + + /* Write info message to log */ + cd->log(ISC_LOG_DEBUG(1), "dlz_wildcard_dynamic: authority for '%s'", + zone); + + result = ISC_R_NOTFOUND; + nrec = DLZ_LIST_HEAD(cd->rrs_list); + while (nrec != NULL) { + if (strcmp("@", nrec->name) == 0) { + isc_result_t presult; + + querystring = build_querystring(nrec->data); + if (querystring == NULL) { + result = ISC_R_NOMEMORY; + goto done; + } + + presult = cd->putrr(lookup, nrec->type, nrec->ttl, + querystring); + if (presult != ISC_R_SUCCESS) { + result = presult; + goto done; + } + + result = ISC_R_SUCCESS; + + free(querystring); + querystring = NULL; + } + nrec = DLZ_LIST_NEXT(nrec, link); + } + +done: + cd->zone = NULL; + + if (querystring != NULL) { + free(querystring); + } + + return (result); +} + +static void +destroy_rrlist(config_data_t *cd) { + nrr_t *trec, *nrec; + + nrec = DLZ_LIST_HEAD(cd->rrs_list); + + while (nrec != NULL) { + trec = nrec; + + destroy_querylist(&trec->data); + + if (trec->name != NULL) { + free(trec->name); + } + if (trec->type != NULL) { + free(trec->type); + } + trec->name = trec->type = NULL; + + /* Get the next record, before we destroy this one. */ + nrec = DLZ_LIST_NEXT(nrec, link); + + free(trec); + } +} + +isc_result_t +dlz_create(const char *dlzname, unsigned int argc, char *argv[], void **dbdata, + ...) { + config_data_t *cd; + char *endp; + unsigned int i; + int def_ttl; + nrr_t *trec = NULL; + isc_result_t result; + const char *helper_name; + va_list ap; + + if (argc < 8 || argc % 4 != 0) { + return (ISC_R_FAILURE); + } + + cd = calloc(1, sizeof(config_data_t)); + if (cd == NULL) { + return (ISC_R_NOMEMORY); + } + memset(cd, 0, sizeof(config_data_t)); + + /* Fill in the helper functions */ + va_start(ap, dbdata); + while ((helper_name = va_arg(ap, const char *)) != NULL) { + b9_add_helper(cd, helper_name, va_arg(ap, void *)); + } + va_end(ap); + + /* + * Write info message to log + */ + cd->log(ISC_LOG_INFO, + "Loading '%s' using DLZ_wildcard driver. " + "Zone: %s, AXFR allowed for: %s, $TTL: %s", + dlzname, argv[1], argv[2], argv[3]); + + /* initialize the records list here to simplify cleanup */ + DLZ_LIST_INIT(cd->rrs_list); + + cd->zone_pattern = strdup(argv[1]); + cd->axfr_pattern = strdup(argv[2]); + if (cd->zone_pattern == NULL || cd->axfr_pattern == NULL) { + result = ISC_R_NOMEMORY; + goto cleanup; + } + + def_ttl = strtol(argv[3], &endp, 10); + if (*endp != '\0' || def_ttl < 0) { + def_ttl = 3600; + cd->log(ISC_LOG_ERROR, "default TTL invalid, using 3600"); + } + + for (i = 4; i < argc; i += 4) { + result = ISC_R_NOMEMORY; + + trec = malloc(sizeof(nrr_t)); + if (trec == NULL) { + goto full_cleanup; + } + + memset(trec, 0, sizeof(nrr_t)); + + /* Initialize the record link */ + DLZ_LINK_INIT(trec, link); + /* Append the record to the list */ + DLZ_LIST_APPEND(cd->rrs_list, trec, link); + + trec->name = strdup(argv[i]); + if (trec->name == NULL) { + goto full_cleanup; + } + + trec->type = strdup(argv[i + 2]); + if (trec->type == NULL) { + goto full_cleanup; + } + + trec->ttl = strtol(argv[i + 1], &endp, 10); + if (argv[i + 1][0] == '\0' || *endp != '\0' || trec->ttl < 0) { + trec->ttl = def_ttl; + } + + result = build_querylist(argv[i + 3], &cd->zone, &cd->record, + &cd->client, &trec->data, 0, cd->log); + /* If unsuccessful, log err msg and cleanup */ + if (result != ISC_R_SUCCESS) { + cd->log(ISC_LOG_ERROR, + "Could not build RR data list at argv[%d]", + i + 3); + goto full_cleanup; + } + } + + *dbdata = cd; + + return (ISC_R_SUCCESS); + +full_cleanup: + destroy_rrlist(cd); + +cleanup: + if (cd->zone_pattern != NULL) { + free(cd->zone_pattern); + } + if (cd->axfr_pattern != NULL) { + free(cd->axfr_pattern); + } + free(cd); + + return (result); +} + +void +dlz_destroy(void *dbdata) { + config_data_t *cd = (config_data_t *)dbdata; + + /* + * Write debugging message to log + */ + cd->log(ISC_LOG_DEBUG(2), "Unloading DLZ_wildcard driver."); + + destroy_rrlist(cd); + + free(cd->zone_pattern); + free(cd->axfr_pattern); + free(cd); +} + +/* + * Return the version of the API + */ +int +dlz_version(unsigned int *flags) { + UNUSED(flags); + /* XXX: ok to set DNS_SDLZFLAG_THREADSAFE here? */ + return (DLZ_DLOPEN_VERSION); +} + +/* + * Register a helper function from the bind9 dlz_dlopen driver + */ +static void +b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr) { + if (strcmp(helper_name, "log") == 0) { + cd->log = (log_t *)ptr; + } + if (strcmp(helper_name, "putrr") == 0) { + cd->putrr = (dns_sdlz_putrr_t *)ptr; + } + if (strcmp(helper_name, "putnamedrr") == 0) { + cd->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr; + } + if (strcmp(helper_name, "writeable_zone") == 0) { + cd->writeable_zone = (dns_dlz_writeablezone_t *)ptr; + } +} + +static const char * +shortest_match(const char *pattern, const char *string) { + const char *p = string; + if (pattern == NULL || p == NULL || *p == '\0') { + return (NULL); + } + + p += strlen(p); + while (p-- > string) { + if (*p == '.') { + if (fnmatch(pattern, p + 1, FNM_CASEFOLD) == 0) { + return (p + 1); + } + } + } + if (fnmatch(pattern, string, FNM_CASEFOLD) == 0) { + return (string); + } + + return (NULL); +} + +/* + * The helper functions stolen from the FreeBSD kernel (sys/libkern/fnmatch.c). + * + * Why don't we use fnmatch(3) from libc? Because it is not thread-safe, and + * it is not thread-safe because it supports multibyte characters. But here, + * in BIND, we want to be thread-safe and don't need multibyte - DNS names are + * always ASCII. + */ +#define EOS '\0' + +#define RANGE_MATCH 1 +#define RANGE_NOMATCH 0 +#define RANGE_ERROR (-1) + +static int +fnmatch(const char *pattern, const char *string, int flags) { + const char *stringstart; + char *newp; + char c, test; + + for (stringstart = string;;) { + switch (c = *pattern++) { + case EOS: + if ((flags & FNM_LEADING_DIR) && *string == '/') { + return (0); + } + return (*string == EOS ? 0 : FNM_NOMATCH); + case '?': + if (*string == EOS) { + return (FNM_NOMATCH); + } + if (*string == '/' && (flags & FNM_PATHNAME)) { + return (FNM_NOMATCH); + } + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + { + return (FNM_NOMATCH); + } + ++string; + break; + case '*': + c = *pattern; + /* Collapse multiple stars. */ + while (c == '*') { + c = *++pattern; + } + + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + { + return (FNM_NOMATCH); + } + + /* Optimize for pattern with * at end or before /. */ + if (c == EOS) { + if (flags & FNM_PATHNAME) { + return ((flags & FNM_LEADING_DIR) || + index(string, + '/') == + NULL + ? 0 + : FNM_NOMATCH); + } else { + return (0); + } + } else if (c == '/' && flags & FNM_PATHNAME) { + if ((string = index(string, '/')) == NULL) { + return (FNM_NOMATCH); + } + break; + } + + /* General case, use recursion. */ + while ((test = *string) != EOS) { + if (!fnmatch(pattern, string, + flags & ~FNM_PERIOD)) + { + return (0); + } + if (test == '/' && flags & FNM_PATHNAME) { + break; + } + ++string; + } + return (FNM_NOMATCH); + case '[': + if (*string == EOS) { + return (FNM_NOMATCH); + } + if (*string == '/' && (flags & FNM_PATHNAME)) { + return (FNM_NOMATCH); + } + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + { + return (FNM_NOMATCH); + } + + switch (rangematch(pattern, *string, flags, &newp)) { + case RANGE_ERROR: + goto norm; + case RANGE_MATCH: + pattern = newp; + break; + case RANGE_NOMATCH: + return (FNM_NOMATCH); + } + ++string; + break; + case '\\': + if (!(flags & FNM_NOESCAPE)) { + if ((c = *pattern++) == EOS) { + c = '\\'; + --pattern; + } + } + FALLTHROUGH; + default: + norm: + if (c == *string) { + } else if ((flags & FNM_CASEFOLD) && + (tolower((unsigned char)c) == + tolower((unsigned char)*string))) + { + } else { + return (FNM_NOMATCH); + } + string++; + break; + } + } + UNREACHABLE(); +} + +static int +rangematch(const char *pattern, char test, int flags, char **newp) { + int negate, ok; + char c, c2; + + /* + * A bracket expression starting with an unquoted circumflex + * character produces unspecified results (IEEE 1003.2-1992, + * 3.13.2). This implementation treats it like '!', for + * consistency with the regular expression syntax. + * J.T. Conklin (conklin@ngai.kaleida.com) + */ + if ((negate = (*pattern == '!' || *pattern == '^'))) { + ++pattern; + } + + if (flags & FNM_CASEFOLD) { + test = tolower((unsigned char)test); + } + + /* + * A right bracket shall lose its special meaning and represent + * itself in a bracket expression if it occurs first in the list. + * -- POSIX.2 2.8.3.2 + */ + ok = 0; + c = *pattern++; + do { + if (c == '\\' && !(flags & FNM_NOESCAPE)) { + c = *pattern++; + } + if (c == EOS) { + return (RANGE_ERROR); + } + + if (c == '/' && (flags & FNM_PATHNAME)) { + return (RANGE_NOMATCH); + } + + if (flags & FNM_CASEFOLD) { + c = tolower((unsigned char)c); + } + + if (*pattern == '-' && (c2 = *(pattern + 1)) != EOS && + c2 != ']') + { + pattern += 2; + if (c2 == '\\' && !(flags & FNM_NOESCAPE)) { + c2 = *pattern++; + } + if (c2 == EOS) { + return (RANGE_ERROR); + } + + if (flags & FNM_CASEFOLD) { + c2 = tolower((unsigned char)c2); + } + + if (c <= test && test <= c2) { + ok = 1; + } + } else if (c == test) { + ok = 1; + } + } while ((c = *pattern++) != ']'); + + *newp = (char *)(uintptr_t)pattern; + return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH); +} |