"Fossies" - the Fresh Open Source Software Archive

Member "bind-9.17.5/bin/dnssec/dnssec-cds.c" (4 Sep 2020, 32608 Bytes) of package /linux/misc/dns/bind9/9.17.5/bind-9.17.5.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "dnssec-cds.c" see the Fossies "Dox" file reference documentation.

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