1 /* $NetBSD: dnssectool.c,v 1.4 2020/05/24 19:46:11 christos Exp $ */ 2 3 /* 4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC") 5 * 6 * This Source Code Form is subject to the terms of the Mozilla Public 7 * License, v. 2.0. If a copy of the MPL was not distributed with this 8 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * See the COPYRIGHT file distributed with this work for additional 11 * information regarding copyright ownership. 12 */ 13 14 /*! \file */ 15 16 /*% 17 * DNSSEC Support Routines. 18 */ 19 20 #include <inttypes.h> 21 #include <stdbool.h> 22 #include <stdlib.h> 23 24 #ifdef _WIN32 25 #include <Winsock2.h> 26 #endif /* ifdef _WIN32 */ 27 28 #include <isc/base32.h> 29 #include <isc/buffer.h> 30 #include <isc/commandline.h> 31 #include <isc/dir.h> 32 #include <isc/file.h> 33 #include <isc/heap.h> 34 #include <isc/list.h> 35 #include <isc/mem.h> 36 #include <isc/platform.h> 37 #include <isc/print.h> 38 #include <isc/string.h> 39 #include <isc/time.h> 40 #include <isc/util.h> 41 42 #include <dns/db.h> 43 #include <dns/dbiterator.h> 44 #include <dns/dnssec.h> 45 #include <dns/fixedname.h> 46 #include <dns/keyvalues.h> 47 #include <dns/log.h> 48 #include <dns/name.h> 49 #include <dns/nsec.h> 50 #include <dns/nsec3.h> 51 #include <dns/rdataclass.h> 52 #include <dns/rdataset.h> 53 #include <dns/rdatasetiter.h> 54 #include <dns/rdatastruct.h> 55 #include <dns/rdatatype.h> 56 #include <dns/result.h> 57 #include <dns/secalg.h> 58 #include <dns/time.h> 59 60 #include "dnssectool.h" 61 62 #define KEYSTATES_NVALUES 4 63 static const char *keystates[KEYSTATES_NVALUES] = { 64 "hidden", 65 "rumoured", 66 "omnipresent", 67 "unretentive", 68 }; 69 70 int verbose = 0; 71 bool quiet = false; 72 uint8_t dtype[8]; 73 74 static fatalcallback_t *fatalcallback = NULL; 75 76 void 77 fatal(const char *format, ...) { 78 va_list args; 79 80 fprintf(stderr, "%s: fatal: ", program); 81 va_start(args, format); 82 vfprintf(stderr, format, args); 83 va_end(args); 84 fprintf(stderr, "\n"); 85 if (fatalcallback != NULL) { 86 (*fatalcallback)(); 87 } 88 exit(1); 89 } 90 91 void 92 setfatalcallback(fatalcallback_t *callback) { 93 fatalcallback = callback; 94 } 95 96 void 97 check_result(isc_result_t result, const char *message) { 98 if (result != ISC_R_SUCCESS) { 99 fatal("%s: %s", message, isc_result_totext(result)); 100 } 101 } 102 103 void 104 vbprintf(int level, const char *fmt, ...) { 105 va_list ap; 106 if (level > verbose) { 107 return; 108 } 109 va_start(ap, fmt); 110 fprintf(stderr, "%s: ", program); 111 vfprintf(stderr, fmt, ap); 112 va_end(ap); 113 } 114 115 void 116 version(const char *name) { 117 fprintf(stderr, "%s %s\n", name, VERSION); 118 exit(0); 119 } 120 121 void 122 sig_format(dns_rdata_rrsig_t *sig, char *cp, unsigned int size) { 123 char namestr[DNS_NAME_FORMATSIZE]; 124 char algstr[DNS_NAME_FORMATSIZE]; 125 126 dns_name_format(&sig->signer, namestr, sizeof(namestr)); 127 dns_secalg_format(sig->algorithm, algstr, sizeof(algstr)); 128 snprintf(cp, size, "%s/%s/%d", namestr, algstr, sig->keyid); 129 } 130 131 void 132 setup_logging(isc_mem_t *mctx, isc_log_t **logp) { 133 isc_logdestination_t destination; 134 isc_logconfig_t *logconfig = NULL; 135 isc_log_t *log = NULL; 136 int level; 137 138 if (verbose < 0) { 139 verbose = 0; 140 } 141 switch (verbose) { 142 case 0: 143 /* 144 * We want to see warnings about things like out-of-zone 145 * data in the master file even when not verbose. 146 */ 147 level = ISC_LOG_WARNING; 148 break; 149 case 1: 150 level = ISC_LOG_INFO; 151 break; 152 default: 153 level = ISC_LOG_DEBUG(verbose - 2 + 1); 154 break; 155 } 156 157 isc_log_create(mctx, &log, &logconfig); 158 isc_log_setcontext(log); 159 dns_log_init(log); 160 dns_log_setcontext(log); 161 isc_log_settag(logconfig, program); 162 163 /* 164 * Set up a channel similar to default_stderr except: 165 * - the logging level is passed in 166 * - the program name and logging level are printed 167 * - no time stamp is printed 168 */ 169 destination.file.stream = stderr; 170 destination.file.name = NULL; 171 destination.file.versions = ISC_LOG_ROLLNEVER; 172 destination.file.maximum_size = 0; 173 isc_log_createchannel(logconfig, "stderr", ISC_LOG_TOFILEDESC, level, 174 &destination, 175 ISC_LOG_PRINTTAG | ISC_LOG_PRINTLEVEL); 176 177 RUNTIME_CHECK(isc_log_usechannel(logconfig, "stderr", NULL, NULL) == 178 ISC_R_SUCCESS); 179 180 *logp = log; 181 } 182 183 void 184 cleanup_logging(isc_log_t **logp) { 185 isc_log_t *log; 186 187 REQUIRE(logp != NULL); 188 189 log = *logp; 190 *logp = NULL; 191 192 if (log == NULL) { 193 return; 194 } 195 196 isc_log_destroy(&log); 197 isc_log_setcontext(NULL); 198 dns_log_setcontext(NULL); 199 } 200 201 static isc_stdtime_t 202 time_units(isc_stdtime_t offset, char *suffix, const char *str) { 203 switch (suffix[0]) { 204 case 'Y': 205 case 'y': 206 return (offset * (365 * 24 * 3600)); 207 case 'M': 208 case 'm': 209 switch (suffix[1]) { 210 case 'O': 211 case 'o': 212 return (offset * (30 * 24 * 3600)); 213 case 'I': 214 case 'i': 215 return (offset * 60); 216 case '\0': 217 fatal("'%s' ambiguous: use 'mi' for minutes " 218 "or 'mo' for months", 219 str); 220 default: 221 fatal("time value %s is invalid", str); 222 } 223 /* NOTREACHED */ 224 break; 225 case 'W': 226 case 'w': 227 return (offset * (7 * 24 * 3600)); 228 case 'D': 229 case 'd': 230 return (offset * (24 * 3600)); 231 case 'H': 232 case 'h': 233 return (offset * 3600); 234 case 'S': 235 case 's': 236 case '\0': 237 return (offset); 238 default: 239 fatal("time value %s is invalid", str); 240 } 241 /* NOTREACHED */ 242 return (0); /* silence compiler warning */ 243 } 244 245 static inline bool 246 isnone(const char *str) { 247 return ((strcasecmp(str, "none") == 0) || 248 (strcasecmp(str, "never") == 0)); 249 } 250 251 dns_ttl_t 252 strtottl(const char *str) { 253 const char *orig = str; 254 dns_ttl_t ttl; 255 char *endp; 256 257 if (isnone(str)) { 258 return ((dns_ttl_t)0); 259 } 260 261 ttl = strtol(str, &endp, 0); 262 if (ttl == 0 && endp == str) { 263 fatal("TTL must be numeric"); 264 } 265 ttl = time_units(ttl, endp, orig); 266 return (ttl); 267 } 268 269 dst_key_state_t 270 strtokeystate(const char *str) { 271 if (isnone(str)) { 272 return (DST_KEY_STATE_NA); 273 } 274 275 for (int i = 0; i < KEYSTATES_NVALUES; i++) { 276 if (keystates[i] != NULL && strcasecmp(str, keystates[i]) == 0) 277 { 278 return ((dst_key_state_t)i); 279 } 280 } 281 fatal("unknown key state"); 282 } 283 284 isc_stdtime_t 285 strtotime(const char *str, int64_t now, int64_t base, bool *setp) { 286 int64_t val, offset; 287 isc_result_t result; 288 const char *orig = str; 289 char *endp; 290 size_t n; 291 292 if (isnone(str)) { 293 if (setp != NULL) { 294 *setp = false; 295 } 296 return ((isc_stdtime_t)0); 297 } 298 299 if (setp != NULL) { 300 *setp = true; 301 } 302 303 if ((str[0] == '0' || str[0] == '-') && str[1] == '\0') { 304 return ((isc_stdtime_t)0); 305 } 306 307 /* 308 * We accept times in the following formats: 309 * now([+-]offset) 310 * YYYYMMDD([+-]offset) 311 * YYYYMMDDhhmmss([+-]offset) 312 * [+-]offset 313 */ 314 n = strspn(str, "0123456789"); 315 if ((n == 8u || n == 14u) && 316 (str[n] == '\0' || str[n] == '-' || str[n] == '+')) { 317 char timestr[15]; 318 319 strlcpy(timestr, str, sizeof(timestr)); 320 timestr[n] = 0; 321 if (n == 8u) { 322 strlcat(timestr, "000000", sizeof(timestr)); 323 } 324 result = dns_time64_fromtext(timestr, &val); 325 if (result != ISC_R_SUCCESS) { 326 fatal("time value %s is invalid: %s", orig, 327 isc_result_totext(result)); 328 } 329 base = val; 330 str += n; 331 } else if (strncmp(str, "now", 3) == 0) { 332 base = now; 333 str += 3; 334 } 335 336 if (str[0] == '\0') { 337 return ((isc_stdtime_t)base); 338 } else if (str[0] == '+') { 339 offset = strtol(str + 1, &endp, 0); 340 offset = time_units((isc_stdtime_t)offset, endp, orig); 341 val = base + offset; 342 } else if (str[0] == '-') { 343 offset = strtol(str + 1, &endp, 0); 344 offset = time_units((isc_stdtime_t)offset, endp, orig); 345 val = base - offset; 346 } else { 347 fatal("time value %s is invalid", orig); 348 } 349 350 return ((isc_stdtime_t)val); 351 } 352 353 dns_rdataclass_t 354 strtoclass(const char *str) { 355 isc_textregion_t r; 356 dns_rdataclass_t rdclass; 357 isc_result_t result; 358 359 if (str == NULL) { 360 return (dns_rdataclass_in); 361 } 362 DE_CONST(str, r.base); 363 r.length = strlen(str); 364 result = dns_rdataclass_fromtext(&rdclass, &r); 365 if (result != ISC_R_SUCCESS) { 366 fatal("unknown class %s", str); 367 } 368 return (rdclass); 369 } 370 371 unsigned int 372 strtodsdigest(const char *str) { 373 isc_textregion_t r; 374 dns_dsdigest_t alg; 375 isc_result_t result; 376 377 DE_CONST(str, r.base); 378 r.length = strlen(str); 379 result = dns_dsdigest_fromtext(&alg, &r); 380 if (result != ISC_R_SUCCESS) { 381 fatal("unknown DS algorithm %s", str); 382 } 383 return (alg); 384 } 385 386 static int 387 cmp_dtype(const void *ap, const void *bp) { 388 int a = *(const uint8_t *)ap; 389 int b = *(const uint8_t *)bp; 390 return (a - b); 391 } 392 393 void 394 add_dtype(unsigned int dt) { 395 unsigned i, n; 396 397 /* ensure there is space for a zero terminator */ 398 n = sizeof(dtype) / sizeof(dtype[0]) - 1; 399 for (i = 0; i < n; i++) { 400 if (dtype[i] == dt) { 401 return; 402 } 403 if (dtype[i] == 0) { 404 dtype[i] = dt; 405 qsort(dtype, i + 1, 1, cmp_dtype); 406 return; 407 } 408 } 409 fatal("too many -a digest type arguments"); 410 } 411 412 isc_result_t 413 try_dir(const char *dirname) { 414 isc_result_t result; 415 isc_dir_t d; 416 417 isc_dir_init(&d); 418 result = isc_dir_open(&d, dirname); 419 if (result == ISC_R_SUCCESS) { 420 isc_dir_close(&d); 421 } 422 return (result); 423 } 424 425 /* 426 * Check private key version compatibility. 427 */ 428 void 429 check_keyversion(dst_key_t *key, char *keystr) { 430 int major, minor; 431 dst_key_getprivateformat(key, &major, &minor); 432 INSIST(major <= DST_MAJOR_VERSION); /* invalid private key */ 433 434 if (major < DST_MAJOR_VERSION || minor < DST_MINOR_VERSION) { 435 fatal("Key %s has incompatible format version %d.%d, " 436 "use -f to force upgrade to new version.", 437 keystr, major, minor); 438 } 439 if (minor > DST_MINOR_VERSION) { 440 fatal("Key %s has incompatible format version %d.%d, " 441 "use -f to force downgrade to current version.", 442 keystr, major, minor); 443 } 444 } 445 446 void 447 set_keyversion(dst_key_t *key) { 448 int major, minor; 449 dst_key_getprivateformat(key, &major, &minor); 450 INSIST(major <= DST_MAJOR_VERSION); 451 452 if (major != DST_MAJOR_VERSION || minor != DST_MINOR_VERSION) { 453 dst_key_setprivateformat(key, DST_MAJOR_VERSION, 454 DST_MINOR_VERSION); 455 } 456 457 /* 458 * If the key is from a version older than 1.3, set 459 * set the creation date 460 */ 461 if (major < 1 || (major == 1 && minor <= 2)) { 462 isc_stdtime_t now; 463 isc_stdtime_get(&now); 464 dst_key_settime(key, DST_TIME_CREATED, now); 465 } 466 } 467 468 bool 469 key_collision(dst_key_t *dstkey, dns_name_t *name, const char *dir, 470 isc_mem_t *mctx, bool *exact) { 471 isc_result_t result; 472 bool conflict = false; 473 dns_dnsseckeylist_t matchkeys; 474 dns_dnsseckey_t *key = NULL; 475 uint16_t id, oldid; 476 uint32_t rid, roldid; 477 dns_secalg_t alg; 478 char filename[NAME_MAX]; 479 isc_buffer_t fileb; 480 isc_stdtime_t now; 481 482 if (exact != NULL) { 483 *exact = false; 484 } 485 486 id = dst_key_id(dstkey); 487 rid = dst_key_rid(dstkey); 488 alg = dst_key_alg(dstkey); 489 490 /* 491 * For Diffie Hellman just check if there is a direct collision as 492 * they can't be revoked. Additionally dns_dnssec_findmatchingkeys 493 * only handles DNSKEY which is not used for HMAC. 494 */ 495 if (alg == DST_ALG_DH) { 496 isc_buffer_init(&fileb, filename, sizeof(filename)); 497 result = dst_key_buildfilename(dstkey, DST_TYPE_PRIVATE, dir, 498 &fileb); 499 if (result != ISC_R_SUCCESS) { 500 return (true); 501 } 502 return (isc_file_exists(filename)); 503 } 504 505 ISC_LIST_INIT(matchkeys); 506 isc_stdtime_get(&now); 507 result = dns_dnssec_findmatchingkeys(name, dir, now, mctx, &matchkeys); 508 if (result == ISC_R_NOTFOUND) { 509 return (false); 510 } 511 512 while (!ISC_LIST_EMPTY(matchkeys) && !conflict) { 513 key = ISC_LIST_HEAD(matchkeys); 514 if (dst_key_alg(key->key) != alg) { 515 goto next; 516 } 517 518 oldid = dst_key_id(key->key); 519 roldid = dst_key_rid(key->key); 520 521 if (oldid == rid || roldid == id || id == oldid) { 522 conflict = true; 523 if (id != oldid) { 524 if (verbose > 1) { 525 fprintf(stderr, 526 "Key ID %d could " 527 "collide with %d\n", 528 id, oldid); 529 } 530 } else { 531 if (exact != NULL) { 532 *exact = true; 533 } 534 if (verbose > 1) { 535 fprintf(stderr, "Key ID %d exists\n", 536 id); 537 } 538 } 539 } 540 541 next: 542 ISC_LIST_UNLINK(matchkeys, key, link); 543 dns_dnsseckey_destroy(mctx, &key); 544 } 545 546 /* Finish freeing the list */ 547 while (!ISC_LIST_EMPTY(matchkeys)) { 548 key = ISC_LIST_HEAD(matchkeys); 549 ISC_LIST_UNLINK(matchkeys, key, link); 550 dns_dnsseckey_destroy(mctx, &key); 551 } 552 553 return (conflict); 554 } 555 556 bool 557 isoptarg(const char *arg, char **argv, void (*usage)(void)) { 558 if (!strcasecmp(isc_commandline_argument, arg)) { 559 if (argv[isc_commandline_index] == NULL) { 560 fprintf(stderr, "%s: missing argument -%c %s\n", 561 program, isc_commandline_option, 562 isc_commandline_argument); 563 usage(); 564 } 565 isc_commandline_argument = argv[isc_commandline_index]; 566 /* skip to next argument */ 567 isc_commandline_index++; 568 return (true); 569 } 570 return (false); 571 } 572 573 #ifdef _WIN32 574 void 575 InitSockets(void) { 576 WORD wVersionRequested; 577 WSADATA wsaData; 578 int err; 579 580 wVersionRequested = MAKEWORD(2, 0); 581 582 err = WSAStartup(wVersionRequested, &wsaData); 583 if (err != 0) { 584 fprintf(stderr, "WSAStartup() failed: %d\n", err); 585 exit(1); 586 } 587 } 588 589 void 590 DestroySockets(void) { 591 WSACleanup(); 592 } 593 #endif /* ifdef _WIN32 */ 594