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