1 /*	$NetBSD: dlz_wildcard_dynamic.c,v 1.3 2014/12/10 04:37:55 christos Exp $	*/
2 
3 /*
4  * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
5  * Copyright (C) 2012 Vadim Goncharov, Russia, vadim_nuclight@mail.ru.
6  *
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
10  * copies.
11  *
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.
20  *
21  * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
22  * conceived and contributed by Rob Butler.
23  *
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
27  * copies.
28  *
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.
37  */
38 
39 /*
40  * Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
41  * Copyright (C) 1999-2001  Internet Software Consortium.
42  *
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.
46  *
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.
54  */
55 
56 /*
57  * This provides the externally loadable wildcard DLZ module.
58  */
59 
60 #include <stdio.h>
61 #include <string.h>
62 #include <stdarg.h>
63 #include <stdlib.h>
64 
65 #include <dlz_minimal.h>
66 #include <dlz_list.h>
67 #include <dlz_dbi.h>
68 
69 #include <ctype.h>
70 
71 #define DE_CONST(konst, var) \
72 	do { \
73 		union { const void *k; void *v; } _u; \
74 		_u.k = konst; \
75 		var = _u.v; \
76 	} while (/*CONSTCOND*/0)
77 
78 /* fnmatch() return values. */
79 #define	FNM_NOMATCH	1	/* Match failed. */
80 
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
89 
90 /*
91  * Our data structures.
92  */
93 
94 typedef struct named_rr nrr_t;
95 typedef DLZ_LIST(nrr_t) rr_list_t;
96 
97 typedef struct config_data {
98 	char		*zone_pattern;
99 	char		*axfr_pattern;
100 	rr_list_t	rrs_list;
101 	char		*zone;
102 	char		*record;
103 	char		*client;
104 
105 	/* Helper functions from the dlz_dlopen driver */
106 	log_t *log;
107 	dns_sdlz_putrr_t *putrr;
108 	dns_sdlz_putnamedrr_t *putnamedrr;
109 	dns_dlz_writeablezone_t *writeable_zone;
110 } config_data_t;
111 
112 struct named_rr {
113 	char		*name;
114 	char		*type;
115 	int		ttl;
116 	query_list_t	*data;
117 	DLZ_LINK(nrr_t)	link;
118 };
119 
120 /*
121  * Forward references
122  */
123 static int
124 rangematch(const char *, char, int, char **);
125 
126 static int
127 fnmatch(const char *pattern, const char *string, int flags);
128 
129 static void
130 b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr);
131 
132 static const char *
133 shortest_match(const char *pattern, const char *string);
134 
135 isc_result_t
136 dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) {
137 	config_data_t *cd = (config_data_t *) dbdata;
138 	isc_result_t result;
139 	char *querystring = NULL;
140 	nrr_t *nrec;
141 	int i = 0;
142 
143 	DE_CONST(zone, cd->zone);
144 
145 	/* Write info message to log */
146 	cd->log(ISC_LOG_DEBUG(1),
147 		"dlz_wildcard allnodes called for zone '%s'", zone);
148 
149 	result = ISC_R_FAILURE;
150 
151 	nrec = DLZ_LIST_HEAD(cd->rrs_list);
152 	while (nrec != NULL) {
153 		cd->record = nrec->name;
154 
155 		querystring = build_querystring(nrec->data);
156 
157 		if (querystring == NULL) {
158 			result = ISC_R_NOMEMORY;
159 			goto done;
160 		}
161 
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);
166 
167 		result = cd->putnamedrr(allnodes, nrec->name, nrec->type,
168 					nrec->ttl, querystring);
169 		if (result != ISC_R_SUCCESS)
170 			goto done;
171 
172 		nrec = DLZ_LIST_NEXT(nrec, link);
173 	}
174 
175 done:
176 	cd->zone = NULL;
177 
178 	if (querystring != NULL)
179 		free(querystring);
180 
181 	return (result);
182 }
183 
184 isc_result_t
185 dlz_allowzonexfr(void *dbdata, const char *name, const char *client) {
186 	config_data_t *cd = (config_data_t *) dbdata;
187 
188 	UNUSED(name);
189 
190 	/* Write info message to log */
191 	cd->log(ISC_LOG_DEBUG(1),
192 		"dlz_wildcard allowzonexfr called for client '%s'", client);
193 
194 	if (fnmatch(cd->axfr_pattern, client, FNM_CASEFOLD) == 0)
195 		return (ISC_R_SUCCESS);
196 	else
197 		return (ISC_R_NOTFOUND);
198 }
199 
200 #if DLZ_DLOPEN_VERSION < 3
201 isc_result_t
202 dlz_findzonedb(void *dbdata, const char *name)
203 #else
204 isc_result_t
205 dlz_findzonedb(void *dbdata, const char *name,
206 	       dns_clientinfomethods_t *methods,
207 	       dns_clientinfo_t *clientinfo)
208 #endif
209 {
210 	config_data_t *cd = (config_data_t *) dbdata;
211 	const char *p;
212 
213 #if DLZ_DLOPEN_VERSION >= 3
214 	UNUSED(methods);
215 	UNUSED(clientinfo);
216 #endif
217 
218 	p = shortest_match(cd->zone_pattern, name);
219 	if (p == NULL)
220 		return (ISC_R_NOTFOUND);
221 
222 	/* Write info message to log */
223 	cd->log(ISC_LOG_DEBUG(1),
224 		"dlz_wildcard findzonedb matched '%s'", p);
225 
226 	return (ISC_R_SUCCESS);
227 }
228 
229 #if DLZ_DLOPEN_VERSION == 1
230 isc_result_t
231 dlz_lookup(const char *zone, const char *name,
232 	   void *dbdata, dns_sdlzlookup_t *lookup)
233 #else
234 isc_result_t
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)
239 #endif
240 {
241 	isc_result_t result;
242 	config_data_t *cd = (config_data_t *) dbdata;
243 	char *querystring = NULL;
244 	const char *p;
245 	char *namebuf;
246 	nrr_t *nrec;
247 	isc_boolean_t origin = ISC_TRUE;
248 
249 #if DLZ_DLOPEN_VERSION >= 2
250 	UNUSED(methods);
251 	UNUSED(clientinfo);
252 #endif
253 
254 	p = shortest_match(cd->zone_pattern, zone);
255 	if (p == NULL)
256 		return (ISC_R_NOTFOUND);
257 
258 	DE_CONST(name, cd->record);
259 	DE_CONST(p, cd->zone);
260 
261 	if ((p != zone) && (strcmp(name, "@") == 0 || strcmp(name, zone) == 0))
262 	{
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;
268 		origin = ISC_FALSE;
269 	} else if (p == zone)
270 		cd->record = "@";
271 
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);
277 
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)
286 			{
287 				nrec = next;
288 				continue;
289 			}
290 
291 			querystring = build_querystring(nrec->data);
292 			if (querystring == NULL) {
293 				result = ISC_R_NOMEMORY;
294 				goto done;
295 			}
296 
297 			result = cd->putrr(lookup, nrec->type,
298 					   nrec->ttl, querystring);
299 			if (result != ISC_R_SUCCESS)
300 				goto done;
301 
302 			result = ISC_R_SUCCESS;
303 
304 			free(querystring);
305 			querystring = NULL;
306 		}
307 		nrec = next;
308 	}
309 
310 done:
311 	cd->zone = NULL;
312 	cd->record = NULL;
313 
314 	if (querystring != NULL)
315 		free(querystring);
316 
317 	return (result);
318 }
319 
320 isc_result_t
321 dlz_authority(const char *zone, void *dbdata, dns_sdlzlookup_t *lookup) {
322 	isc_result_t result;
323 	config_data_t *cd = (config_data_t *) dbdata;
324 	char *querystring = NULL;
325 	nrr_t *nrec;
326 	const char *p, *name = "@";
327 
328 	p = shortest_match(cd->zone_pattern, zone);
329 	if (p == NULL)
330 		return (ISC_R_NOTFOUND);
331 
332 	DE_CONST(p, cd->zone);
333 
334 	/* Write info message to log */
335 	cd->log(ISC_LOG_DEBUG(1),
336 		"dlz_wildcard_dynamic: authority for '%s'", zone);
337 
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;
344 
345 			querystring = build_querystring(nrec->data);
346 			if (querystring == NULL) {
347 				result = ISC_R_NOMEMORY;
348 				goto done;
349 			}
350 
351 			presult = cd->putrr(lookup, nrec->type,
352 					   nrec->ttl, querystring);
353 			if (presult != ISC_R_SUCCESS) {
354 				result = presult;
355 				goto done;
356 			}
357 
358 			result = ISC_R_SUCCESS;
359 
360 			free(querystring);
361 			querystring = NULL;
362 		}
363 		nrec = DLZ_LIST_NEXT(nrec, link);
364 	}
365 
366 done:
367 	cd->zone = NULL;
368 
369 	if (querystring != NULL)
370 		free(querystring);
371 
372 	return (result);
373 }
374 
375 static void
376 destroy_rrlist(config_data_t *cd) {
377 	nrr_t *trec, *nrec;
378 
379 	nrec = DLZ_LIST_HEAD(cd->rrs_list);
380 
381 	while (nrec != NULL) {
382 		trec = nrec;
383 
384 		destroy_querylist(&trec->data);
385 
386 		if (trec->name != NULL)
387 			free(trec->name);
388 		if (trec->type != NULL)
389 			free(trec->type);
390 		trec->name = trec->type = NULL;
391 
392 		/* Get the next record, before we destroy this one. */
393 		nrec = DLZ_LIST_NEXT(nrec, link);
394 
395 		free(trec);
396 	}
397 }
398 
399 isc_result_t
400 dlz_create(const char *dlzname, unsigned int argc, char *argv[],
401 	   void **dbdata, ...)
402 {
403 	config_data_t *cd;
404 	char *endp;
405 	int i, def_ttl;
406 	nrr_t *trec = NULL;
407 	isc_result_t result;
408 	const char *helper_name;
409 	va_list ap;
410 
411 	if (argc < 8 || argc % 4 != 0)
412 		return (ISC_R_FAILURE);
413 
414 	cd = calloc(1, sizeof(config_data_t));
415 	if (cd == NULL)
416 		return (ISC_R_NOMEMORY);
417 	memset(cd, 0, sizeof(config_data_t));
418 
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*));
423 	va_end(ap);
424 
425 	/*
426 	 * Write info message to log
427 	 */
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]);
432 
433 	/* initialize the records list here to simplify cleanup */
434 	DLZ_LIST_INIT(cd->rrs_list);
435 
436 	cd->zone_pattern = strdup(argv[1]);
437 	if (cd->zone_pattern == NULL)
438 		goto cleanup;
439 
440 	cd->axfr_pattern = strdup(argv[2]);
441 	if (cd->axfr_pattern == NULL)
442 		goto cleanup;
443 
444 	def_ttl = strtol(argv[3], &endp, 10);
445 	if (*endp != '\0' || def_ttl < 0) {
446 		def_ttl = 3600;
447 		cd->log(ISC_LOG_ERROR, "default TTL invalid, using 3600");
448 	}
449 
450 	for (i = 4; i < argc; i += 4) {
451 		result = ISC_R_NOMEMORY;
452 
453 		trec = malloc(sizeof(nrr_t));
454 		if (trec == NULL)
455 			goto full_cleanup;
456 
457 		memset(trec, 0, sizeof(nrr_t));
458 
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);
463 
464 		trec->name = strdup(argv[i]);
465 		if (trec->name == NULL)
466 			goto full_cleanup;
467 
468 		trec->type = strdup(argv[i + 2]);
469 		if (trec->type == NULL)
470 			goto full_cleanup;
471 
472 		trec->ttl = strtol(argv[i + 1], &endp, 10);
473 		if (argv[i + 1][0] == '\0' || *endp != '\0' || trec->ttl < 0)
474 			trec->ttl = def_ttl;
475 
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]",
483 				i + 3);
484 			goto full_cleanup;
485 		}
486 	}
487 
488 	*dbdata = cd;
489 
490 	return (ISC_R_SUCCESS);
491 
492 full_cleanup:
493 	destroy_rrlist(cd);
494 
495 cleanup:
496 	if (cd->zone_pattern != NULL)
497 		free(cd->zone_pattern);
498 	if (cd->axfr_pattern != NULL)
499 		free(cd->axfr_pattern);
500 	free(cd);
501 
502 	return (result);
503 }
504 
505 void
506 dlz_destroy(void *dbdata) {
507 	config_data_t *cd = (config_data_t *) dbdata;
508 
509 	/*
510 	 * Write debugging message to log
511 	 */
512 	cd->log(ISC_LOG_DEBUG(2), "Unloading DLZ_wildcard driver.");
513 
514 	destroy_rrlist(cd);
515 
516 	free(cd->zone_pattern);
517 	free(cd->axfr_pattern);
518 	free(cd);
519 }
520 
521 
522 /*
523  * Return the version of the API
524  */
525 int
526 dlz_version(unsigned int *flags) {
527 	UNUSED(flags);
528 	/* XXX: ok to set DNS_SDLZFLAG_THREADSAFE here? */
529 	return (DLZ_DLOPEN_VERSION);
530 }
531 
532 /*
533  * Register a helper function from the bind9 dlz_dlopen driver
534  */
535 static void
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;
545 }
546 
547 static const char *
548 shortest_match(const char *pattern, const char *string) {
549 	const char *p = string;
550 	if (pattern == NULL || p == NULL || *p == '\0')
551 		return (NULL);
552 
553 	p += strlen(p);
554 	while (p-- > string) {
555 		if (*p == '.') {
556 			if (fnmatch(pattern, p + 1, FNM_CASEFOLD) == 0)
557 				return (p + 1);
558 		}
559 	}
560 	if (fnmatch(pattern, string, FNM_CASEFOLD) == 0)
561 		return (string);
562 
563 	return (NULL);
564 }
565 
566 /*
567  * The helper functions stolen from the FreeBSD kernel (sys/libkern/fnmatch.c).
568  *
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
572  * always ASCII.
573  */
574 #define	EOS	'\0'
575 
576 #define RANGE_MATCH     1
577 #define RANGE_NOMATCH   0
578 #define RANGE_ERROR     (-1)
579 
580 static int
581 fnmatch(const char *pattern, const char *string, int flags) {
582 	const char *stringstart;
583 	char *newp;
584 	char c, test;
585 
586 	for (stringstart = string;;)
587 		switch (c = *pattern++) {
588 		case EOS:
589 			if ((flags & FNM_LEADING_DIR) && *string == '/')
590 				return (0);
591 			return (*string == EOS ? 0 : FNM_NOMATCH);
592 		case '?':
593 			if (*string == EOS)
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);
601 			++string;
602 			break;
603 		case '*':
604 			c = *pattern;
605 			/* Collapse multiple stars. */
606 			while (c == '*')
607 				c = *++pattern;
608 
609 			if (*string == '.' && (flags & FNM_PERIOD) &&
610 			    (string == stringstart ||
611 			    ((flags & FNM_PATHNAME) && *(string - 1) == '/')))
612 				return (FNM_NOMATCH);
613 
614 			/* Optimize for pattern with * at end or before /. */
615 			if (c == EOS)
616 				if (flags & FNM_PATHNAME)
617 					return ((flags & FNM_LEADING_DIR) ||
618 					    index(string, '/') == NULL ?
619 					    0 : FNM_NOMATCH);
620 				else
621 					return (0);
622 			else if (c == '/' && flags & FNM_PATHNAME) {
623 				if ((string = index(string, '/')) == NULL)
624 					return (FNM_NOMATCH);
625 				break;
626 			}
627 
628 			/* General case, use recursion. */
629 			while ((test = *string) != EOS) {
630 				if (!fnmatch(pattern, string,
631 					     flags & ~FNM_PERIOD))
632 					return (0);
633 				if (test == '/' && flags & FNM_PATHNAME)
634 					break;
635 				++string;
636 			}
637 			return (FNM_NOMATCH);
638 		case '[':
639 			if (*string == EOS)
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);
647 
648 			switch (rangematch(pattern, *string, flags, &newp)) {
649 			case RANGE_ERROR:
650 				goto norm;
651 			case RANGE_MATCH:
652 				pattern = newp;
653 				break;
654 			case RANGE_NOMATCH:
655 				return (FNM_NOMATCH);
656 			}
657 			++string;
658 			break;
659 		case '\\':
660 			if (!(flags & FNM_NOESCAPE)) {
661 				if ((c = *pattern++) == EOS) {
662 					c = '\\';
663 					--pattern;
664 				}
665 			}
666 			/* FALLTHROUGH */
667 		default:
668 		norm:
669 			if (c == *string)
670 				;
671 			else if ((flags & FNM_CASEFOLD) &&
672 				 (tolower((unsigned char)c) ==
673 				  tolower((unsigned char)*string)))
674 				;
675 			else
676 				return (FNM_NOMATCH);
677 			string++;
678 			break;
679 		}
680 	/* NOTREACHED */
681 }
682 
683 static int
684 rangematch(const char *pattern, char test, int flags, char **newp) {
685 	int negate, ok;
686 	char c, c2;
687 
688 	/*
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)
694 	 */
695 	if ( (negate = (*pattern == '!' || *pattern == '^')) )
696 		++pattern;
697 
698 	if (flags & FNM_CASEFOLD)
699 		test = tolower((unsigned char)test);
700 
701 	/*
702 	 * A right bracket shall lose its special meaning and represent
703 	 * itself in a bracket expression if it occurs first in the list.
704 	 * -- POSIX.2 2.8.3.2
705 	 */
706 	ok = 0;
707 	c = *pattern++;
708 	do {
709 		if (c == '\\' && !(flags & FNM_NOESCAPE))
710 			c = *pattern++;
711 		if (c == EOS)
712 			return (RANGE_ERROR);
713 
714 		if (c == '/' && (flags & FNM_PATHNAME))
715 			return (RANGE_NOMATCH);
716 
717 		if (flags & FNM_CASEFOLD)
718 			c = tolower((unsigned char)c);
719 
720 		if (*pattern == '-'
721 		    && (c2 = *(pattern+1)) != EOS && c2 != ']') {
722 			pattern += 2;
723 			if (c2 == '\\' && !(flags & FNM_NOESCAPE))
724 				c2 = *pattern++;
725 			if (c2 == EOS)
726 				return (RANGE_ERROR);
727 
728 			if (flags & FNM_CASEFOLD)
729 				c2 = tolower((unsigned char)c2);
730 
731 			if (c <= test && test <= c2)
732 				ok = 1;
733 		} else if (c == test)
734 			ok = 1;
735 	} while ((c = *pattern++) != ']');
736 
737 	*newp = (char *)(uintptr_t)pattern;
738 	return (ok == negate ? RANGE_NOMATCH : RANGE_MATCH);
739 }
740