summaryrefslogtreecommitdiffstats
path: root/contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c')
-rw-r--r--contrib/dlz/modules/wildcard/dlz_wildcard_dynamic.c773
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);
+}