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