1/*	$NetBSD: named-checkzone.c,v 1.10 2024/02/21 22:50:59 christos Exp $	*/
2
3/*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * SPDX-License-Identifier: MPL-2.0
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 * See the COPYRIGHT file distributed with this work for additional
13 * information regarding copyright ownership.
14 */
15
16/*! \file */
17
18#include <inttypes.h>
19#include <stdbool.h>
20#include <stdlib.h>
21
22#include <isc/app.h>
23#include <isc/attributes.h>
24#include <isc/commandline.h>
25#include <isc/dir.h>
26#include <isc/file.h>
27#include <isc/hash.h>
28#include <isc/log.h>
29#include <isc/mem.h>
30#include <isc/print.h>
31#include <isc/result.h>
32#include <isc/string.h>
33#include <isc/task.h>
34#include <isc/timer.h>
35#include <isc/util.h>
36
37#include <dns/db.h>
38#include <dns/fixedname.h>
39#include <dns/log.h>
40#include <dns/master.h>
41#include <dns/masterdump.h>
42#include <dns/name.h>
43#include <dns/rdataclass.h>
44#include <dns/rdataset.h>
45#include <dns/types.h>
46#include <dns/zone.h>
47
48#include "check-tool.h"
49
50static int quiet = 0;
51static isc_mem_t *mctx = NULL;
52dns_zone_t *zone = NULL;
53dns_zonetype_t zonetype = dns_zone_primary;
54static int dumpzone = 0;
55static const char *output_filename;
56static const char *prog_name = NULL;
57static const dns_master_style_t *outputstyle = NULL;
58static enum { progmode_check, progmode_compile } progmode;
59
60#define ERRRET(result, function)                                              \
61	do {                                                                  \
62		if (result != ISC_R_SUCCESS) {                                \
63			if (!quiet)                                           \
64				fprintf(stderr, "%s() returned %s\n",         \
65					function, isc_result_totext(result)); \
66			return (result);                                      \
67		}                                                             \
68	} while (0)
69
70noreturn static void
71usage(void);
72
73static void
74usage(void) {
75	fprintf(stderr,
76		"usage: %s [-djqvD] [-c class] "
77		"[-f inputformat] [-F outputformat] [-J filename] "
78		"[-s (full|relative)] [-t directory] [-w directory] "
79		"[-k (ignore|warn|fail)] [-m (ignore|warn|fail)] "
80		"[-n (ignore|warn|fail)] [-r (ignore|warn|fail)] "
81		"[-i (full|full-sibling|local|local-sibling|none)] "
82		"[-M (ignore|warn|fail)] [-S (ignore|warn|fail)] "
83		"[-W (ignore|warn)] "
84		"%s zonename [ (filename|-) ]\n",
85		prog_name,
86		progmode == progmode_check ? "[-o filename]" : "-o filename");
87	exit(1);
88}
89
90static void
91destroy(void) {
92	if (zone != NULL) {
93		dns_zone_detach(&zone);
94	}
95}
96
97/*% main processing routine */
98int
99main(int argc, char **argv) {
100	int c;
101	char *origin = NULL;
102	const char *filename = NULL;
103	isc_log_t *lctx = NULL;
104	isc_result_t result;
105	char classname_in[] = "IN";
106	char *classname = classname_in;
107	const char *workdir = NULL;
108	const char *inputformatstr = NULL;
109	const char *outputformatstr = NULL;
110	dns_masterformat_t inputformat = dns_masterformat_text;
111	dns_masterformat_t outputformat = dns_masterformat_text;
112	dns_masterrawheader_t header;
113	uint32_t rawversion = 1, serialnum = 0;
114	dns_ttl_t maxttl = 0;
115	bool snset = false;
116	bool logdump = false;
117	FILE *errout = stdout;
118	char *endp;
119
120	/*
121	 * Uncomment the following line if memory debugging is needed:
122	 * isc_mem_debugging |= ISC_MEM_DEBUGRECORD;
123	 */
124
125	outputstyle = &dns_master_style_full;
126
127	prog_name = strrchr(argv[0], '/');
128	if (prog_name == NULL) {
129		prog_name = strrchr(argv[0], '\\');
130	}
131	if (prog_name != NULL) {
132		prog_name++;
133	} else {
134		prog_name = argv[0];
135	}
136	/*
137	 * Libtool doesn't preserve the program name prior to final
138	 * installation.  Remove the libtool prefix ("lt-").
139	 */
140	if (strncmp(prog_name, "lt-", 3) == 0) {
141		prog_name += 3;
142	}
143
144#define PROGCMP(X) \
145	(strcasecmp(prog_name, X) == 0 || strcasecmp(prog_name, X ".exe") == 0)
146
147	if (PROGCMP("named-checkzone")) {
148		progmode = progmode_check;
149	} else if (PROGCMP("named-compilezone")) {
150		progmode = progmode_compile;
151	} else {
152		UNREACHABLE();
153	}
154
155	/* Compilation specific defaults */
156	if (progmode == progmode_compile) {
157		zone_options |= (DNS_ZONEOPT_CHECKNS | DNS_ZONEOPT_FATALNS |
158				 DNS_ZONEOPT_CHECKSPF | DNS_ZONEOPT_CHECKDUPRR |
159				 DNS_ZONEOPT_CHECKNAMES |
160				 DNS_ZONEOPT_CHECKNAMESFAIL |
161				 DNS_ZONEOPT_CHECKWILDCARD);
162	} else {
163		zone_options |= (DNS_ZONEOPT_CHECKDUPRR | DNS_ZONEOPT_CHECKSPF);
164	}
165
166#define ARGCMP(X) (strcmp(isc_commandline_argument, X) == 0)
167
168	isc_commandline_errprint = false;
169
170	while ((c = isc_commandline_parse(argc, argv,
171					  "c:df:hi:jJ:k:L:l:m:n:qr:s:t:o:vw:DF:"
172					  "M:S:T:W:")) != EOF)
173	{
174		switch (c) {
175		case 'c':
176			classname = isc_commandline_argument;
177			break;
178
179		case 'd':
180			debug++;
181			break;
182
183		case 'i':
184			if (ARGCMP("full")) {
185				zone_options |= DNS_ZONEOPT_CHECKINTEGRITY |
186						DNS_ZONEOPT_CHECKSIBLING;
187				docheckmx = true;
188				docheckns = true;
189				dochecksrv = true;
190			} else if (ARGCMP("full-sibling")) {
191				zone_options |= DNS_ZONEOPT_CHECKINTEGRITY;
192				zone_options &= ~DNS_ZONEOPT_CHECKSIBLING;
193				docheckmx = true;
194				docheckns = true;
195				dochecksrv = true;
196			} else if (ARGCMP("local")) {
197				zone_options |= DNS_ZONEOPT_CHECKINTEGRITY;
198				zone_options |= DNS_ZONEOPT_CHECKSIBLING;
199				docheckmx = false;
200				docheckns = false;
201				dochecksrv = false;
202			} else if (ARGCMP("local-sibling")) {
203				zone_options |= DNS_ZONEOPT_CHECKINTEGRITY;
204				zone_options &= ~DNS_ZONEOPT_CHECKSIBLING;
205				docheckmx = false;
206				docheckns = false;
207				dochecksrv = false;
208			} else if (ARGCMP("none")) {
209				zone_options &= ~DNS_ZONEOPT_CHECKINTEGRITY;
210				zone_options &= ~DNS_ZONEOPT_CHECKSIBLING;
211				docheckmx = false;
212				docheckns = false;
213				dochecksrv = false;
214			} else {
215				fprintf(stderr, "invalid argument to -i: %s\n",
216					isc_commandline_argument);
217				exit(1);
218			}
219			break;
220
221		case 'f':
222			inputformatstr = isc_commandline_argument;
223			break;
224
225		case 'F':
226			outputformatstr = isc_commandline_argument;
227			break;
228
229		case 'j':
230			nomerge = false;
231			break;
232
233		case 'J':
234			journal = isc_commandline_argument;
235			nomerge = false;
236			break;
237
238		case 'k':
239			if (ARGCMP("warn")) {
240				zone_options |= DNS_ZONEOPT_CHECKNAMES;
241				zone_options &= ~DNS_ZONEOPT_CHECKNAMESFAIL;
242			} else if (ARGCMP("fail")) {
243				zone_options |= DNS_ZONEOPT_CHECKNAMES |
244						DNS_ZONEOPT_CHECKNAMESFAIL;
245			} else if (ARGCMP("ignore")) {
246				zone_options &= ~(DNS_ZONEOPT_CHECKNAMES |
247						  DNS_ZONEOPT_CHECKNAMESFAIL);
248			} else {
249				fprintf(stderr, "invalid argument to -k: %s\n",
250					isc_commandline_argument);
251				exit(1);
252			}
253			break;
254
255		case 'L':
256			snset = true;
257			endp = NULL;
258			serialnum = strtol(isc_commandline_argument, &endp, 0);
259			if (*endp != '\0') {
260				fprintf(stderr, "source serial number "
261						"must be numeric");
262				exit(1);
263			}
264			break;
265
266		case 'l':
267			zone_options |= DNS_ZONEOPT_CHECKTTL;
268			endp = NULL;
269			maxttl = strtol(isc_commandline_argument, &endp, 0);
270			if (*endp != '\0') {
271				fprintf(stderr, "maximum TTL "
272						"must be numeric");
273				exit(1);
274			}
275			break;
276
277		case 'n':
278			if (ARGCMP("ignore")) {
279				zone_options &= ~(DNS_ZONEOPT_CHECKNS |
280						  DNS_ZONEOPT_FATALNS);
281			} else if (ARGCMP("warn")) {
282				zone_options |= DNS_ZONEOPT_CHECKNS;
283				zone_options &= ~DNS_ZONEOPT_FATALNS;
284			} else if (ARGCMP("fail")) {
285				zone_options |= DNS_ZONEOPT_CHECKNS |
286						DNS_ZONEOPT_FATALNS;
287			} else {
288				fprintf(stderr, "invalid argument to -n: %s\n",
289					isc_commandline_argument);
290				exit(1);
291			}
292			break;
293
294		case 'm':
295			if (ARGCMP("warn")) {
296				zone_options |= DNS_ZONEOPT_CHECKMX;
297				zone_options &= ~DNS_ZONEOPT_CHECKMXFAIL;
298			} else if (ARGCMP("fail")) {
299				zone_options |= DNS_ZONEOPT_CHECKMX |
300						DNS_ZONEOPT_CHECKMXFAIL;
301			} else if (ARGCMP("ignore")) {
302				zone_options &= ~(DNS_ZONEOPT_CHECKMX |
303						  DNS_ZONEOPT_CHECKMXFAIL);
304			} else {
305				fprintf(stderr, "invalid argument to -m: %s\n",
306					isc_commandline_argument);
307				exit(1);
308			}
309			break;
310
311		case 'o':
312			output_filename = isc_commandline_argument;
313			break;
314
315		case 'q':
316			quiet++;
317			break;
318
319		case 'r':
320			if (ARGCMP("warn")) {
321				zone_options |= DNS_ZONEOPT_CHECKDUPRR;
322				zone_options &= ~DNS_ZONEOPT_CHECKDUPRRFAIL;
323			} else if (ARGCMP("fail")) {
324				zone_options |= DNS_ZONEOPT_CHECKDUPRR |
325						DNS_ZONEOPT_CHECKDUPRRFAIL;
326			} else if (ARGCMP("ignore")) {
327				zone_options &= ~(DNS_ZONEOPT_CHECKDUPRR |
328						  DNS_ZONEOPT_CHECKDUPRRFAIL);
329			} else {
330				fprintf(stderr, "invalid argument to -r: %s\n",
331					isc_commandline_argument);
332				exit(1);
333			}
334			break;
335
336		case 's':
337			if (ARGCMP("full")) {
338				outputstyle = &dns_master_style_full;
339			} else if (ARGCMP("relative")) {
340				outputstyle = &dns_master_style_default;
341			} else {
342				fprintf(stderr,
343					"unknown or unsupported style: %s\n",
344					isc_commandline_argument);
345				exit(1);
346			}
347			break;
348
349		case 't':
350			result = isc_dir_chroot(isc_commandline_argument);
351			if (result != ISC_R_SUCCESS) {
352				fprintf(stderr, "isc_dir_chroot: %s: %s\n",
353					isc_commandline_argument,
354					isc_result_totext(result));
355				exit(1);
356			}
357			break;
358
359		case 'v':
360			printf("%s\n", PACKAGE_VERSION);
361			exit(0);
362
363		case 'w':
364			workdir = isc_commandline_argument;
365			break;
366
367		case 'D':
368			dumpzone++;
369			break;
370
371		case 'M':
372			if (ARGCMP("fail")) {
373				zone_options &= ~DNS_ZONEOPT_WARNMXCNAME;
374				zone_options &= ~DNS_ZONEOPT_IGNOREMXCNAME;
375			} else if (ARGCMP("warn")) {
376				zone_options |= DNS_ZONEOPT_WARNMXCNAME;
377				zone_options &= ~DNS_ZONEOPT_IGNOREMXCNAME;
378			} else if (ARGCMP("ignore")) {
379				zone_options |= DNS_ZONEOPT_WARNMXCNAME;
380				zone_options |= DNS_ZONEOPT_IGNOREMXCNAME;
381			} else {
382				fprintf(stderr, "invalid argument to -M: %s\n",
383					isc_commandline_argument);
384				exit(1);
385			}
386			break;
387
388		case 'S':
389			if (ARGCMP("fail")) {
390				zone_options &= ~DNS_ZONEOPT_WARNSRVCNAME;
391				zone_options &= ~DNS_ZONEOPT_IGNORESRVCNAME;
392			} else if (ARGCMP("warn")) {
393				zone_options |= DNS_ZONEOPT_WARNSRVCNAME;
394				zone_options &= ~DNS_ZONEOPT_IGNORESRVCNAME;
395			} else if (ARGCMP("ignore")) {
396				zone_options |= DNS_ZONEOPT_WARNSRVCNAME;
397				zone_options |= DNS_ZONEOPT_IGNORESRVCNAME;
398			} else {
399				fprintf(stderr, "invalid argument to -S: %s\n",
400					isc_commandline_argument);
401				exit(1);
402			}
403			break;
404
405		case 'T':
406			if (ARGCMP("warn")) {
407				zone_options |= DNS_ZONEOPT_CHECKSPF;
408			} else if (ARGCMP("ignore")) {
409				zone_options &= ~DNS_ZONEOPT_CHECKSPF;
410			} else {
411				fprintf(stderr, "invalid argument to -T: %s\n",
412					isc_commandline_argument);
413				exit(1);
414			}
415			break;
416
417		case 'W':
418			if (ARGCMP("warn")) {
419				zone_options |= DNS_ZONEOPT_CHECKWILDCARD;
420			} else if (ARGCMP("ignore")) {
421				zone_options &= ~DNS_ZONEOPT_CHECKWILDCARD;
422			}
423			break;
424
425		case '?':
426			if (isc_commandline_option != '?') {
427				fprintf(stderr, "%s: invalid argument -%c\n",
428					prog_name, isc_commandline_option);
429			}
430			FALLTHROUGH;
431		case 'h':
432			usage();
433
434		default:
435			fprintf(stderr, "%s: unhandled option -%c\n", prog_name,
436				isc_commandline_option);
437			exit(1);
438		}
439	}
440
441	if (workdir != NULL) {
442		result = isc_dir_chdir(workdir);
443		if (result != ISC_R_SUCCESS) {
444			fprintf(stderr, "isc_dir_chdir: %s: %s\n", workdir,
445				isc_result_totext(result));
446			exit(1);
447		}
448	}
449
450	if (inputformatstr != NULL) {
451		if (strcasecmp(inputformatstr, "text") == 0) {
452			inputformat = dns_masterformat_text;
453		} else if (strcasecmp(inputformatstr, "raw") == 0) {
454			inputformat = dns_masterformat_raw;
455		} else if (strncasecmp(inputformatstr, "raw=", 4) == 0) {
456			inputformat = dns_masterformat_raw;
457			fprintf(stderr, "WARNING: input format raw, version "
458					"ignored\n");
459		} else {
460			fprintf(stderr, "unknown file format: %s\n",
461				inputformatstr);
462			exit(1);
463		}
464	}
465
466	if (outputformatstr != NULL) {
467		if (strcasecmp(outputformatstr, "text") == 0) {
468			outputformat = dns_masterformat_text;
469		} else if (strcasecmp(outputformatstr, "raw") == 0) {
470			outputformat = dns_masterformat_raw;
471		} else if (strncasecmp(outputformatstr, "raw=", 4) == 0) {
472			char *end;
473
474			outputformat = dns_masterformat_raw;
475			rawversion = strtol(outputformatstr + 4, &end, 10);
476			if (end == outputformatstr + 4 || *end != '\0' ||
477			    rawversion > 1U)
478			{
479				fprintf(stderr, "unknown raw format version\n");
480				exit(1);
481			}
482		} else {
483			fprintf(stderr, "unknown file format: %s\n",
484				outputformatstr);
485			exit(1);
486		}
487	}
488
489	if (progmode == progmode_compile) {
490		dumpzone = 1; /* always dump */
491		logdump = !quiet;
492		if (output_filename == NULL) {
493			fprintf(stderr, "output file required, but not "
494					"specified\n");
495			usage();
496		}
497	}
498
499	if (output_filename != NULL) {
500		dumpzone = 1;
501	}
502
503	/*
504	 * If we are printing to stdout then send the informational
505	 * output to stderr.
506	 */
507	if (dumpzone &&
508	    (output_filename == NULL || strcmp(output_filename, "-") == 0 ||
509	     strcmp(output_filename, "/dev/fd/1") == 0 ||
510	     strcmp(output_filename, "/dev/stdout") == 0))
511	{
512		errout = stderr;
513		logdump = false;
514	}
515
516	if (argc - isc_commandline_index < 1 ||
517	    argc - isc_commandline_index > 2)
518	{
519		usage();
520	}
521
522	isc_mem_create(&mctx);
523	if (!quiet) {
524		RUNTIME_CHECK(setup_logging(mctx, errout, &lctx) ==
525			      ISC_R_SUCCESS);
526	}
527
528	origin = argv[isc_commandline_index++];
529
530	if (isc_commandline_index == argc) {
531		/* "-" will be interpreted as stdin */
532		filename = "-";
533	} else {
534		filename = argv[isc_commandline_index];
535	}
536
537	isc_commandline_index++;
538
539	result = load_zone(mctx, origin, filename, inputformat, classname,
540			   maxttl, &zone);
541
542	if (snset) {
543		dns_master_initrawheader(&header);
544		header.flags = DNS_MASTERRAW_SOURCESERIALSET;
545		header.sourceserial = serialnum;
546		dns_zone_setrawdata(zone, &header);
547	}
548
549	if (result == ISC_R_SUCCESS && dumpzone) {
550		if (logdump) {
551			fprintf(errout, "dump zone to %s...", output_filename);
552			fflush(errout);
553		}
554		result = dump_zone(origin, zone, output_filename, outputformat,
555				   outputstyle, rawversion);
556		if (logdump) {
557			fprintf(errout, "done\n");
558		}
559	}
560
561	if (!quiet && result == ISC_R_SUCCESS) {
562		fprintf(errout, "OK\n");
563	}
564	destroy();
565	if (lctx != NULL) {
566		isc_log_destroy(&lctx);
567	}
568	isc_mem_destroy(&mctx);
569
570	return ((result == ISC_R_SUCCESS) ? 0 : 1);
571}
572