text: /*
   * Copyright (C) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
   *
   * 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.
   *
   * 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 ROB BUTLER
   * DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
   * ROB BUTLER 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.
   */
  
  /*
   * Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
   * Copyright (C) 1999-2001  Internet Software Consortium.
   *
   * Permission to use, copy, modify, and/or 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 ISC DISCLAIMS ALL WARRANTIES WITH
   * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
   * AND FITNESS.  IN NO EVENT SHALL ISC 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 filesystem DLZ module, without
   * update support
   */
  
  #include <stdio.h>
  #include <string.h>
  #include <stdarg.h>
  #include <stdint.h>
  #include <stdlib.h>
  
  #include <sys/stat.h>
  
  #include "dlz_minimal.h"
  #include "dlz_list.h"
  #include "dir.h"
  
  typedef struct config_data {
  	char		*basedir;
  	int		basedirsize;
  	char		*datadir;
  	int		datadirsize;
  	char		*xfrdir;
  	int		xfrdirsize;
  	int		splitcnt;
  	char		separator;
  	char		pathsep;
  
  	/* 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;
  
  typedef struct dir_entry dir_entry_t;
  
  struct dir_entry {
  	char dirpath[DIR_PATHMAX];
  	DLZ_LINK(dir_entry_t)	link;
  };
  
  typedef DLZ_LIST(dir_entry_t) dlist_t;
  
  /* forward reference */
  
  static void
  b9_add_helper(struct config_data *cd, const char *helper_name, void *ptr);
  
  /*
   * Private methods
   */
  static isc_boolean_t
  is_safe(const char *input) {
  	unsigned int i;
  	unsigned int len = strlen(input);
  
          /* check that only allowed characters are in the domain name */
  	for (i = 0; i < len; i++) {
  		/* '.' is allowed, but has special requirements */
  		if (input[i] == '.') {
  			/* '.' is not allowed as first char */
  			if (i == 0)
  				return (ISC_FALSE);
  			/* '..', two dots together is not allowed. */
  			else if (input[i-1] == '.')
  				return (ISC_FALSE);
  			/* '.' is not allowed as last char */
  			if (i == len)
  				return (ISC_FALSE);
  			/* only 1 dot in ok location, continue at next char */
  			continue;
  		}
  		/* '-' is allowed, continue at next char */
  		if (input[i] == '-')
  			continue;
  		/* 0-9 is allowed, continue at next char */
  		if (input[i] >= '0' && input[i] <= '9')
  			continue;
  		/* A-Z uppercase is allowed, continue at next char */
  		if (input[i] >= 'A' && input[i] <= 'Z')
  			continue;
  		/* a-z lowercase is allowed, continue at next char */
  		if (input[i] >= 'a' && input[i] <= 'z')
  			continue;
  
  		/*
  		 * colon needs to be allowed for IPV6 client
  		 * addresses.  Not dangerous in domain names, as not a
  		 * special char.
  		 */
  		if (input[i] == ':')
  			continue;
  
  		/*
  		 * '@' needs to be allowed for in zone data.  Not
  		 * dangerous in domain names, as not a special char.
  		 */
  		if (input[i] == '@')
  			continue;
  
  		/*
  		 * if we reach this point we have encountered a
  		 * disallowed char!
  		 */
  		return (ISC_FALSE);
  	}
          /* everything ok. */
  	return (ISC_TRUE);
  }
  
  static isc_result_t
  create_path_helper(char *out, const char *in, config_data_t *cd) {
  	char *tmpString;
  	char *tmpPtr;
  	int i;
  
  	tmpString = strdup(in);
  	if (tmpString == NULL)
  		return (ISC_R_NOMEMORY);
  
  	/*
  	 * don't forget is_safe guarantees '.' will NOT be the
  	 * first/last char
  	 */
  	while ((tmpPtr = strrchr(tmpString, '.')) != NULL) {
  		i = 0;
  		while (tmpPtr[i+1] != '\0') {
  			if (cd->splitcnt < 1)
  				strcat(out, (char *) &tmpPtr[i+1]);
  			else
  				strncat(out, (char *) &tmpPtr[i+1],
  					cd->splitcnt);
  			strncat(out, (char *) &cd->pathsep, 1);
  			if (cd->splitcnt == 0)
  				break;
  			if (strlen((char *) &tmpPtr[i+1]) <=
  			    (unsigned int) cd->splitcnt)
  				break;
  			i += cd->splitcnt;
  		}
  		tmpPtr[0] = '\0';
  	}
  
  	/* handle the "first" label properly */
  	i=0;
  	tmpPtr = tmpString;
  	while (tmpPtr[i] != '\0') {
  		if (cd->splitcnt < 1)
  			strcat(out, (char *) &tmpPtr[i]);
  		else
  			strncat(out, (char *) &tmpPtr[i], cd->splitcnt);
  		strncat(out, (char *) &cd->pathsep, 1);
  		if (cd->splitcnt == 0)
  			break;
  		if (strlen((char *) &tmpPtr[i]) <=
  		    (unsigned int) cd->splitcnt)
  			break;
  		i += cd->splitcnt;
  	}
  
  	free(tmpString);
  	return (ISC_R_SUCCESS);
  }
  
  /*%
   * Checks to make sure zone and host are safe.  If safe, then
   * hashes zone and host strings to build a path.  If zone / host
   * are not safe an error is returned.
   */
  
  static isc_result_t
  create_path(const char *zone, const char *host, const char *client,
  	    config_data_t *cd, char **path)
  {
  
  	char *tmpPath;
  	int pathsize;
  	int len;
  	isc_result_t result;
  	isc_boolean_t isroot = ISC_FALSE;
  
  	/* special case for root zone */
  	if (strcmp(zone, ".") == 0)
  		isroot = ISC_TRUE;
  
  	/* if the requested zone is "unsafe", return error */
  	if (!isroot && !is_safe(zone))
  		return (ISC_R_FAILURE);
  
  	/* if host was passed, verify that it is safe */
  	if (host != NULL && !is_safe(host))
  		return (ISC_R_FAILURE);
  
  	/* if client was passed, verify that it is safe */
  	if (client != NULL && !is_safe(client))
  		return (ISC_R_FAILURE);
  
  	/* Determine how much memory the split up string will require */
  	if (host != NULL)
  		len = strlen(zone) + strlen(host);
  	else if (client != NULL)
  		len = strlen(zone) + strlen(client);
  	else
  		len = strlen(zone);
  
  	/*
  	 * even though datadir and xfrdir will never be in the same
  	 * string we only waste a few bytes by allocating for both,
  	 * and then we are safe from buffer overruns.
  	 */
  	pathsize = len + cd->basedirsize +
  		   cd->datadirsize + cd->xfrdirsize + 4;
  
  	/* if we are splitting names, we will need extra space. */
  	if (cd->splitcnt > 0)
  		pathsize += len/cd->splitcnt;
  
  	tmpPath = malloc(pathsize * sizeof(char));
  	if (tmpPath == NULL) {
  		/* write error message */
  		cd->log(ISC_LOG_ERROR,
  			"Filesystem driver unable to "
  			"allocate memory in create_path().");
  		result = ISC_R_NOMEMORY;
  		goto cleanup_mem;
  	}
  
  	/*
  	 * build path string.
  	 * start out with base directory.
  	 */
  	strcpy(tmpPath, cd->basedir);
  
  	/* add zone name - parsed properly */
  	if (!isroot) {
  		result = create_path_helper(tmpPath, zone, cd);
  		if (result != ISC_R_SUCCESS)
  			goto cleanup_mem;
  	}
  
  	/*
  	 * When neither client or host is passed we are building a
  	 * path to see if a zone is supported.  We require that a zone
  	 * path have the "data dir" directory contained within it so
  	 * that we know this zone is really supported.  Otherwise,
  	 * this zone may not really be supported because we are
  	 * supporting a delagated sub zone.
  	 *
  	 * Example:
  	 *
  	 * We are supporting long.domain.com and using a splitcnt of
  	 * 0.  the base dir is "/base-dir/" and the data dir is
  	 * "/.datadir" We want to see if we are authoritative for
  	 * domain.com.  Path /base-dir/com/domain/.datadir since
  	 * /base-dir/com/domain/.datadir does not exist, we are not
  	 * authoritative for the domain "domain.com".  However we are
  	 * authoritative for the domain "long.domain.com" because the
  	 * path /base-dir/com/domain/long/.datadir does exist!
  	 */
  
  	/* if client is passed append xfr dir, otherwise append data dir */
  	if (client != NULL) {
  		strcat(tmpPath, cd->xfrdir);
  		strncat(tmpPath, (char *) &cd->pathsep, 1);
  		strcat(tmpPath, client);
  	} else
  		strcat(tmpPath, cd->datadir);
  
  	/* if host not null, add it. */
  	if (host != NULL) {
  		strncat(tmpPath, (char *) &cd->pathsep, 1);
  		result = create_path_helper(tmpPath, host, cd);
  		if (result != ISC_R_SUCCESS)
  			goto cleanup_mem;
  	}
  
  	/* return the path we built. */
  	*path = tmpPath;
  
  	/* return success */
  	result = ISC_R_SUCCESS;
  
   cleanup_mem:
  	/* cleanup memory */
  
  	/* free tmpPath memory */
  	if (tmpPath != NULL && result != ISC_R_SUCCESS)
  		free(tmpPath);
  
  	return (result);
  }
  
  static isc_result_t
  process_dir(dir_t *dir, void *passback, config_data_t *cd,
  	    dlist_t *dir_list, unsigned int basedirlen)
  {
  
  	char tmp[DIR_PATHMAX + DIR_NAMEMAX];
  	int astPos;
  	struct stat	sb;
  	isc_result_t result = ISC_R_FAILURE;
  	char *endp;
  	char *type;
  	char *ttlStr;
  	char *data;
  	char host[DIR_NAMEMAX];
  	char *tmpString;
  	char *tmpPtr;
  	int ttl;
  	int i;
  	int len;
  	dir_entry_t *direntry;
  	isc_boolean_t foundHost;
  
  	tmp[0] = '\0'; /* set 1st byte to '\0' so strcpy works right. */
  	host[0] = '\0';
  	foundHost = ISC_FALSE;
  
  	/* copy base directory name to tmp. */
  	strcpy(tmp, dir->dirname);
  
  	/* dir->dirname will always have '*' as the last char. */
  	astPos = strlen(dir->dirname) - 1;
  
  	/* if dir_list != NULL, were are performing a zone xfr */
  	if (dir_list != NULL) {
  		/* if splitcnt == 0, determine host from path. */
  		if (cd->splitcnt == 0) {
  			if (strlen(tmp) - 3 > basedirlen) {
  				tmp[astPos-1] = '\0';
  				tmpString = (char *) &tmp[basedirlen+1];
  				/* handle filesystem's special wildcard "-"  */
  				if (strcmp(tmpString, "-") == 0) {
  					strcpy(host, "*");
  				} else {
  					/*
  					 * not special wildcard -- normal name
  					 */
  					while ((tmpPtr = strrchr(tmpString,
  								 cd->pathsep))
  					       != NULL)
  					{
  						if ((strlen(host) +
  						     strlen(tmpPtr + 1) + 2)
  						    > DIR_NAMEMAX)
  							continue;
  						strcat(host, tmpPtr + 1);
  						strcat(host, ".");
  						tmpPtr[0] = '\0';
  					}
  					if ((strlen(host) +
  					     strlen(tmpString) + 1)
  					    <= DIR_NAMEMAX)
  						strcat(host, tmpString);
  				}
  
  				foundHost = ISC_TRUE;
  				/* set tmp again for use later */
  				strcpy(tmp, dir->dirname);
  			}
  		} else {
  			/*
  			 * if splitcnt != 0 determine host from
  			 * ".host" directory entry
  			 */
  			while (dir_read(dir) == ISC_R_SUCCESS) {
  				if (strncasecmp(".host",
  						dir->entry.name, 5) == 0) {
  					/*
  					 * handle filesystem's special
  					 * wildcard "-"
  					 */
  					if (strcmp((char *) &dir->entry.name[6],
  						   "-") == 0)
  						strcpy(host, "*");
  					else {
  						strncpy(host,
  						   (char *) &dir->entry.name[6],
  						   sizeof(host) - 1);
  						host[255] = '\0';
  					}
  					foundHost = ISC_TRUE;
  					break;
  				}
  			}
  			/* reset dir list for use later */
  			dir_reset(dir);
  		} /* end of else */
  	}
  
  	while (dir_read(dir) == ISC_R_SUCCESS) {
  		cd->log(ISC_LOG_DEBUG(1),
  			"Filesystem driver Dir name:"
  			" '%s' Dir entry: '%s'\n",
  			dir->dirname, dir->entry.name);
  
  		/* skip any entries starting with "." */
  		if (dir->entry.name[0] == '.')
  			continue;
  
  		/*
  		 * get rid of '*', set to NULL.  Effectively trims
  		 * string from previous loop to base directory only
  		 * while still leaving memory for concat to be
  		 * performed next.
  		 */
  
  		tmp[astPos] = '\0';
  
  		/* add name to base directory name. */
  		strcat(tmp, dir->entry.name);
  
  		/* make sure we can stat entry */
  		if (stat(tmp, &sb) == 0 ) {
  			/* if entry is a directory */
  			if ((sb.st_mode & S_IFDIR) != 0) {
  				/*
  				 * if dir list is NOT NULL, add dir to
  				 * dir list
  				 */
  				if (dir_list != NULL) {
  					direntry = malloc(sizeof(dir_entry_t));
  					if (direntry == NULL)
  						return (ISC_R_NOMEMORY);
  					strcpy(direntry->dirpath, tmp);
  					DLZ_LINK_INIT(direntry, link);
  					DLZ_LIST_APPEND(*dir_list, direntry,
  							link);
  					result = ISC_R_SUCCESS;
  				}
  				continue;
  
  				/*
  				 * if entry is a file be sure we do
  				 * not add entry to DNS results if we
  				 * are performing a zone xfr and we
  				 * could not find a host entry.
  				 */
  
  			} else if (dir_list != NULL &&
  				   foundHost == ISC_FALSE) {
  				continue;
  			}
  		} else /* if we cannot stat entry, skip it. */
  			continue;
  
  		type = dir->entry.name;
  		ttlStr = strchr(type,  cd->separator);
  		if (ttlStr == NULL) {
  			cd->log(ISC_LOG_ERROR,
  				"Filesystem driver: "
  				"%s could not be parsed properly", tmp);
  			return (ISC_R_FAILURE);
  		}
  
  		/* replace separator char with NULL to split string */
  		ttlStr[0] = '\0';
  		/* start string after NULL of previous string */
  		ttlStr = (char *) &ttlStr[1];
  
  		data = strchr(ttlStr, cd->separator);
  		if (data == NULL) {
  			cd->log(ISC_LOG_ERROR,
  				"Filesystem driver: "
  				"%s could not be parsed properly", tmp);
  			return (ISC_R_FAILURE);
  		}
  
  		/* replace separator char with NULL to split string */
  		data[0] = '\0';
  
  		/* start string after NULL of previous string */
  		data = (char *) &data[1];
  
  		/* replace all cd->separator chars with a space. */
  		len = strlen(data);
  
  		for (i=0; i < len; i++) {
  			if (data[i] == cd->separator)
  				data[i] = ' ';
  		}
  
  		/* convert text to int, make sure it worked right */
  		ttl = strtol(ttlStr, &endp, 10);
  		if (*endp != '\0' || ttl < 0)
  			cd->log(ISC_LOG_ERROR,
  				"Filesystem driver "
  				"ttl must be a postive number");
  
  		/* pass data back to Bind */
  		if (dir_list == NULL)
  			result = cd->putrr((dns_sdlzlookup_t *) passback,
  					   type, ttl, data);
  		else
  			result = cd->putnamedrr((dns_sdlzallnodes_t *) passback,
  						(char *) host,
  						type, ttl, data);
  
  		/* if error, return error right away */
  		if (result != ISC_R_SUCCESS)
  			return (result);
  	} /* end of while loop */
  
  	return (result);
  }
  
  /*
   * DLZ methods
   */
  isc_result_t
  dlz_allowzonexfr(void *dbdata, const char *name, const char *client) {
  	isc_result_t result;
  	char *path;
  	struct stat	sb;
  	config_data_t *cd;
  	path = NULL;
  
  	cd = (config_data_t *) dbdata;
  
  	if (create_path(name, NULL, client, cd, &path) != ISC_R_SUCCESS) {
  		return (ISC_R_NOTFOUND);
  	}
  
  	if (stat(path, &sb) != 0) {
  		result = ISC_R_NOTFOUND;
  		goto complete_AXFR;
  	}
  
  	if ((sb.st_mode & S_IFREG) != 0) {
  		result = ISC_R_SUCCESS;
  		goto complete_AXFR;
  	}
  
  	result = ISC_R_NOTFOUND;
  
   complete_AXFR:
  	free(path);
  	return (result);
  }
  
  isc_result_t
  dlz_allnodes(const char *zone, void *dbdata, dns_sdlzallnodes_t *allnodes) {
  	isc_result_t result;
  	dlist_t *dir_list;
  	config_data_t *cd = (config_data_t *) dbdata;
  	char *basepath;
  	unsigned int basepathlen;
  	struct stat	sb;
  	dir_t dir;
  	dir_entry_t *dir_entry;
  	dir_entry_t *next_de;
  
  	basepath = NULL;
  	dir_list = NULL;
  
  	/* allocate memory for list */
  	dir_list = malloc(sizeof(dlist_t));
  	if (dir_list == NULL) {
  		result = ISC_R_NOTFOUND;
  		goto complete_allnds;
  	}
  
  	/* initialize list */
  	DLZ_LIST_INIT(*dir_list);
  
  	if (create_path(zone, NULL, NULL, cd, &basepath) != ISC_R_SUCCESS) {
  		return (ISC_R_NOTFOUND);
  	}
  
  	/* remove path separator at end of path so stat works properly */
  	basepathlen = strlen(basepath);
  
  	if (stat(basepath, &sb) != 0) {
  		result = ISC_R_NOTFOUND;
  		goto complete_allnds;
  	}
  
  	if ((sb.st_mode & S_IFDIR) == 0) {
  		result = ISC_R_NOTFOUND;
  		goto complete_allnds;
  	}
  
  	/* initialize and open directory */
  	dir_init(&dir);
  	result = dir_open(&dir, basepath);
  
  	/* if directory open failed, return error. */
  	if (result != ISC_R_SUCCESS) {
  		cd->log(ISC_LOG_ERROR,
  			"Unable to open %s directory to read entries.",
  			basepath);
  		result = ISC_R_FAILURE;
  		goto complete_allnds;
  	}
  
  	/* process the directory */
  	result = process_dir(&dir, allnodes, cd, dir_list, basepathlen);
  
  	/* close the directory */
  	dir_close(&dir);
  
  	if (result != ISC_R_SUCCESS)
  		goto complete_allnds;
  
  	/* get first dir entry from list. */
  	dir_entry = DLZ_LIST_HEAD(*dir_list);
  	while (dir_entry != NULL) {
  		result = dir_open(&dir, dir_entry->dirpath);
  		/* if directory open failed, return error. */
  		if (result != ISC_R_SUCCESS) {
  			cd->log(ISC_LOG_ERROR,
  				"Unable to open %s "
  				"directory to read entries.", basepath);
  			result = ISC_R_FAILURE;
  			goto complete_allnds;
  		}
  
  		/* process the directory */
  		result = process_dir(&dir, allnodes, cd, dir_list, basepathlen);
  
  		/* close the directory */
  		dir_close(&dir);
  
  		if (result != ISC_R_SUCCESS)
  			goto complete_allnds;
  
  		dir_entry = DLZ_LIST_NEXT(dir_entry, link);
  	} /* end while */
  
   complete_allnds:
  	if (dir_list != NULL) {
  		/* clean up entries from list. */
  		dir_entry = DLZ_LIST_HEAD(*dir_list);
  		while (dir_entry != NULL) {
  			next_de = DLZ_LIST_NEXT(dir_entry, link);
  			free(dir_entry);
  			dir_entry = next_de;
  		} /* end while */
  		free(dir_list);
  	}
  
  	if (basepath != NULL)
  		free(basepath);
  
  	return (result);
  }
  
  #if DLZ_DLOPEN_VERSION < 3
  isc_result_t
  dlz_findzonedb(void *dbdata, const char *name)
  #else
  isc_result_t
  dlz_findzonedb(void *dbdata, const char *name,
  	       dns_clientinfomethods_t *methods,
  	       dns_clientinfo_t *clientinfo)
  #endif
  {
  
  	isc_result_t result;
  	config_data_t *cd = (config_data_t *) dbdata;
  	char *path;
  	struct stat sb;
  	path = NULL;
  
  #if DLZ_DLOPEN_VERSION >= 3
  	UNUSED(methods);
  	UNUSED(clientinfo);
  #endif
  
  	if (create_path(name, NULL, NULL, cd, &path) != ISC_R_SUCCESS)
  		return (ISC_R_NOTFOUND);
  
  	cd->log(ISC_LOG_DEBUG(1),
  		"Filesystem driver Findzone() Checking for path: '%s'\n", path);
  
  	if (stat(path, &sb) != 0) {
  		result = ISC_R_NOTFOUND;
  		goto complete_FZ;
  	}
  
  	if ((sb.st_mode & S_IFDIR) != 0) {
  		result = ISC_R_SUCCESS;
  		goto complete_FZ;
  	}
  
  	result = ISC_R_NOTFOUND;
  
   complete_FZ:
  
  	free(path);
  	return (result);
  }
  
  #if DLZ_DLOPEN_VERSION == 1
  isc_result_t
  dlz_lookup(const char *zone, const char *name,
  	   void *dbdata, dns_sdlzlookup_t *lookup)
  #else
  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
  {
  	isc_result_t result = ISC_R_NOTFOUND;
  	config_data_t *cd = (config_data_t *) dbdata;
  	char *path;
  	struct stat sb;
  	dir_t dir;
  	path = NULL;
  
  	UNUSED(lookup);
  #if DLZ_DLOPEN_VERSION >= 2
  	UNUSED(methods);
  	UNUSED(clientinfo);
  #endif
  
  	if (strcmp(name, "*") == 0)
  		/*
  		 * handle filesystem's special wildcard "-"
  		 */
  		result = create_path(zone, "-", NULL, cd, &path);
  	else
  		result = create_path(zone, name, NULL, cd, &path);
  
  	if (result != ISC_R_SUCCESS)
  		return (ISC_R_NOTFOUND);
  
  	/* remove path separator at end of path so stat works properly */
  	path[strlen(path)-1] = '\0';
  
  	cd->log(ISC_LOG_DEBUG(1),
  		"Filesystem driver lookup() Checking for path: '%s'\n", path);
  
  	if (stat(path, &sb) != 0) {
  		result = ISC_R_NOTFOUND;
  		goto complete_lkup;
  	}
  
  	if ((sb.st_mode & S_IFDIR) == 0) {
  		result = ISC_R_NOTFOUND;
  		goto complete_lkup;
  	}
  
  	/* initialize and open directory */
  	dir_init(&dir);
  	result = dir_open(&dir, path);
  
  	/* if directory open failed, return error. */
  	if (result != ISC_R_SUCCESS) {
  		cd->log(ISC_LOG_ERROR,
  			"Unable to open %s directory to read entries.", path);
  		result = ISC_R_FAILURE;
  		goto complete_lkup;
  	}
  
  	/* process any records in the directory */
  	result = process_dir(&dir, lookup, cd, NULL, 0);
  
  	/* close the directory */
  	dir_close(&dir);
  
   complete_lkup:
  
  	free(path);
  	return (result);
  }
  
  isc_result_t
  dlz_create(const char *dlzname, unsigned int argc, char *argv[],
  	   void **dbdata, ...)
  {
  	config_data_t *cd;
  	char *endp;
  	int len;
  	char pathsep;
  	const char *helper_name;
  	va_list ap;
  
  	UNUSED(dlzname);
  
  	/* allocate memory for our config data and helper functions */
  	cd = calloc(1, sizeof(config_data_t));
  	if (cd == NULL)
  		goto no_mem;
  
  	/* zero the memory */
  	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);
  
  	/* we require 5 command line args. */
  	if (argc != 6) {
  		cd->log(ISC_LOG_ERROR,
  			"Filesystem driver requires "
  			"6 command line args.");
  		return (ISC_R_FAILURE);
  	}
  
  	if (strlen(argv[5]) > 1) {
  		cd->log(ISC_LOG_ERROR,
  			"Filesystem driver can only "
  			"accept a single character for separator.");
  		return (ISC_R_FAILURE);
  	}
  
  	/* verify base dir ends with '/' or '\' */
  	len = strlen(argv[1]);
  	if (argv[1][len-1] != '\\' && argv[1][len-1] != '/') {
  		cd->log(ISC_LOG_ERROR,
  			"Base dir parameter for filesystem driver "
  			"should end with %s",
  			"either '/' or '\\' ");
  		return (ISC_R_FAILURE);
  	}
  
  	/* determine and save path separator for later */
  	if (argv[1][len-1] == '\\')
  		pathsep = '\\';
  	else
  		pathsep = '/';
  
  	cd->pathsep = pathsep;
  
  	/* get and store our base directory */
  	cd->basedir = strdup(argv[1]);
  	if (cd->basedir == NULL)
  		goto no_mem;
  	cd->basedirsize = strlen(cd->basedir);
  
  	/* get and store our data sub-dir */
  	cd->datadir = strdup(argv[2]);
  	if (cd->datadir == NULL)
  		goto no_mem;
  	cd->datadirsize = strlen(cd->datadir);
  
  	/* get and store our zone xfr sub-dir */
  	cd->xfrdir = strdup(argv[3]);
  	if (cd->xfrdir == NULL)
  		goto no_mem;
  	cd->xfrdirsize = strlen(cd->xfrdir);
  
  	/* get and store our directory split count */
  	cd->splitcnt = strtol(argv[4], &endp, 10);
  	if (*endp != '\0' || cd->splitcnt < 0)
  		cd->log(ISC_LOG_ERROR,
  			"Directory split count must be zero (0) "
  			"or a postive number");
  
  	/* get and store our separator character */
  	cd->separator = *argv[5];
  
  	/* pass back config data */
  	*dbdata = cd;
  
  	/* return success */
  	return (ISC_R_SUCCESS);
  
  	/* handle no memory error */
   no_mem:
  
  	/* write error message */
  	if (cd != NULL && cd->log != NULL)
  		cd->log(ISC_LOG_ERROR,
  			"filesystem_dynamic: Filesystem driver unable to "
  			"allocate memory for config data.");
  
  	/* if we allocated a config data object clean it up */
  	if (cd != NULL)
  		dlz_destroy(cd);
  
  	/* return error */
  	return (ISC_R_NOMEMORY);
  }
  
  void
  dlz_destroy(void *dbdata) {
  	config_data_t *cd;
  
  	cd = (config_data_t *) dbdata;
  
  	/*
  	 * free memory for each section of config data that was
  	 * allocated
  	 */
  	if (cd->basedir != NULL)
  		free(cd->basedir);
  
  	if (cd->datadir != NULL)
  		free(cd->datadir);
  
  	if (cd->xfrdir != NULL)
  		free(cd->xfrdir);
  
  	/* free config data memory */
  	free(cd);
  }
  
  /*
   * Return the version of the API
   */
  int
  dlz_version(unsigned int *flags) {
  	UNUSED(flags);
  	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;
  }
  