Update repub branch u/fanf2/patch to rebasing branch u/fanf2/rebasing revision v9_13_...
[ipreg/bind9.git] / bin / dnssec / dnssec-cds.c
CommitLineData
ba37674d 1/*
843d3896 2 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
ba37674d
EH
3 *
4 * This Source Code Form is subject to the terms of the Mozilla Public
5 * License, v. 2.0. If a copy of the MPL was not distributed with this
6 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
843d3896
OS
7 *
8 * See the COPYRIGHT file distributed with this work for additional
9 * information regarding copyright ownership.
ba37674d
EH
10 */
11
12/*
13 * Written by Tony Finch <dot@dotat.at> <fanf2@cam.ac.uk>
14 * at Cambridge University Information Services
15 */
16
17/*! \file */
18
ba37674d 19#include <errno.h>
cb6a185c 20#include <inttypes.h>
994e6569 21#include <stdbool.h>
ba37674d
EH
22#include <stdlib.h>
23
24#include <isc/buffer.h>
25#include <isc/commandline.h>
ba37674d
EH
26#include <isc/file.h>
27#include <isc/hash.h>
28#include <isc/mem.h>
29#include <isc/print.h>
30#include <isc/serial.h>
31#include <isc/string.h>
32#include <isc/time.h>
33#include <isc/util.h>
34
35#include <dns/callbacks.h>
36#include <dns/db.h>
37#include <dns/dbiterator.h>
38#include <dns/dnssec.h>
39#include <dns/ds.h>
40#include <dns/fixedname.h>
41#include <dns/keyvalues.h>
42#include <dns/log.h>
43#include <dns/master.h>
44#include <dns/name.h>
45#include <dns/rdata.h>
46#include <dns/rdataclass.h>
47#include <dns/rdatalist.h>
48#include <dns/rdataset.h>
49#include <dns/rdatasetiter.h>
50#include <dns/rdatatype.h>
51#include <dns/result.h>
52#include <dns/time.h>
53
54#include <dst/dst.h>
55
c3b8130f 56#if USE_PKCS11
ba37674d
EH
57#include <pk11/result.h>
58#endif
59
60#include "dnssectool.h"
61
62#ifndef PATH_MAX
f0f71420 63#define PATH_MAX 1024 /* WIN32, and others don't define this. */
ba37674d
EH
64#endif
65
66const char *program = "dnssec-cds";
67int verbose;
68
69/*
70 * Infrastructure
71 */
72static isc_log_t *lctx = NULL;
73static isc_mem_t *mctx = NULL;
ba37674d
EH
74
75/*
76 * The domain we are working on
77 */
78static const char *namestr = NULL;
79static dns_fixedname_t fixed;
80static dns_name_t *name = NULL;
81static dns_rdataclass_t rdclass = dns_rdataclass_in;
82
83/*
84 * List of digest types used by ds_from_cdnskey(), filled in by add_dtype()
85 * from -a arguments. The size of the array is an arbitrary limit.
86 */
0f219714 87static dns_dsdigest_t dtype[8];
ba37674d 88
d36b7f86 89static const char *startstr = NULL; /* from which we derive notbefore */
ba37674d
EH
90static isc_stdtime_t notbefore = 0; /* restrict sig inception times */
91static dns_rdata_rrsig_t oldestsig; /* for recording inception time */
92
93static int nkey; /* number of child zone DNSKEY records */
94
95/*
96 * The validation strategy of this program is top-down.
97 *
98 * We start with an implicitly trusted authoritative dsset.
99 *
100 * The child DNSKEY RRset is scanned to find out which keys are
101 * authenticated by DS records, and the result is recorded in a key
102 * table as described later in this comment.
103 *
104 * The key table is used up to three times to verify the signatures on
105 * the child DNSKEY, CDNSKEY, and CDS RRsets. In this program, only keys
106 * that have matching DS records are used for validating signatures.
107 *
108 * For replay attack protection, signatures are ignored if their inception
109 * time is before the previously recorded inception time. We use the earliest
110 * signature so that another run of dnssec-cds with the same records will
111 * still accept all the signatures.
112 *
113 * A key table is an array of nkey keyinfo structures, like
114 *
115 * keyinfo_t key_tbl[nkey];
116 *
117 * Each key is decoded into more useful representations, held in
118 * keyinfo->rdata
119 * keyinfo->dst
120 *
121 * If a key has no matching DS record then keyinfo->dst is NULL.
122 *
123 * The key algorithm and ID are saved in keyinfo->algo and
124 * keyinfo->tag for quicky skipping DS and RRSIG records that can't
125 * match.
126 */
127typedef struct keyinfo {
128 dns_rdata_t rdata;
129 dst_key_t *dst;
0f219714 130 dns_secalg_t algo;
ba37674d
EH
131 dns_keytag_t tag;
132} keyinfo_t;
133
134/* A replaceable function that can generate a DS RRset from some input */
135typedef isc_result_t
136ds_maker_func_t(dns_rdatalist_t *dslist, isc_buffer_t *buf, dns_rdata_t *rdata);
137
138static dns_rdataset_t cdnskey_set, cdnskey_sig;
139static dns_rdataset_t cds_set, cds_sig;
140static dns_rdataset_t dnskey_set, dnskey_sig;
141static dns_rdataset_t old_ds_set, new_ds_set;
142
143static keyinfo_t *old_key_tbl, *new_key_tbl;
144
145isc_buffer_t *new_ds_buf = NULL; /* backing store for new_ds_set */
146
147static void
148verbose_time(int level, const char *msg, isc_stdtime_t time) {
149 isc_result_t result;
150 isc_buffer_t timebuf;
151 char timestr[32];
152
153 if (verbose < level) {
154 return;
155 }
156
157 isc_buffer_init(&timebuf, timestr, sizeof(timestr));
158 result = dns_time64_totext(time, &timebuf);
159 check_result(result, "dns_time64_totext()");
160 isc_buffer_putuint8(&timebuf, 0);
161 if (verbose < 3) {
162 vbprintf(level, "%s %s\n", msg, timestr);
163 } else {
c355e1f3
OS
164 vbprintf(level, "%s %s (%" PRIu32 ")\n",
165 msg, timestr, time);
ba37674d
EH
166 }
167}
168
169static void
170initname(char *setname) {
171 isc_result_t result;
172 isc_buffer_t buf;
173
4df4a8e7 174 name = dns_fixedname_initname(&fixed);
ba37674d
EH
175 namestr = setname;
176
177 isc_buffer_init(&buf, setname, strlen(setname));
178 isc_buffer_add(&buf, strlen(setname));
179 result = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL);
180 if (result != ISC_R_SUCCESS) {
181 fatal("could not initialize name %s", setname);
182 }
183}
184
185static void
186findset(dns_db_t *db, dns_dbnode_t *node, dns_rdatatype_t type,
187 dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset)
188{
189 isc_result_t result;
190
191 dns_rdataset_init(rdataset);
192 if (sigrdataset != NULL) {
193 dns_rdataset_init(sigrdataset);
194 }
195 result = dns_db_findrdataset(db, node, NULL, type, 0, 0,
196 rdataset, sigrdataset);
197 if (result != ISC_R_NOTFOUND) {
198 check_result(result, "dns_db_findrdataset()");
199 }
200}
201
202static void
203freeset(dns_rdataset_t *rdataset) {
204 if (dns_rdataset_isassociated(rdataset)) {
205 dns_rdataset_disassociate(rdataset);
206 }
207}
208
209static void
210freelist(dns_rdataset_t *rdataset) {
211 dns_rdatalist_t *rdlist;
212 dns_rdata_t *rdata;
213
214 if (!dns_rdataset_isassociated(rdataset)) {
215 return;
216 }
217
218 dns_rdatalist_fromrdataset(rdataset, &rdlist);
219
220 for (rdata = ISC_LIST_HEAD(rdlist->rdata);
221 rdata != NULL;
222 rdata = ISC_LIST_HEAD(rdlist->rdata))
223 {
224 ISC_LIST_UNLINK(rdlist->rdata, rdata, link);
225 isc_mem_put(mctx, rdata, sizeof(*rdata));
226 }
227 isc_mem_put(mctx, rdlist, sizeof(*rdlist));
228 dns_rdataset_disassociate(rdataset);
229}
230
231static void
232free_all_sets(void) {
233 freeset(&cdnskey_set);
234 freeset(&cdnskey_sig);
235 freeset(&cds_set);
236 freeset(&cds_sig);
237 freeset(&dnskey_set);
238 freeset(&dnskey_sig);
239 freeset(&old_ds_set);
240 freelist(&new_ds_set);
241 if (new_ds_buf != NULL) {
242 isc_buffer_free(&new_ds_buf);
243 }
244}
245
246static void
247load_db(const char *filename, dns_db_t **dbp, dns_dbnode_t **nodep) {
248 isc_result_t result;
249
250 result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone,
251 rdclass, 0, NULL, dbp);
252 check_result(result, "dns_db_create()");
253
275a6a3b
WK
254 result = dns_db_load(*dbp, filename,
255 dns_masterformat_text, DNS_MASTER_HINT);
ba37674d
EH
256 if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) {
257 fatal("can't load %s: %s", filename,
258 isc_result_totext(result));
259 }
260
994e6569 261 result = dns_db_findnode(*dbp, name, false, nodep);
ba37674d
EH
262 if (result != ISC_R_SUCCESS) {
263 fatal("can't find %s node in %s", namestr, filename);
264 }
265}
266
267static void
268free_db(dns_db_t **dbp, dns_dbnode_t **nodep) {
269 dns_db_detachnode(*dbp, nodep);
270 dns_db_detach(dbp);
271}
272
273static void
274load_child_sets(const char *file) {
275 dns_db_t *db = NULL;
276 dns_dbnode_t *node = NULL;
277
278 load_db(file, &db, &node);
279 findset(db, node, dns_rdatatype_dnskey, &dnskey_set, &dnskey_sig);
280 findset(db, node, dns_rdatatype_cdnskey, &cdnskey_set, &cdnskey_sig);
281 findset(db, node, dns_rdatatype_cds, &cds_set, &cds_sig);
282 free_db(&db, &node);
283}
284
285static void
286get_dsset_name(char *filename, size_t size,
287 const char *path, const char *suffix)
288{
289 isc_result_t result;
290 isc_buffer_t buf;
291 size_t len;
292
293 isc_buffer_init(&buf, filename, size);
294
295 len = strlen(path);
296
297 /* allow room for a trailing slash */
298 if (isc_buffer_availablelength(&buf) <= len) {
299 fatal("%s: pathname too long", path);
300 }
301 isc_buffer_putstr(&buf, path);
302
303 if (isc_file_isdirectory(path) == ISC_R_SUCCESS) {
304 const char *prefix = "dsset-";
305
306 if (path[len - 1] != '/') {
307 isc_buffer_putstr(&buf, "/");
308 }
309
310 if (isc_buffer_availablelength(&buf) < strlen(prefix)) {
311 fatal("%s: pathname too long", path);
312 }
313 isc_buffer_putstr(&buf, prefix);
314
994e6569 315 result = dns_name_tofilenametext(name, false, &buf);
ba37674d
EH
316 check_result(result, "dns_name_tofilenametext()");
317 if (isc_buffer_availablelength(&buf) == 0) {
318 fatal("%s: pathname too long", path);
319 }
320 }
321 /* allow room for a trailing nul */
322 if (isc_buffer_availablelength(&buf) <= strlen(suffix)) {
323 fatal("%s: pathname too long", path);
324 }
325 isc_buffer_putstr(&buf, suffix);
326 isc_buffer_putuint8(&buf, 0);
327}
328
329static void
330load_parent_set(const char *path) {
331 isc_result_t result;
332 dns_db_t *db = NULL;
333 dns_dbnode_t *node = NULL;
334 isc_time_t modtime;
335 char filename[PATH_MAX + 1];
336
337 get_dsset_name(filename, sizeof(filename), path, "");
338
339 result = isc_file_getmodtime(filename, &modtime);
340 if (result != ISC_R_SUCCESS) {
341 fatal("could not get modification time of %s: %s",
342 filename, isc_result_totext(result));
343 }
344 notbefore = isc_time_seconds(&modtime);
345 if (startstr != NULL) {
346 isc_stdtime_t now;
347 isc_stdtime_get(&now);
348 notbefore = strtotime(startstr, now, notbefore, NULL);
349 }
350 verbose_time(1, "child records must not be signed before", notbefore);
351
352 load_db(filename, &db, &node);
353 findset(db, node, dns_rdatatype_ds, &old_ds_set, NULL);
354
355 if (!dns_rdataset_isassociated(&old_ds_set)) {
356 fatal("could not find DS records for %s in %s",
357 namestr, filename);
358 }
359
360 free_db(&db, &node);
361}
362
5df3f839
MA
363#define MAX_CDS_RDATA_TEXT_SIZE DNS_RDATA_MAXLENGTH * 2
364
ba37674d
EH
365static isc_buffer_t *
366formatset(dns_rdataset_t *rdataset) {
367 isc_result_t result;
368 isc_buffer_t *buf = NULL;
369 dns_master_style_t *style = NULL;
370 unsigned int styleflags;
ba37674d
EH
371
372 styleflags = (rdataset->ttl == 0) ? DNS_STYLEFLAG_NO_TTL : 0;
373
374 /*
375 * This style is for consistency with the output of dnssec-dsfromkey
376 * which just separates fields with spaces. The huge tab stop width
377 * eliminates any tab characters.
378 */
e2a06db7
WK
379 result = dns_master_stylecreate(&style, styleflags,
380 0, 0, 0, 0, 0, 1000000, 0,
381 mctx);
5df3f839 382 check_result(result, "dns_master_stylecreate2 failed");
ba37674d 383
5df3f839
MA
384 result = isc_buffer_allocate(mctx, &buf, MAX_CDS_RDATA_TEXT_SIZE);
385 check_result(result, "printing DS records");
386 result = dns_master_rdatasettotext(name, rdataset, style, buf);
387
388 if ((result == ISC_R_SUCCESS) && isc_buffer_availablelength(buf) < 1) {
389 result = ISC_R_NOSPACE;
390 }
391
392 check_result(result, "dns_rdataset_totext()");
ba37674d
EH
393
394 isc_buffer_putuint8(buf, 0);
395
396 dns_master_styledestroy(&style, mctx);
397
398 return (buf);
399}
400
401static void
402write_parent_set(const char *path, const char *inplace,
994e6569 403 bool nsupdate, dns_rdataset_t *rdataset)
ba37674d
EH
404{
405 isc_result_t result;
406 isc_buffer_t *buf = NULL;
407 isc_region_t r;
408 isc_time_t filetime;
409 char backname[PATH_MAX + 1];
410 char filename[PATH_MAX + 1];
411 char tmpname[PATH_MAX + 1];
412 FILE *fp = NULL;
413
414 if (nsupdate && inplace == NULL) {
415 return;
416 }
417
418 buf = formatset(rdataset);
419 isc_buffer_usedregion(buf, &r);
420
421 /*
422 * Try to ensure a write error doesn't make a zone go insecure!
423 */
424 if (inplace == NULL) {
425 printf("%s", (char *)r.base);
426 isc_buffer_free(&buf);
427 if (fflush(stdout) == EOF) {
428 fatal("error writing to stdout: %s", strerror(errno));
429 }
430 return;
431 }
432
433 if (inplace[0] != '\0') {
434 get_dsset_name(backname, sizeof(backname), path, inplace);
435 }
436 get_dsset_name(filename, sizeof(filename), path, "");
437 get_dsset_name(tmpname, sizeof(tmpname), path, "-XXXXXXXXXX");
438
439 result = isc_file_openunique(tmpname, &fp);
440 if (result != ISC_R_SUCCESS) {
441 fatal("open %s: %s", tmpname, isc_result_totext(result));
442 }
443 fprintf(fp, "%s", (char *)r.base);
444 isc_buffer_free(&buf);
445 if (fclose(fp) == EOF) {
3f3b51e7 446 int err = errno;
ba37674d 447 isc_file_remove(tmpname);
3f3b51e7 448 fatal("error writing to %s: %s", tmpname, strerror(err));
ba37674d
EH
449 }
450
451 isc_time_set(&filetime, oldestsig.timesigned, 0);
452 result = isc_file_settime(tmpname, &filetime);
453 if (result != ISC_R_SUCCESS) {
454 isc_file_remove(tmpname);
455 fatal("can't set modification time of %s: %s",
456 tmpname, isc_result_totext(result));
457 }
458
459 if (inplace[0] != '\0') {
460 isc_file_rename(filename, backname);
461 }
462 isc_file_rename(tmpname, filename);
463}
464
465typedef enum { LOOSE, TIGHT } strictness_t;
466
467/*
468 * Find out if any (C)DS record matches a particular (C)DNSKEY.
469 */
994e6569 470static bool
ba37674d
EH
471match_key_dsset(keyinfo_t *ki, dns_rdataset_t *dsset, strictness_t strictness)
472{
473 isc_result_t result;
474 unsigned char dsbuf[DNS_DS_BUFFERSIZE];
475
476 for (result = dns_rdataset_first(dsset);
477 result == ISC_R_SUCCESS;
478 result = dns_rdataset_next(dsset))
479 {
480 dns_rdata_ds_t ds;
481 dns_rdata_t dsrdata = DNS_RDATA_INIT;
482 dns_rdata_t newdsrdata = DNS_RDATA_INIT;
994e6569 483 bool c;
ba37674d
EH
484
485 dns_rdataset_current(dsset, &dsrdata);
486 result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
487 check_result(result, "dns_rdata_tostruct(DS)");
488
489 if (ki->tag != ds.key_tag || ki->algo != ds.algorithm) {
490 continue;
491 }
492
ba37674d
EH
493 result = dns_ds_buildrdata(name, &ki->rdata, ds.digest_type,
494 dsbuf, &newdsrdata);
ba37674d
EH
495 if (result != ISC_R_SUCCESS) {
496 vbprintf(3, "dns_ds_buildrdata("
497 "keytag=%d, algo=%d, digest=%d): %s\n",
498 ds.key_tag, ds.algorithm, ds.digest_type,
499 dns_result_totext(result));
500 continue;
501 }
502 /* allow for both DS and CDS */
503 c = dsrdata.type != dns_rdatatype_ds;
504 dsrdata.type = dns_rdatatype_ds;
505 if (dns_rdata_compare(&dsrdata, &newdsrdata) == 0) {
506 vbprintf(1, "found matching %s %d %d %d\n",
507 c ? "CDS" : "DS",
508 ds.key_tag, ds.algorithm, ds.digest_type);
994e6569 509 return (true);
ba37674d
EH
510 } else if (strictness == TIGHT) {
511 vbprintf(0, "key does not match %s %d %d %d "
512 "when it looks like it should\n",
513 c ? "CDS" : "DS",
514 ds.key_tag, ds.algorithm, ds.digest_type);
994e6569 515 return (false);
ba37674d
EH
516 }
517 }
518
d36b7f86
EH
519 vbprintf(1, "no matching %s for %s %d %d\n",
520 dsset->type == dns_rdatatype_cds
521 ? "CDS" : "DS",
522 ki->rdata.type == dns_rdatatype_cdnskey
523 ? "CDNSKEY" : "DNSKEY",
524 ki->tag, ki->algo);
525
994e6569 526 return (false);
ba37674d
EH
527}
528
529/*
530 * Find which (C)DNSKEY records match a (C)DS RRset.
531 * This creates a keyinfo_t key_tbl[nkey] array.
532 */
533static keyinfo_t *
534match_keyset_dsset(dns_rdataset_t *keyset, dns_rdataset_t *dsset,
535 strictness_t strictness)
536{
537 isc_result_t result;
538 keyinfo_t *keytable;
539 int i;
540
541 nkey = dns_rdataset_count(keyset);
542
543 keytable = isc_mem_get(mctx, sizeof(keyinfo_t) * nkey);
544 if (keytable == NULL) {
545 fatal("out of memory");
546 }
547
548 for (result = dns_rdataset_first(keyset), i = 0;
549 result == ISC_R_SUCCESS;
550 result = dns_rdataset_next(keyset), i++)
551 {
552 keyinfo_t *ki;
553 dns_rdata_dnskey_t dnskey;
554 dns_rdata_t *keyrdata;
555 isc_region_t r;
556
557 INSIST(i < nkey);
558 ki = &keytable[i];
559 keyrdata = &ki->rdata;
560
561 dns_rdata_init(keyrdata);
562 dns_rdataset_current(keyset, keyrdata);
563
564 result = dns_rdata_tostruct(keyrdata, &dnskey, NULL);
565 check_result(result, "dns_rdata_tostruct(DNSKEY)");
566 ki->algo = dnskey.algorithm;
567
568 dns_rdata_toregion(keyrdata, &r);
e69dc0db 569 ki->tag = dst_region_computeid(&r);
ba37674d
EH
570
571 ki->dst = NULL;
572 if (!match_key_dsset(ki, dsset, strictness)) {
573 continue;
574 }
575
576 result = dns_dnssec_keyfromrdata(name, keyrdata,
577 mctx, &ki->dst);
578 if (result != ISC_R_SUCCESS) {
579 vbprintf(3, "dns_dnssec_keyfromrdata("
580 "keytag=%d, algo=%d): %s\n",
581 ki->tag, ki->algo,
582 dns_result_totext(result));
583 }
584 }
585
586 return (keytable);
587}
588
589static void
590free_keytable(keyinfo_t **keytable_p) {
591 keyinfo_t *keytable = *keytable_p;
592 keyinfo_t *ki;
593 int i;
594
595 for (i = 0; i < nkey; i++) {
596 ki = &keytable[i];
597 if (ki->dst != NULL) {
598 dst_key_free(&ki->dst);
599 }
600 }
601
602 isc_mem_put(mctx, keytable, sizeof(keyinfo_t) * nkey);
603 *keytable_p = NULL;
604}
605
606/*
607 * Find out which keys have signed an RRset. Keys that do not match a
608 * DS record are skipped.
609 *
610 * The return value is an array with nkey elements, one for each key,
611 * either zero if the key was skipped or did not sign the RRset, or
612 * otherwise the key algorithm. This is used by the signature coverage
613 * check functions below.
614 */
0f219714 615static dns_secalg_t *
ba37674d
EH
616matching_sigs(keyinfo_t *keytbl, dns_rdataset_t *rdataset,
617 dns_rdataset_t *sigset)
618{
619 isc_result_t result;
0f219714 620 dns_secalg_t *algo;
ba37674d
EH
621 int i;
622
623 algo = isc_mem_get(mctx, nkey);
624 if (algo == NULL) {
625 fatal("allocating RRSIG/DNSKEY match list: %s",
626 isc_result_totext(ISC_R_NOMEMORY));
627 }
628 memset(algo, 0, nkey);
629
630 for (result = dns_rdataset_first(sigset);
631 result == ISC_R_SUCCESS;
632 result = dns_rdataset_next(sigset))
633 {
634 dns_rdata_t sigrdata = DNS_RDATA_INIT;
635 dns_rdata_rrsig_t sig;
636
637 dns_rdataset_current(sigset, &sigrdata);
638 result = dns_rdata_tostruct(&sigrdata, &sig, NULL);
639 check_result(result, "dns_rdata_tostruct(RRSIG)");
640
641 /*
642 * Replay attack protection: check against current age limit
643 */
644 if (isc_serial_lt(sig.timesigned, notbefore)) {
645 vbprintf(1, "skip RRSIG by key %d: too old\n",
646 sig.keyid);
647 continue;
648 }
649
650 for (i = 0; i < nkey; i++) {
651 keyinfo_t *ki = &keytbl[i];
d36b7f86 652 if (sig.keyid != ki->tag ||
ba37674d
EH
653 sig.algorithm != ki->algo ||
654 !dns_name_equal(&sig.signer, name))
655 {
656 continue;
657 }
d36b7f86
EH
658 if (ki->dst == NULL) {
659 vbprintf(1, "skip RRSIG by key %d:"
660 " no matching (C)DS\n",
661 sig.keyid);
662 continue;
663 }
ba37674d
EH
664
665 result = dns_dnssec_verify(name, rdataset, ki->dst,
994e6569 666 false, 0, mctx,
25cd3168
WK
667 &sigrdata, NULL);
668
669 if (result != ISC_R_SUCCESS &&
670 result != DNS_R_FROMWILDCARD) {
d36b7f86
EH
671 vbprintf(1, "skip RRSIG by key %d:"
672 " verification failed: %s\n",
673 sig.keyid, isc_result_totext(result));
ba37674d
EH
674 continue;
675 }
676
677 vbprintf(1, "found RRSIG by key %d\n", ki->tag);
678 algo[i] = sig.algorithm;
679
680 /*
681 * Replay attack protection: work out next age limit,
682 * only after the signature has been verified
683 */
684 if (oldestsig.timesigned == 0 ||
685 isc_serial_lt(sig.timesigned,
686 oldestsig.timesigned))
687 {
688 verbose_time(2, "this is the oldest so far",
689 sig.timesigned);
690 oldestsig = sig;
691 }
692 }
693 }
694
695 return (algo);
696}
697
698/*
699 * Consume the result of matching_sigs(). When checking records
700 * fetched from the child zone, any working signature is enough.
701 */
994e6569 702static bool
0f219714 703signed_loose(dns_secalg_t *algo) {
994e6569 704 bool ok = false;
ba37674d
EH
705 int i;
706 for (i = 0; i < nkey; i++) {
707 if (algo[i] != 0) {
994e6569 708 ok = true;
ba37674d
EH
709 }
710 }
711 isc_mem_put(mctx, algo, nkey);
712 return (ok);
713}
714
715/*
716 * Consume the result of matching_sigs(). To ensure that the new DS
717 * RRset does not break the chain of trust to the DNSKEY RRset, every
718 * key algorithm in the DS RRset must have a signature in the DNSKEY
719 * RRset.
720 */
994e6569 721static bool
0f219714 722signed_strict(dns_rdataset_t *dsset, dns_secalg_t *algo) {
ba37674d 723 isc_result_t result;
994e6569 724 bool all_ok = true;
ba37674d
EH
725
726 for (result = dns_rdataset_first(dsset);
727 result == ISC_R_SUCCESS;
728 result = dns_rdataset_next(dsset))
729 {
730 dns_rdata_t dsrdata = DNS_RDATA_INIT;
731 dns_rdata_ds_t ds;
994e6569 732 bool ds_ok;
ba37674d
EH
733 int i;
734
735 dns_rdataset_current(dsset, &dsrdata);
736 result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
737 check_result(result, "dns_rdata_tostruct(DS)");
738
994e6569 739 ds_ok = false;
ba37674d
EH
740 for (i = 0; i < nkey; i++) {
741 if (algo[i] == ds.algorithm) {
994e6569 742 ds_ok = true;
ba37674d
EH
743 }
744 }
745 if (!ds_ok) {
746 vbprintf(0, "missing signature for algorithm %d "
747 "(key %d)\n", ds.algorithm, ds.key_tag);
994e6569 748 all_ok = false;
ba37674d
EH
749 }
750 }
751
752 isc_mem_put(mctx, algo, nkey);
753 return (all_ok);
754}
755
756static dns_rdata_t *
757rdata_get(void) {
758 dns_rdata_t *rdata;
759
760 rdata = isc_mem_get(mctx, sizeof(*rdata));
761 if (rdata == NULL) {
762 fatal("allocating DS rdata: %s",
763 isc_result_totext(ISC_R_NOMEMORY));
764 }
765 dns_rdata_init(rdata);
766
767 return (rdata);
768}
769
770static isc_result_t
771rdata_put(isc_result_t result, dns_rdatalist_t *rdlist, dns_rdata_t *rdata) {
772 if (result == ISC_R_SUCCESS) {
773 ISC_LIST_APPEND(rdlist->rdata, rdata, link);
774 } else {
775 isc_mem_put(mctx, rdata, sizeof(*rdata));
776 }
777
778 return (result);
779}
780
781/*
782 * This basically copies the rdata into the buffer, but going via the
783 * unpacked struct has the side-effect of changing the rdatatype. The
784 * dns_rdata_cds_t and dns_rdata_ds_t types are aliases.
785 */
786static isc_result_t
787ds_from_cds(dns_rdatalist_t *dslist, isc_buffer_t *buf, dns_rdata_t *cds) {
788 isc_result_t result;
789 dns_rdata_ds_t ds;
790 dns_rdata_t *rdata;
791
792 rdata = rdata_get();
793
794 result = dns_rdata_tostruct(cds, &ds, NULL);
795 check_result(result, "dns_rdata_tostruct(CDS)");
796 ds.common.rdtype = dns_rdatatype_ds;
797
798 result = dns_rdata_fromstruct(rdata, rdclass, dns_rdatatype_ds,
799 &ds, buf);
800
801 return (rdata_put(result, dslist, rdata));
802}
803
804static isc_result_t
805ds_from_cdnskey(dns_rdatalist_t *dslist, isc_buffer_t *buf,
806 dns_rdata_t *cdnskey)
807{
808 isc_result_t result;
809 unsigned i, n;
810
811 n = sizeof(dtype)/sizeof(dtype[0]);
812 for (i = 0; i < n; i++) {
813 if (dtype[i] != 0) {
814 dns_rdata_t *rdata;
815 isc_region_t r;
816
817 isc_buffer_availableregion(buf, &r);
818 if (r.length < DNS_DS_BUFFERSIZE) {
819 return (ISC_R_NOSPACE);
820 }
821
ba37674d
EH
822 rdata = rdata_get();
823 result = dns_ds_buildrdata(name, cdnskey, dtype[i],
824 r.base, rdata);
825 if (result == ISC_R_SUCCESS) {
826 isc_buffer_add(buf, DNS_DS_BUFFERSIZE);
827 }
828
829 result = rdata_put(result, dslist, rdata);
830 if (result != ISC_R_SUCCESS) {
831 return (result);
832 }
833 }
834 }
835
836 return (ISC_R_SUCCESS);
837}
838
839/*
840 * For sorting the digest types so that DS records generated
841 * from CDNSKEY records are in canonical order.
842 */
843static int
844cmp_dtype(const void *ap, const void *bp) {
0f219714
TF
845 int a = *(const dns_dsdigest_t *)ap;
846 int b = *(const dns_dsdigest_t *)bp;
ba37674d
EH
847 return (a - b);
848}
849
850static void
851add_dtype(const char *dn) {
0f219714 852 dns_dsdigest_t dt;
ba37674d
EH
853 unsigned i, n;
854
855 dt = strtodsdigest(dn);
856 n = sizeof(dtype)/sizeof(dtype[0]);
857 for (i = 0; i < n; i++) {
858 if (dtype[i] == 0 || dtype[i] == dt) {
859 dtype[i] = dt;
860 qsort(dtype, i+1, 1, cmp_dtype);
861 return;
862 }
863 }
864 fatal("too many -a digest type arguments");
865}
866
867static void
868make_new_ds_set(ds_maker_func_t *ds_from_rdata,
cb6a185c 869 uint32_t ttl, dns_rdataset_t *rdset)
ba37674d
EH
870{
871 unsigned int size = 16;
872 for (;;) {
873 isc_result_t result;
874 dns_rdatalist_t *dslist;
875
876 dslist = isc_mem_get(mctx, sizeof(*dslist));
877 if (dslist == NULL) {
878 fatal("allocating new DS list: %s",
879 isc_result_totext(ISC_R_NOMEMORY));
880 }
881
882 dns_rdatalist_init(dslist);
883 dslist->rdclass = rdclass;
884 dslist->type = dns_rdatatype_ds;
885 dslist->ttl = ttl;
886
887 dns_rdataset_init(&new_ds_set);
888 result = dns_rdatalist_tordataset(dslist, &new_ds_set);
889 check_result(result, "dns_rdatalist_tordataset(dslist)");
890
891 result = isc_buffer_allocate(mctx, &new_ds_buf, size);
892 check_result(result, "building new DS records");
893
894 for (result = dns_rdataset_first(rdset);
895 result == ISC_R_SUCCESS;
896 result = dns_rdataset_next(rdset))
897 {
898 isc_result_t tresult;
899 dns_rdata_t rdata = DNS_RDATA_INIT;
900
901 dns_rdataset_current(rdset, &rdata);
902
903 tresult = ds_from_rdata(dslist, new_ds_buf, &rdata);
904 if (tresult == ISC_R_NOSPACE) {
905 vbprintf(20, "DS list buffer size %u\n", size);
906 freelist(&new_ds_set);
907 isc_buffer_free(&new_ds_buf);
908 size *= 2;
909 break;
910 }
911
912 check_result(tresult, "ds_from_rdata()");
913 }
914
915 if (result == ISC_R_NOMORE) {
916 break;
917 }
918 }
919}
920
921static inline int
922rdata_cmp(const void *rdata1, const void *rdata2) {
923 return (dns_rdata_compare((const dns_rdata_t *)rdata1,
924 (const dns_rdata_t *)rdata2));
925}
926
927/*
928 * Ensure that every key identified by the DS RRset has the same set of
929 * digest types.
930 */
994e6569 931static bool
ba37674d
EH
932consistent_digests(dns_rdataset_t *dsset) {
933 isc_result_t result;
934 dns_rdata_t *arrdata;
935 dns_rdata_ds_t *ds;
936 dns_keytag_t key_tag;
0f219714 937 dns_secalg_t algorithm;
994e6569 938 bool match;
ba37674d
EH
939 int i, j, n, d;
940
941 /*
942 * First sort the dsset. DS rdata fields are tag, algorithm, digest,
943 * so sorting them brings together all the records for each key.
944 */
945
946 n = dns_rdataset_count(dsset);
947
948 arrdata = isc_mem_get(mctx, n * sizeof(dns_rdata_t));
949 if (arrdata == NULL) {
950 fatal("allocating DS rdata array: %s",
951 isc_result_totext(ISC_R_NOMEMORY));
952 }
953
954 for (result = dns_rdataset_first(dsset), i = 0;
955 result == ISC_R_SUCCESS;
956 result = dns_rdataset_next(dsset), i++)
957 {
958 dns_rdata_init(&arrdata[i]);
959 dns_rdataset_current(dsset, &arrdata[i]);
960 }
961
962 qsort(arrdata, n, sizeof(dns_rdata_t), rdata_cmp);
963
964 /*
965 * Convert sorted arrdata to more accessible format
966 */
967 ds = isc_mem_get(mctx, n * sizeof(dns_rdata_ds_t));
968 if (ds == NULL) {
969 fatal("allocating unpacked DS array: %s",
970 isc_result_totext(ISC_R_NOMEMORY));
971 }
972
973 for (i = 0; i < n; i++) {
974 result = dns_rdata_tostruct(&arrdata[i], &ds[i], NULL);
975 check_result(result, "dns_rdata_tostruct(DS)");
976 }
977
978 /*
979 * Count number of digest types (d) for first key
980 */
981 key_tag = ds[0].key_tag;
982 algorithm = ds[0].algorithm;
983 for (d = 0, i = 0; i < n; i++, d++) {
984 if (ds[i].key_tag != key_tag || ds[i].algorithm != algorithm) {
985 break;
986 }
987 }
988
989 /*
990 * Check subsequent keys match the first one
991 */
994e6569 992 match = true;
ba37674d
EH
993 while (i < n) {
994 key_tag = ds[i].key_tag;
995 algorithm = ds[i].algorithm;
996 for (j = 0; j < d && i+j < n; j++) {
997 if (ds[i+j].key_tag != key_tag ||
998 ds[i+j].algorithm != algorithm ||
999 ds[i+j].digest_type != ds[j].digest_type)
1000 {
994e6569 1001 match = false;
ba37674d
EH
1002 }
1003 }
1004 i += d;
1005 }
1006
1007 /*
1008 * Done!
1009 */
1010 isc_mem_put(mctx, ds, n * sizeof(dns_rdata_ds_t));
1011 isc_mem_put(mctx, arrdata, n * sizeof(dns_rdata_t));
1012
1013 return (match);
1014}
1015
1016static void
1017print_diff(const char *cmd, dns_rdataset_t *rdataset) {
1018 isc_buffer_t *buf;
1019 isc_region_t r;
1020 unsigned char *nl;
1021 size_t len;
1022
1023 buf = formatset(rdataset);
1024 isc_buffer_usedregion(buf, &r);
1025
1026 while ((nl = memchr(r.base, '\n', r.length)) != NULL) {
1027 len = nl - r.base + 1;
1028 printf("update %s %.*s", cmd, (int)len, (char *)r.base);
1029 isc_region_consume(&r, len);
1030 }
1031
1032 isc_buffer_free(&buf);
1033}
1034
1035static void
cb6a185c 1036update_diff(const char *cmd, uint32_t ttl,
ba37674d
EH
1037 dns_rdataset_t *addset, dns_rdataset_t *delset)
1038{
1039 isc_result_t result;
1040 dns_db_t *db;
1041 dns_dbnode_t *node;
1042 dns_dbversion_t *ver;
1043 dns_rdataset_t diffset;
cb6a185c 1044 uint32_t save;
ba37674d
EH
1045
1046 db = NULL;
1047 result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone,
1048 rdclass, 0, NULL, &db);
1049 check_result(result, "dns_db_create()");
1050
1051 ver = NULL;
1052 result = dns_db_newversion(db, &ver);
1053 check_result(result, "dns_db_newversion()");
1054
1055 node = NULL;
994e6569 1056 result = dns_db_findnode(db, name, true, &node);
ba37674d
EH
1057 check_result(result, "dns_db_findnode()");
1058
1059 dns_rdataset_init(&diffset);
1060
1061 result = dns_db_addrdataset(db, node, ver, 0, addset,
1062 DNS_DBADD_MERGE, NULL);
1063 check_result(result, "dns_db_addrdataset()");
1064
1065 result = dns_db_subtractrdataset(db, node, ver, delset,
1066 0, &diffset);
1067 if (result == DNS_R_UNCHANGED) {
1068 save = addset->ttl;
1069 addset->ttl = ttl;
1070 print_diff(cmd, addset);
1071 addset->ttl = save;
1072 } else if (result != DNS_R_NXRRSET) {
1073 check_result(result, "dns_db_subtractrdataset()");
1074 diffset.ttl = ttl;
1075 print_diff(cmd, &diffset);
1076 dns_rdataset_disassociate(&diffset);
1077 }
1078
1079 dns_db_detachnode(db, &node);
994e6569 1080 dns_db_closeversion(db, &ver, false);
ba37674d
EH
1081 dns_db_detach(&db);
1082}
1083
1084static void
cb6a185c 1085nsdiff(uint32_t ttl, dns_rdataset_t *oldset, dns_rdataset_t *newset) {
ba37674d
EH
1086 if (ttl == 0) {
1087 vbprintf(1, "warning: no TTL in nsupdate script\n");
1088 }
1089 update_diff("add", ttl, newset, oldset);
1090 update_diff("del", 0, oldset, newset);
1091 if (verbose > 0) {
1092 printf("show\nsend\nanswer\n");
1093 } else {
1094 printf("send\n");
1095 }
1096 if (fflush(stdout) == EOF) {
1097 fatal("write stdout: %s", strerror(errno));
1098 }
1099}
1100
1101ISC_PLATFORM_NORETURN_PRE static void
1102usage(void) ISC_PLATFORM_NORETURN_POST;
1103
1104static void
1105usage(void) {
1106 fprintf(stderr, "Usage:\n");
1107 fprintf(stderr,
1108 " %s options [options] -f <file> -d <path> <domain>\n",
1109 program);
1110 fprintf(stderr, "Version: %s\n", VERSION);
1111 fprintf(stderr, "Options:\n"
27593e65 1112" -a <algorithm> digest algorithm (SHA-1 / SHA-256 / SHA-384)\n"
ba37674d
EH
1113" -c <class> of domain (default IN)\n"
1114" -D prefer CDNSKEY records instead of CDS\n"
1115" -d <file|dir> where to find parent dsset- file\n"
1116" -f <file> child DNSKEY+CDNSKEY+CDS+RRSIG records\n"
1117" -i[extension] update dsset- file in place\n"
1118" -s <start-time> oldest permitted child signatures\n"
1119" -u emit nsupdate script\n"
1120" -T <ttl> TTL of DS records\n"
1121" -V print version\n"
1122" -v <verbosity>\n"
1123 );
1124 exit(1);
1125}
1126
1127int
1128main(int argc, char *argv[]) {
1129 const char *child_path = NULL;
1130 const char *ds_path = NULL;
1131 const char *inplace = NULL;
1132 isc_result_t result;
994e6569
OS
1133 bool prefer_cdnskey = false;
1134 bool nsupdate = false;
cb6a185c 1135 uint32_t ttl = 0;
ba37674d
EH
1136 int ch;
1137 char *endp;
1138
1139 result = isc_mem_create(0, 0, &mctx);
1140 if (result != ISC_R_SUCCESS) {
1141 fatal("out of memory");
1142 }
1143
c3b8130f 1144#if USE_PKCS11
ba37674d
EH
1145 pk11_result_register();
1146#endif
1147 dns_result_register();
1148
994e6569 1149 isc_commandline_errprint = false;
ba37674d
EH
1150
1151#define OPTIONS "a:c:Dd:f:i:ms:T:uv:V"
1152 while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) {
1153 switch (ch) {
1154 case 'a':
1155 add_dtype(isc_commandline_argument);
1156 break;
1157 case 'c':
1158 rdclass = strtoclass(isc_commandline_argument);
1159 break;
1160 case 'D':
994e6569 1161 prefer_cdnskey = true;
ba37674d
EH
1162 break;
1163 case 'd':
1164 ds_path = isc_commandline_argument;
1165 break;
1166 case 'f':
1167 child_path = isc_commandline_argument;
1168 break;
1169 case 'i':
1170 /*
1171 * This is a bodge to make the argument optional,
1172 * so that it works just like sed(1).
1173 */
1174 if (isc_commandline_argument ==
1175 argv[isc_commandline_index - 1])
1176 {
1177 isc_commandline_index--;
1178 inplace = "";
1179 } else {
1180 inplace = isc_commandline_argument;
1181 }
1182 break;
1183 case 'm':
1184 isc_mem_debugging = ISC_MEM_DEBUGTRACE |
1185 ISC_MEM_DEBUGRECORD;
1186 break;
1187 case 's':
1188 startstr = isc_commandline_argument;
1189 break;
1190 case 'T':
1191 ttl = strtottl(isc_commandline_argument);
1192 break;
1193 case 'u':
994e6569 1194 nsupdate = true;
ba37674d
EH
1195 break;
1196 case 'V':
1197 /* Does not return. */
1198 version(program);
1199 break;
1200 case 'v':
1201 verbose = strtoul(isc_commandline_argument, &endp, 0);
1202 if (*endp != '\0') {
1203 fatal("-v must be followed by a number");
1204 }
1205 break;
1206 default:
1207 usage();
1208 break;
1209 }
1210 }
1211 argv += isc_commandline_index;
1212 argc -= isc_commandline_index;
1213
1214 if (argc != 1) {
1215 usage();
1216 }
1217 initname(argv[0]);
1218
1219 /*
1220 * Default digest type if none specified.
1221 */
1222 if (dtype[0] == 0) {
1223 dtype[0] = DNS_DSDIGEST_SHA256;
1224 }
1225
1226 setup_logging(mctx, &lctx);
1227
3a4f820d 1228 result = dst_lib_init(mctx, NULL);
ba37674d
EH
1229 if (result != ISC_R_SUCCESS) {
1230 fatal("could not initialize dst: %s",
1231 isc_result_totext(result));
1232 }
ba37674d
EH
1233
1234 if (ds_path == NULL) {
1235 fatal("missing -d DS pathname");
1236 }
1237 load_parent_set(ds_path);
1238
1239 /*
1240 * Preserve the TTL if it wasn't overridden.
1241 */
1242 if (ttl == 0) {
1243 ttl = old_ds_set.ttl;
1244 }
1245
1246 if (child_path == NULL) {
1247 fatal("path to file containing child data must be specified");
1248 }
1249
1250 load_child_sets(child_path);
1251
1252 /*
1253 * Check child records have accompanying RRSIGs and DNSKEYs
1254 */
1255
1256 if (!dns_rdataset_isassociated(&dnskey_set) ||
1257 !dns_rdataset_isassociated(&dnskey_sig))
1258 {
1259 fatal("could not find signed DNSKEY RRset for %s", namestr);
1260 }
1261
1262 if (dns_rdataset_isassociated(&cdnskey_set) &&
1263 !dns_rdataset_isassociated(&cdnskey_sig))
1264 {
1265 fatal("missing RRSIG CDNSKEY records for %s", namestr);
1266 }
1267 if (dns_rdataset_isassociated(&cds_set) &&
1268 !dns_rdataset_isassociated(&cds_sig))
1269 {
1270 fatal("missing RRSIG CDS records for %s", namestr);
1271 }
1272
1273 vbprintf(1, "which child DNSKEY records match parent DS records?\n");
1274 old_key_tbl = match_keyset_dsset(&dnskey_set, &old_ds_set, LOOSE);
1275
1276 /*
1277 * We have now identified the keys that are allowed to authenticate
1278 * the DNSKEY RRset (RFC 4035 section 5.2 bullet 2), and CDNSKEY and
1279 * CDS RRsets (RFC 7344 section 4.1 bullet 2).
1280 */
1281
1282 vbprintf(1, "verify DNSKEY signature(s)\n");
1283 if (!signed_loose(matching_sigs(old_key_tbl, &dnskey_set, &dnskey_sig)))
1284 {
1285 fatal("could not validate child DNSKEY RRset for %s", namestr);
1286 }
1287
1288 if (dns_rdataset_isassociated(&cdnskey_set)) {
1289 vbprintf(1, "verify CDNSKEY signature(s)\n");
1290 if (!signed_loose(matching_sigs(old_key_tbl,
1291 &cdnskey_set, &cdnskey_sig)))
1292 {
1293 fatal("could not validate child CDNSKEY RRset for %s",
1294 namestr);
1295 }
1296 }
1297 if (dns_rdataset_isassociated(&cds_set)) {
1298 vbprintf(1, "verify CDS signature(s)\n");
1299 if (!signed_loose(matching_sigs(old_key_tbl,
1300 &cds_set, &cds_sig)))
1301 {
1302 fatal("could not validate child CDS RRset for %s",
1303 namestr);
1304 }
1305 }
1306
1307 free_keytable(&old_key_tbl);
1308
1309 /*
1310 * Report the result of the replay attack protection checks
1311 * used for the output file timestamp
1312 */
1313 if (oldestsig.timesigned != 0 && verbose > 0) {
1314 char type[32];
1315 dns_rdatatype_format(oldestsig.covered, type, sizeof(type));
1316 verbose_time(1, "child signature inception time",
1317 oldestsig.timesigned);
1318 vbprintf(2, "from RRSIG %s by key %d\n",
1319 type, oldestsig.keyid);
1320 }
1321
1322 /*
1323 * Sucessfully do nothing if there's neither CDNSKEY nor CDS
1324 * RFC 7344 section 4.1 first paragraph
1325 */
1326 if (!dns_rdataset_isassociated(&cdnskey_set) &&
1327 !dns_rdataset_isassociated(&cds_set))
1328 {
1329 vbprintf(1, "%s has neither CDS nor CDNSKEY records\n",
1330 namestr);
1331 write_parent_set(ds_path, inplace, nsupdate, &old_ds_set);
1332 exit(0);
1333 }
1334
1335 /*
1336 * Make DS records from the CDS or CDNSKEY records
1337 * Prefer CDS if present, unless run with -D
1338 */
1339 if (prefer_cdnskey && dns_rdataset_isassociated(&cdnskey_set)) {
1340 make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set);
1341 } else if (dns_rdataset_isassociated(&cds_set)) {
1342 make_new_ds_set(ds_from_cds, ttl, &cds_set);
1343 } else {
1344 make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set);
1345 }
1346
1347 /*
1348 * Now we have a candidate DS RRset, we need to check it
1349 * won't break the delegation.
1350 */
1351 vbprintf(1, "which child DNSKEY records match new DS records?\n");
1352 new_key_tbl = match_keyset_dsset(&dnskey_set, &new_ds_set, TIGHT);
1353
1354 if (!consistent_digests(&new_ds_set)) {
1355 fatal("CDS records at %s do not cover each key "
1356 "with the same set of digest types", namestr);
1357 }
1358
1359 vbprintf(1, "verify DNSKEY signature(s)\n");
1360 if (!signed_strict(&new_ds_set,
1361 matching_sigs(new_key_tbl,
1362 &dnskey_set, &dnskey_sig)))
1363 {
1364 fatal("could not validate child DNSKEY RRset "
1365 "with new DS records for %s", namestr);
1366 }
1367
1368 free_keytable(&new_key_tbl);
1369
1370 /*
1371 * OK, it's all good!
1372 */
1373 if (nsupdate) {
1374 nsdiff(ttl, &old_ds_set, &new_ds_set);
1375 }
1376
1377 write_parent_set(ds_path, inplace, nsupdate, &new_ds_set);
1378
1379 free_all_sets();
1380 cleanup_logging(&lctx);
1381 dst_lib_destroy();
ba37674d
EH
1382 if (verbose > 10) {
1383 isc_mem_stats(mctx, stdout);
1384 }
1385 isc_mem_destroy(&mctx);
1386
1387 exit(0);
1388}