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