1 /*	$NetBSD: dlz_filesystem_dynamic.c,v 1.1.1.3 2014/12/10 03:34:31 christos Exp $	*/
2 
3 /*
4  * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the
8  * above copyright notice and this permission notice appear in all
9  * copies.
10  *
11  * THE SOFTWARE IS PROVIDED "AS IS" AND STICHTING NLNET
12  * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
13  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
14  * STICHTING NLNET BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
15  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
16  * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
17  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
18  * USE OR PERFORMANCE OF THIS SOFTWARE.
19  *
20  * The development of Dynamically Loadable Zones (DLZ) for Bind 9 was
21  * conceived and contributed by Rob Butler.
22  *
23  * Permission to use, copy, modify, and distribute this software for any
24  * purpose with or without fee is hereby granted, provided that the
25  * above copyright notice and this permission notice appear in all
26  * copies.
27  *
28  * THE SOFTWARE IS PROVIDED "AS IS" AND ROB BUTLER
29  * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
30  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
31  * ROB BUTLER BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
32  * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
33  * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
34  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
35  * USE OR PERFORMANCE OF THIS SOFTWARE.
36  */
37 
38 /*
39  * Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
40  * Copyright (C) 1999-2001  Internet Software Consortium.
41  *
42  * Permission to use, copy, modify, and/or distribute this software for any
43  * purpose with or without fee is hereby granted, provided that the above
44  * copyright notice and this permission notice appear in all copies.
45  *
46  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
47  * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
48  * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
49  * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
50  * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
51  * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
52  * PERFORMANCE OF THIS SOFTWARE.
53  */
54 
55 /*
56  * This provides the externally loadable filesystem DLZ module, without
57  * update support
58  */
59 
60 #include <stdio.h>
61 #include <string.h>
62 #include <stdarg.h>
63 #include <stdint.h>
64 #include <stdlib.h>
65 
66 #include <sys/stat.h>
67 
68 #include "dlz_minimal.h"
69 #include "dlz_list.h"
70 #include "dir.h"
71 
72 typedef struct config_data {
73 	char		*basedir;
74 	int		basedirsize;
75 	char		*datadir;
76 	int		datadirsize;
77 	char		*xfrdir;
78 	int		xfrdirsize;
79 	int		splitcnt;
80 	char		separator;
81 	char		pathsep;
82 
83 	/* Helper functions from the dlz_dlopen driver */
84 	log_t *log;
85 	dns_sdlz_putrr_t *putrr;
86 	dns_sdlz_putnamedrr_t *putnamedrr;
87 	dns_dlz_writeablezone_t *writeable_zone;
88 } config_data_t;
89 
90 typedef struct dir_entry dir_entry_t;
91 
92 struct dir_entry {
93 	char dirpath[DIR_PATHMAX];
94 	DLZ_LINK(dir_entry_t)	link;
95 };
96 
97 typedef DLZ_LIST(dir_entry_t) dlist_t;
98 
99 /* forward reference */
100 
101 static void
102 b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr);
103 
104 /*
105  * Private methods
106  */
107 static isc_boolean_t
108 is_safe(const char *input) {
109 	unsigned int i;
110 	unsigned int len = strlen(input);
111 
112         /* check that only allowed characters are in the domain name */
113 	for (i = 0; i < len; i++) {
114 		/* '.' is allowed, but has special requirements */
115 		if (input[i] == '.') {
116 			/* '.' is not allowed as first char */
117 			if (i == 0)
118 				return (ISC_FALSE);
119 			/* '..', two dots together is not allowed. */
120 			else if (input[i-1] == '.')
121 				return (ISC_FALSE);
122 			/* '.' is not allowed as last char */
123 			if (i == len)
124 				return (ISC_FALSE);
125 			/* only 1 dot in ok location, continue at next char */
126 			continue;
127 		}
128 		/* '-' is allowed, continue at next char */
129 		if (input[i] == '-')
130 			continue;
131 		/* 0-9 is allowed, continue at next char */
132 		if (input[i] >= '0' && input[i] <= '9')
133 			continue;
134 		/* A-Z uppercase is allowed, continue at next char */
135 		if (input[i] >= 'A' && input[i] <= 'Z')
136 			continue;
137 		/* a-z lowercase is allowed, continue at next char */
138 		if (input[i] >= 'a' && input[i] <= 'z')
139 			continue;
140 
141 		/*
142 		 * colon needs to be allowed for IPV6 client
143 		 * addresses.  Not dangerous in domain names, as not a
144 		 * special char.
145 		 */
146 		if (input[i] == ':')
147 			continue;
148 
149 		/*
150 		 * '@' needs to be allowed for in zone data.  Not
151 		 * dangerous in domain names, as not a special char.
152 		 */
153 		if (input[i] == '@')
154 			continue;
155 
156 		/*
157 		 * if we reach this point we have encountered a
158 		 * disallowed char!
159 		 */
160 		return (ISC_FALSE);
161 	}
162         /* everything ok. */
163 	return (ISC_TRUE);
164 }
165 
166 static isc_result_t
167 create_path_helper(char *out, const char *in, config_data_t *cd) {
168 	char *tmpString;
169 	char *tmpPtr;
170 	int i;
171 
172 	tmpString = strdup(in);
173 	if (tmpString == NULL)
174 		return (ISC_R_NOMEMORY);
175 
176 	/*
177 	 * don't forget is_safe guarantees '.' will NOT be the
178 	 * first/last char
179 	 */
180 	while ((tmpPtr = strrchr(tmpString, '.')) != NULL) {
181 		i = 0;
182 		while (tmpPtr[i+1] != '\0') {
183 			if (cd->splitcnt < 1)
184 				strcat(out, (char *) &tmpPtr[i+1]);
185 			else
186 				strncat(out, (char *) &tmpPtr[i+1],
187 					cd->splitcnt);
188 			strncat(out, (char *) &cd->pathsep, 1);
189 			if (cd->splitcnt == 0)
190 				break;
191 			if (strlen((char *) &tmpPtr[i+1]) <=
192 			    (unsigned int) cd->splitcnt)
193 				break;
194 			i += cd->splitcnt;
195 		}
196 		tmpPtr[0] = '\0';
197 	}
198 
199 	/* handle the "first" label properly */
200 	i=0;
201 	tmpPtr = tmpString;
202 	while (tmpPtr[i] != '\0') {
203 		if (cd->splitcnt < 1)
204 			strcat(out, (char *) &tmpPtr[i]);
205 		else
206 			strncat(out, (char *) &tmpPtr[i], cd->splitcnt);
207 		strncat(out, (char *) &cd->pathsep, 1);
208 		if (cd->splitcnt == 0)
209 			break;
210 		if (strlen((char *) &tmpPtr[i]) <=
211 		    (unsigned int) cd->splitcnt)
212 			break;
213 		i += cd->splitcnt;
214 	}
215 
216 	free(tmpString);
217 	return (ISC_R_SUCCESS);
218 }
219 
220 /*%
221  * Checks to make sure zone and host are safe.  If safe, then
222  * hashes zone and host strings to build a path.  If zone / host
223  * are not safe an error is returned.
224  */
225 
226 static isc_result_t
227 create_path(const char *zone, const char *host, const char *client,
228 	    config_data_t *cd, char **path)
229 {
230 
231 	char *tmpPath;
232 	int pathsize;
233 	int len;
234 	isc_result_t result;
235 	isc_boolean_t isroot = ISC_FALSE;
236 
237 	/* special case for root zone */
238 	if (strcmp(zone, ".") == 0)
239 		isroot = ISC_TRUE;
240 
241 	/* if the requested zone is "unsafe", return error */
242 	if (!isroot && !is_safe(zone))
243 		return (ISC_R_FAILURE);
244 
245 	/* if host was passed, verify that it is safe */
246 	if (host != NULL && !is_safe(host))
247 		return (ISC_R_FAILURE);
248 
249 	/* if client was passed, verify that it is safe */
250 	if (client != NULL && !is_safe(client))
251 		return (ISC_R_FAILURE);
252 
253 	/* Determine how much memory the split up string will require */
254 	if (host != NULL)
255 		len = strlen(zone) + strlen(host);
256 	else if (client != NULL)
257 		len = strlen(zone) + strlen(client);
258 	else
259 		len = strlen(zone);
260 
261 	/*
262 	 * even though datadir and xfrdir will never be in the same
263 	 * string we only waste a few bytes by allocating for both,
264 	 * and then we are safe from buffer overruns.
265 	 */
266 	pathsize = len + cd->basedirsize +
267 		   cd->datadirsize + cd->xfrdirsize + 4;
268 
269 	/* if we are splitting names, we will need extra space. */
270 	if (cd->splitcnt > 0)
271 		pathsize += len/cd->splitcnt;
272 
273 	tmpPath = malloc(pathsize * sizeof(char));
274 	if (tmpPath == NULL) {
275 		/* write error message */
276 		cd->log(ISC_LOG_ERROR,
277 			"Filesystem driver unable to "
278 			"allocate memory in create_path().");
279 		result = ISC_R_NOMEMORY;
280 		goto cleanup_mem;
281 	}
282 
283 	/*
284 	 * build path string.
285 	 * start out with base directory.
286 	 */
287 	strcpy(tmpPath, cd->basedir);
288 
289 	/* add zone name - parsed properly */
290 	if (!isroot) {
291 		result = create_path_helper(tmpPath, zone, cd);
292 		if (result != ISC_R_SUCCESS)
293 			goto cleanup_mem;
294 	}
295 
296 	/*
297 	 * When neither client or host is passed we are building a
298 	 * path to see if a zone is supported.  We require that a zone
299 	 * path have the "data dir" directory contained within it so
300 	 * that we know this zone is really supported.  Otherwise,
301 	 * this zone may not really be supported because we are
302 	 * supporting a delagated sub zone.
303 	 *
304 	 * Example:
305 	 *
306 	 * We are supporting long.domain.com and using a splitcnt of
307 	 * 0.  the base dir is "/base-dir/" and the data dir is
308 	 * "/.datadir" We want to see if we are authoritative for
309 	 * domain.com.  Path /base-dir/com/domain/.datadir since
310 	 * /base-dir/com/domain/.datadir does not exist, we are not
311 	 * authoritative for the domain "domain.com".  However we are
312 	 * authoritative for the domain "long.domain.com" because the
313 	 * path /base-dir/com/domain/long/.datadir does exist!
314 	 */
315 
316 	/* if client is passed append xfr dir, otherwise append data dir */
317 	if (client != NULL) {
318 		strcat(tmpPath, cd->xfrdir);
319 		strncat(tmpPath, (char *) &cd->pathsep, 1);
320 		strcat(tmpPath, client);
321 	} else
322 		strcat(tmpPath, cd->datadir);
323 
324 	/* if host not null, add it. */
325 	if (host != NULL) {
326 		strncat(tmpPath, (char *) &cd->pathsep, 1);
327 		result = create_path_helper(tmpPath, host, cd);
328 		if (result != ISC_R_SUCCESS)
329 			goto cleanup_mem;
330 	}
331 
332 	/* return the path we built. */
333 	*path = tmpPath;
334 
335 	/* return success */
336 	result = ISC_R_SUCCESS;
337 
338  cleanup_mem:
339 	/* cleanup memory */
340 
341 	/* free tmpPath memory */
342 	if (tmpPath != NULL && result != ISC_R_SUCCESS)
343 		free(tmpPath);
344 
345 	return (result);
346 }
347 
348 static isc_result_t
349 process_dir(dir_t *dir, void *passback, config_data_t *cd,
350 	    dlist_t *dir_list, unsigned int basedirlen)
351 {
352 
353 	char tmp[DIR_PATHMAX + DIR_NAMEMAX];
354 	int astPos;
355 	struct stat	sb;
356 	isc_result_t result = ISC_R_FAILURE;
357 	char *endp;
358 	char *type;
359 	char *ttlStr;
360 	char *data;
361 	char host[DIR_NAMEMAX];
362 	char *tmpString;
363 	char *tmpPtr;
364 	int ttl;
365 	int i;
366 	int len;
367 	dir_entry_t *direntry;
368 	isc_boolean_t foundHost;
369 
370 	tmp[0] = '\0'; /* set 1st byte to '\0' so strcpy works right. */
371 	host[0] = '\0';
372 	foundHost = ISC_FALSE;
373 
374 	/* copy base directory name to tmp. */
375 	strcpy(tmp, dir->dirname);
376 
377 	/* dir->dirname will always have '*' as the last char. */
378 	astPos = strlen(dir->dirname) - 1;
379 
380 	/* if dir_list != NULL, were are performing a zone xfr */
381 	if (dir_list != NULL) {
382 		/* if splitcnt == 0, determine host from path. */
383 		if (cd->splitcnt == 0) {
384 			if (strlen(tmp) - 3 > basedirlen) {
385 				tmp[astPos-1] = '\0';
386 				tmpString = (char *) &tmp[basedirlen+1];
387 				/* handle filesystem's special wildcard "-"  */
388 				if (strcmp(tmpString, "-") == 0) {
389 					strcpy(host, "*");
390 				} else {
391 					/*
392 					 * not special wildcard -- normal name
393 					 */
394 					while ((tmpPtr = strrchr(tmpString,
395 								 cd->pathsep))
396 					       != NULL)
397 					{
398 						if ((strlen(host) +
399 						     strlen(tmpPtr + 1) + 2)
400 						    > DIR_NAMEMAX)
401 							continue;
402 						strcat(host, tmpPtr + 1);
403 						strcat(host, ".");
404 						tmpPtr[0] = '\0';
405 					}
406 					if ((strlen(host) +
407 					     strlen(tmpString) + 1)
408 					    <= DIR_NAMEMAX)
409 						strcat(host, tmpString);
410 				}
411 
412 				foundHost = ISC_TRUE;
413 				/* set tmp again for use later */
414 				strcpy(tmp, dir->dirname);
415 			}
416 		} else {
417 			/*
418 			 * if splitcnt != 0 determine host from
419 			 * ".host" directory entry
420 			 */
421 			while (dir_read(dir) == ISC_R_SUCCESS) {
422 				if (strncasecmp(".host",
423 						dir->entry.name, 5) == 0) {
424 					/*
425 					 * handle filesystem's special
426 					 * wildcard "-"
427 					 */
428 					if (strcmp((char *) &dir->entry.name[6],
429 						   "-") == 0)
430 						strcpy(host, "*");
431 					else {
432 						strncpy(host,
433 						   (char *) &dir->entry.name[6],
434 						   sizeof(host) - 1);
435 						host[255] = '\0';
436 					}
437 					foundHost = ISC_TRUE;
438 					break;
439 				}
440 			}
441 			/* reset dir list for use later */
442 			dir_reset(dir);
443 		} /* end of else */
444 	}
445 
446 	while (dir_read(dir) == ISC_R_SUCCESS) {
447 		cd->log(ISC_LOG_DEBUG(1),
448 			"Filesystem driver Dir name:"
449 			" '%s' Dir entry: '%s'\n",
450 			dir->dirname, dir->entry.name);
451 
452 		/* skip any entries starting with "." */
453 		if (dir->entry.name[0] == '.')
454 			continue;
455 
456 		/*
457 		 * get rid of '*', set to NULL.  Effectively trims
458 		 * string from previous loop to base directory only
459 		 * while still leaving memory for concat to be
460 		 * performed next.
461 		 */
462 
463 		tmp[astPos] = '\0';
464 
465 		/* add name to base directory name. */
466 		strcat(tmp, dir->entry.name);
467 
468 		/* make sure we can stat entry */
469 		if (stat(tmp, &sb) == 0 ) {
470 			/* if entry is a directory */
471 			if ((sb.st_mode & S_IFDIR) != 0) {
472 				/*
473 				 * if dir list is NOT NULL, add dir to
474 				 * dir list
475 				 */
476 				if (dir_list != NULL) {
477 					direntry = malloc(sizeof(dir_entry_t));
478 					if (direntry == NULL)
479 						return (ISC_R_NOMEMORY);
480 					strcpy(direntry->dirpath, tmp);
481 					DLZ_LINK_INIT(direntry, link);
482 					DLZ_LIST_APPEND(*dir_list, direntry,
483 							link);
484 					result = ISC_R_SUCCESS;
485 				}
486 				continue;
487 
488 				/*
489 				 * if entry is a file be sure we do
490 				 * not add entry to DNS results if we
491 				 * are performing a zone xfr and we
492 				 * could not find a host entry.
493 				 */
494 
495 			} else if (dir_list != NULL &&
496 				   foundHost == ISC_FALSE) {
497 				continue;
498 			}
499 		} else /* if we cannot stat entry, skip it. */
500 			continue;
501 
502 		type = dir->entry.name;
503 		ttlStr = strchr(type,  cd->separator);
504 		if (ttlStr == NULL) {
505 			cd->log(ISC_LOG_ERROR,
506 				"Filesystem driver: "
507 				"%s could not be parsed properly", tmp);
508 			return (ISC_R_FAILURE);
509 		}
510 
511 		/* replace separator char with NULL to split string */
512 		ttlStr[0] = '\0';
513 		/* start string after NULL of previous string */
514 		ttlStr = (char *) &ttlStr[1];
515 
516 		data = strchr(ttlStr, cd->separator);
517 		if (data == NULL) {
518 			cd->log(ISC_LOG_ERROR,
519 				"Filesystem driver: "
520 				"%s could not be parsed properly", tmp);
521 			return (ISC_R_FAILURE);
522 		}
523 
524 		/* replace separator char with NULL to split string */
525 		data[0] = '\0';
526 
527 		/* start string after NULL of previous string */
528 		data = (char *) &data[1];
529 
530 		/* replace all cd->separator chars with a space. */
531 		len = strlen(data);
532 
533 		for (i=0; i < len; i++) {
534 			if (data[i] == cd->separator)
535 				data[i] = ' ';
536 		}
537 
538 		/* convert text to int, make sure it worked right */
539 		ttl = strtol(ttlStr, &endp, 10);
540 		if (*endp != '\0' || ttl < 0)
541 			cd->log(ISC_LOG_ERROR,
542 				"Filesystem driver "
543 				"ttl must be a postive number");
544 
545 		/* pass data back to Bind */
546 		if (dir_list == NULL)
547 			result = cd->putrr((dns_sdlzlookup_t *) passback,
548 					   type, ttl, data);
549 		else
550 			result = cd->putnamedrr((dns_sdlzallnodes_t *) passback,
551 						(char *) host,
552 						type, ttl, data);
553 
554 		/* if error, return error right away */
555 		if (result != ISC_R_SUCCESS)
556 			return (result);
557 	} /* end of while loop */
558 
559 	return (result);
560 }
561 
562 /*
563  * DLZ methods
564  */
565 isc_result_t
566 dlz_allowzonexfr(void *dbdata, const char *name, const char *client) {
567 	isc_result_t result;
568 	char *path;
569 	struct stat	sb;
570 	config_data_t *cd;
571 	path = NULL;
572 
573 	cd = (config_data_t *) dbdata;
574 
575 	if (create_path(name, NULL, client, cd, &path) != ISC_R_SUCCESS) {
576 		return (ISC_R_NOTFOUND);
577 	}
578 
579 	if (stat(path, &sb) != 0) {
580 		result = ISC_R_NOTFOUND;
581 		goto complete_AXFR;
582 	}
583 
584 	if ((sb.st_mode & S_IFREG) != 0) {
585 		result = ISC_R_SUCCESS;
586 		goto complete_AXFR;
587 	}
588 
589 	result = ISC_R_NOTFOUND;
590 
591  complete_AXFR:
592 	free(path);
593 	return (result);
594 }
595 
596 isc_result_t
597 dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) {
598 	isc_result_t result;
599 	dlist_t *dir_list;
600 	config_data_t *cd = (config_data_t *) dbdata;
601 	char *basepath;
602 	unsigned int basepathlen;
603 	struct stat	sb;
604 	dir_t dir;
605 	dir_entry_t *dir_entry;
606 	dir_entry_t *next_de;
607 
608 	basepath = NULL;
609 	dir_list = NULL;
610 
611 	/* allocate memory for list */
612 	dir_list = malloc(sizeof(dlist_t));
613 	if (dir_list == NULL) {
614 		result = ISC_R_NOTFOUND;
615 		goto complete_allnds;
616 	}
617 
618 	/* initialize list */
619 	DLZ_LIST_INIT(*dir_list);
620 
621 	if (create_path(zone, NULL, NULL, cd, &basepath) != ISC_R_SUCCESS) {
622 		return (ISC_R_NOTFOUND);
623 	}
624 
625 	/* remove path separator at end of path so stat works properly */
626 	basepathlen = strlen(basepath);
627 
628 	if (stat(basepath, &sb) != 0) {
629 		result = ISC_R_NOTFOUND;
630 		goto complete_allnds;
631 	}
632 
633 	if ((sb.st_mode & S_IFDIR) == 0) {
634 		result = ISC_R_NOTFOUND;
635 		goto complete_allnds;
636 	}
637 
638 	/* initialize and open directory */
639 	dir_init(&dir);
640 	result = dir_open(&dir, basepath);
641 
642 	/* if directory open failed, return error. */
643 	if (result != ISC_R_SUCCESS) {
644 		cd->log(ISC_LOG_ERROR,
645 			"Unable to open %s directory to read entries.",
646 			basepath);
647 		result = ISC_R_FAILURE;
648 		goto complete_allnds;
649 	}
650 
651 	/* process the directory */
652 	result = process_dir(&dir, allnodes, cd, dir_list, basepathlen);
653 
654 	/* close the directory */
655 	dir_close(&dir);
656 
657 	if (result != ISC_R_SUCCESS)
658 		goto complete_allnds;
659 
660 	/* get first dir entry from list. */
661 	dir_entry = DLZ_LIST_HEAD(*dir_list);
662 	while (dir_entry != NULL) {
663 		result = dir_open(&dir, dir_entry->dirpath);
664 		/* if directory open failed, return error. */
665 		if (result != ISC_R_SUCCESS) {
666 			cd->log(ISC_LOG_ERROR,
667 				"Unable to open %s "
668 				"directory to read entries.", basepath);
669 			result = ISC_R_FAILURE;
670 			goto complete_allnds;
671 		}
672 
673 		/* process the directory */
674 		result = process_dir(&dir, allnodes, cd, dir_list, basepathlen);
675 
676 		/* close the directory */
677 		dir_close(&dir);
678 
679 		if (result != ISC_R_SUCCESS)
680 			goto complete_allnds;
681 
682 		dir_entry = DLZ_LIST_NEXT(dir_entry, link);
683 	} /* end while */
684 
685  complete_allnds:
686 	if (dir_list != NULL) {
687 		/* clean up entries from list. */
688 		dir_entry = DLZ_LIST_HEAD(*dir_list);
689 		while (dir_entry != NULL) {
690 			next_de = DLZ_LIST_NEXT(dir_entry, link);
691 			free(dir_entry);
692 			dir_entry = next_de;
693 		} /* end while */
694 		free(dir_list);
695 	}
696 
697 	if (basepath != NULL)
698 		free(basepath);
699 
700 	return (result);
701 }
702 
703 #if DLZ_DLOPEN_VERSION < 3
704 isc_result_t
705 dlz_findzonedb(void *dbdata, const char *name)
706 #else
707 isc_result_t
708 dlz_findzonedb(void *dbdata, const char *name,
709 	       dns_clientinfomethods_t *methods,
710 	       dns_clientinfo_t *clientinfo)
711 #endif
712 {
713 
714 	isc_result_t result;
715 	config_data_t *cd = (config_data_t *) dbdata;
716 	char *path;
717 	struct stat sb;
718 	path = NULL;
719 
720 #if DLZ_DLOPEN_VERSION >= 3
721 	UNUSED(methods);
722 	UNUSED(clientinfo);
723 #endif
724 
725 	if (create_path(name, NULL, NULL, cd, &path) != ISC_R_SUCCESS)
726 		return (ISC_R_NOTFOUND);
727 
728 	cd->log(ISC_LOG_DEBUG(1),
729 		"Filesystem driver Findzone() Checking for path: '%s'\n", path);
730 
731 	if (stat(path, &sb) != 0) {
732 		result = ISC_R_NOTFOUND;
733 		goto complete_FZ;
734 	}
735 
736 	if ((sb.st_mode & S_IFDIR) != 0) {
737 		result = ISC_R_SUCCESS;
738 		goto complete_FZ;
739 	}
740 
741 	result = ISC_R_NOTFOUND;
742 
743  complete_FZ:
744 
745 	free(path);
746 	return (result);
747 }
748 
749 #if DLZ_DLOPEN_VERSION == 1
750 isc_result_t
751 dlz_lookup(const char *zone, const char *name,
752 	   void *dbdata, dns_sdlzlookup_t *lookup)
753 #else
754 isc_result_t
755 dlz_lookup(const char *zone, const char *name,
756 	   void *dbdata, dns_sdlzlookup_t *lookup,
757 	   dns_clientinfomethods_t *methods,
758 	   dns_clientinfo_t *clientinfo)
759 #endif
760 {
761 	isc_result_t result = ISC_R_NOTFOUND;
762 	config_data_t *cd = (config_data_t *) dbdata;
763 	char *path;
764 	struct stat sb;
765 	dir_t dir;
766 	path = NULL;
767 
768 	UNUSED(lookup);
769 #if DLZ_DLOPEN_VERSION >= 2
770 	UNUSED(methods);
771 	UNUSED(clientinfo);
772 #endif
773 
774 	if (strcmp(name, "*") == 0)
775 		/*
776 		 * handle filesystem's special wildcard "-"
777 		 */
778 		result = create_path(zone, "-", NULL, cd, &path);
779 	else
780 		result = create_path(zone, name, NULL, cd, &path);
781 
782 	if (result != ISC_R_SUCCESS)
783 		return (ISC_R_NOTFOUND);
784 
785 	/* remove path separator at end of path so stat works properly */
786 	path[strlen(path)-1] = '\0';
787 
788 	cd->log(ISC_LOG_DEBUG(1),
789 		"Filesystem driver lookup() Checking for path: '%s'\n", path);
790 
791 	if (stat(path, &sb) != 0) {
792 		result = ISC_R_NOTFOUND;
793 		goto complete_lkup;
794 	}
795 
796 	if ((sb.st_mode & S_IFDIR) == 0) {
797 		result = ISC_R_NOTFOUND;
798 		goto complete_lkup;
799 	}
800 
801 	/* initialize and open directory */
802 	dir_init(&dir);
803 	result = dir_open(&dir, path);
804 
805 	/* if directory open failed, return error. */
806 	if (result != ISC_R_SUCCESS) {
807 		cd->log(ISC_LOG_ERROR,
808 			"Unable to open %s directory to read entries.", path);
809 		result = ISC_R_FAILURE;
810 		goto complete_lkup;
811 	}
812 
813 	/* process any records in the directory */
814 	result = process_dir(&dir, lookup, cd, NULL, 0);
815 
816 	/* close the directory */
817 	dir_close(&dir);
818 
819  complete_lkup:
820 
821 	free(path);
822 	return (result);
823 }
824 
825 isc_result_t
826 dlz_create(const char *dlzname, unsigned int argc, char *argv[],
827 	   void **dbdata, ...)
828 {
829 	config_data_t *cd;
830 	char *endp;
831 	int len;
832 	char pathsep;
833 	const char *helper_name;
834 	va_list ap;
835 
836 	UNUSED(dlzname);
837 
838 	/* allocate memory for our config data and helper functions */
839 	cd = calloc(1, sizeof(config_data_t));
840 	if (cd == NULL)
841 		goto no_mem;
842 
843 	/* zero the memory */
844 	memset(cd, 0, sizeof(config_data_t));
845 
846 	/* Fill in the helper functions */
847 	va_start(ap, dbdata);
848 	while ((helper_name = va_arg(ap, const char*)) != NULL)
849 		b9_add_helper(cd, helper_name, va_arg(ap, void*));
850 	va_end(ap);
851 
852 	/* we require 5 command line args. */
853 	if (argc != 6) {
854 		cd->log(ISC_LOG_ERROR,
855 			"Filesystem driver requires "
856 			"6 command line args.");
857 		return (ISC_R_FAILURE);
858 	}
859 
860 	if (strlen(argv[5]) > 1) {
861 		cd->log(ISC_LOG_ERROR,
862 			"Filesystem driver can only "
863 			"accept a single character for separator.");
864 		return (ISC_R_FAILURE);
865 	}
866 
867 	/* verify base dir ends with '/' or '\' */
868 	len = strlen(argv[1]);
869 	if (argv[1][len-1] != '\\' && argv[1][len-1] != '/') {
870 		cd->log(ISC_LOG_ERROR,
871 			"Base dir parameter for filesystem driver "
872 			"should end with %s",
873 			"either '/' or '\\' ");
874 		return (ISC_R_FAILURE);
875 	}
876 
877 	/* determine and save path separator for later */
878 	if (argv[1][len-1] == '\\')
879 		pathsep = '\\';
880 	else
881 		pathsep = '/';
882 
883 	cd->pathsep = pathsep;
884 
885 	/* get and store our base directory */
886 	cd->basedir = strdup(argv[1]);
887 	if (cd->basedir == NULL)
888 		goto no_mem;
889 	cd->basedirsize = strlen(cd->basedir);
890 
891 	/* get and store our data sub-dir */
892 	cd->datadir = strdup(argv[2]);
893 	if (cd->datadir == NULL)
894 		goto no_mem;
895 	cd->datadirsize = strlen(cd->datadir);
896 
897 	/* get and store our zone xfr sub-dir */
898 	cd->xfrdir = strdup(argv[3]);
899 	if (cd->xfrdir == NULL)
900 		goto no_mem;
901 	cd->xfrdirsize = strlen(cd->xfrdir);
902 
903 	/* get and store our directory split count */
904 	cd->splitcnt = strtol(argv[4], &endp, 10);
905 	if (*endp != '\0' || cd->splitcnt < 0)
906 		cd->log(ISC_LOG_ERROR,
907 			"Directory split count must be zero (0) "
908 			"or a postive number");
909 
910 	/* get and store our separator character */
911 	cd->separator = *argv[5];
912 
913 	/* pass back config data */
914 	*dbdata = cd;
915 
916 	/* return success */
917 	return (ISC_R_SUCCESS);
918 
919 	/* handle no memory error */
920  no_mem:
921 
922 	/* write error message */
923 	if (cd != NULL && cd->log != NULL)
924 		cd->log(ISC_LOG_ERROR,
925 			"filesystem_dynamic: Filesystem driver unable to "
926 			"allocate memory for config data.");
927 
928 	/* if we allocated a config data object clean it up */
929 	if (cd != NULL)
930 		dlz_destroy(cd);
931 
932 	/* return error */
933 	return (ISC_R_NOMEMORY);
934 }
935 
936 void
937 dlz_destroy(void *dbdata) {
938 	config_data_t *cd;
939 
940 	cd = (config_data_t *) dbdata;
941 
942 	/*
943 	 * free memory for each section of config data that was
944 	 * allocated
945 	 */
946 	if (cd->basedir != NULL)
947 		free(cd->basedir);
948 
949 	if (cd->datadir != NULL)
950 		free(cd->datadir);
951 
952 	if (cd->xfrdir != NULL)
953 		free(cd->xfrdir);
954 
955 	/* free config data memory */
956 	free(cd);
957 }
958 
959 /*
960  * Return the version of the API
961  */
962 int
963 dlz_version(unsigned int *flags) {
964 	UNUSED(flags);
965 	return (DLZ_DLOPEN_VERSION);
966 }
967 
968 /*
969  * Register a helper function from the bind9 dlz_dlopen driver
970  */
971 static void
972 b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr) {
973 	if (strcmp(helper_name, "log") == 0)
974 		cd->log = (log_t *)ptr;
975 	if (strcmp(helper_name, "putrr") == 0)
976 		cd->putrr = (dns_sdlz_putrr_t *)ptr;
977 	if (strcmp(helper_name, "putnamedrr") == 0)
978 		cd->putnamedrr = (dns_sdlz_putnamedrr_t *)ptr;
979 	if (strcmp(helper_name, "writeable_zone") == 0)
980 		cd->writeable_zone = (dns_dlz_writeablezone_t *)ptr;
981 }
982