/*
 * 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) 2002 Stichting NLnet, Netherlands, stichting@nlnet.nl.
 *
 * 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 THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
 * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 <stdarg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include "dir.h"
#include "dlz_list.h"
#include "dlz_minimal.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 bool
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 (false);
			}
			/* '..', two dots together is not allowed. */
			if (input[i - 1] == '.') {
				return (false);
			}
			/* '.' is not allowed as last char */
			if (i == len - 1) {
				return (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 (false);
	}
	/* everything ok. */
	return (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;
	bool isroot = false;

	/* special case for root zone */
	if (strcmp(zone, ".") == 0) {
		isroot = 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;
	bool foundHost;

	tmp[0] = '\0'; /* set 1st byte to '\0' so strcpy works right. */
	host[0] = '\0';
	foundHost = 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 = 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 = 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) {
				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 positive 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;

	/* 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) {
		result = ISC_R_NOTFOUND;
		goto complete_allnds;
	}

	/* 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  /* 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 */
{
	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 DLZ_DLOPEN_VERSION >= 3 */

	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  /* 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 = 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 DLZ_DLOPEN_VERSION >= 2 */

	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,
	   ...) {
	isc_result_t result = ISC_R_NOMEMORY;
	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.");
		result = ISC_R_FAILURE;
		goto free_cd;
	}

	if (strlen(argv[5]) > 1) {
		cd->log(ISC_LOG_ERROR, "Filesystem driver can only "
				       "accept a single character for "
				       "separator.");
		result = ISC_R_FAILURE;
		goto free_cd;
	}

	/* 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 '\\' ");
		result = ISC_R_FAILURE;
		goto free_cd;
	}

	/* 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 positive 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.");
	}

free_cd:
	/* if we allocated a config data object clean it up */
	if (cd != NULL) {
		dlz_destroy(cd);
	}

	/* return error */
	return (result);
}

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;
	}
}
