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