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