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