[BACK]Return to dnssec-cds.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / external / mpl / bind / dist / bin / dnssec

File: [cvs.NetBSD.org] / src / external / mpl / bind / dist / bin / dnssec / dnssec-cds.c (download)

Revision 1.1.1.7 (vendor branch), Fri Sep 23 12:09:08 2022 UTC (18 months ago) by christos
Branch: ISC
CVS Tags: bind-9-16-33
Changes since 1.1.1.6: +3 -1 lines

Import 9.16.33; last imported was 9.16.20

	--- 9.16.33 released ---

5962.	[security]	Fix memory leak in EdDSA verify processing.
			(CVE-2022-38178) [GL #3487]

5961.	[security]	Fix memory leak in ECDSA verify processing.
			(CVE-2022-38177) [GL #3487]

5960.	[security]	Fix serve-stale crash that could happen when
			stale-answer-client-timeout was set to 0 and there was
			a stale CNAME in the cache for an incoming query.
			(CVE-2022-3080) [GL #3517]

5957.	[security]	Prevent excessive resource use while processing large
			delegations. (CVE-2022-2795) [GL #3394]

5956.	[func]		Make RRL code treat all QNAMEs that are subject to
			wildcard processing within a given zone as the same
			name. [GL #3459]

5955.	[port]		The libxml2 library has deprecated the usage of
			xmlInitThreads() and xmlCleanupThreads() functions. Use
			xmlInitParser() and xmlCleanupParser() instead.
			[GL #3518]

5954.	[func]		Fallback to IDNA2003 processing in dig when IDNA2008
			conversion fails. [GL #3485]

5953.	[bug]		Fix a crash on shutdown in delete_trace_entry(). Add
			mctx attach/detach pair to make sure that the memory
			context used by a memory pool is not destroyed before
			the memory pool itself. [GL #3515]

5952.	[bug]		Use quotes around address strings in YAML output.
			[GL #3511]

5951.	[bug]		In some cases, the dnstap query_message field was
			erroneously set when logging response messages.
			[GL #3501]

5948.	[bug]		Fix nsec3.c:dns_nsec3_activex() function, add a missing
			dns_db_detachnode() call. [GL #3500]

5945.	[bug]		If parsing /etc/bind.key failed, delv could assert
			when trying to parse the built in trust anchors as
			the parser hadn't been reset. [GL !6468]

5942.	[bug]		Fix tkey.c:buildquery() function's error handling by
			adding the missing cleanup code. [GL #3492]

5941.	[func]		Zones with dnssec-policy now require dynamic DNS or
			inline-siging to be configured explicitly. [GL #3381]

5936.	[bug]		Don't enable serve-stale for lookups that error because
			it is a duplicate query or a query that would be
			dropped. [GL #2982]

	--- 9.16.32 released ---

5934.	[func]		Improve fetches-per-zone fetch limit logging to log
			the final allowed and spilled values of the fetch
			counters before the counter object gets destroyed.
			[GL #3461]

5933.	[port]		Automatically disable RSASHA1 and NSEC3RSASHA1 in
			named on Fedorda 33, Oracle Linux 9 and RHEL9 when
			they are disabled by the security policy. [GL #3469]

5932.	[bug]		Fix rndc dumpdb -expired and always include expired
			RRsets, not just for RBTDB_VIRTUAL time window.
			[GL #3462]

5929.	[bug]		The "max-zone-ttl" option in "dnssec-policy" was
			not fully effective; it was used for timing key
			rollovers but did not actually place an upper limit
			on TTLs when loading a zone. This has been
			corrected, and the documentation has been clarified
			to indicate that the old "max-zone-ttl" zone option
			is now ignored when "dnssec-policy" is in use.
			[GL #2918]

5924.	[func]		When it's necessary to use AXFR to respond to an
			IXFR request, a message explaining the reason
			is now logged at level info. [GL #2683]

5923.	[bug]		Fix inheritance for dnssec-policy when checking for
			inline-signing. [GL #3438]

5922.	[bug]		Forwarding of UPDATE message could fail with the
			introduction of netmgr. This has been fixed. [GL #3389]

	--- 9.16.31 released ---

5917.	[bug]		Update ifconfig.sh script as is miscomputed interface
			identifiers when destroying interfaces. [GL #3061]

5915.	[bug]		Detect missing closing brace (}) and computational
			overflows in $GENERATE directives. [GL #3429]

5913.	[bug]		Fix a race between resolver query timeout and
			validation in resolver.c:validated(). Remove
			resolver.c:maybe_destroy() as it is no loger needed.
			[GL #3398]

5909.	[bug]		The server-side destination port was missing from dnstap
			captures of client traffic. [GL #3309]

5905.	[bug]		When the TCP connection would be closed/reset between
			the connect/accept and the read, the uv_read_start()
			return value would be unexpected and cause an assertion
			failure. [GL #3400]

5903.	[bug]		When named checks that the OPCODE in a response matches
			that of the request, if there is a mismatch named logs
			an error.  Some of those error messages incorrectly
			used RCODE instead of OPCODE to lookup the nemonic.
			This has been corrected. [GL !6420]

	--- 9.16.30 released ---

5899.	[func]		Don't try to process DNSSEC-related and ZONEMD records
			in catz. [GL #3380]

5890.	[bug]		When the fetches-per-server quota was adjusted
			because of an authoritative server timing out more
			or less frequently, it was incorrectly set to 1
			rather than the intended value.  This has been
			fixed. [GL #3327]

5888.	[bug]		Only write key files if the dnssec-policy keymgr has
			changed the metadata. [GL #3302]

5823.	[func]		Replace hazard pointers based lock-free list with
			locked-list based queue that's simpler and has no or
			little performance impact. [GL #3180]

	--- 9.16.29 released ---

5885.	[bug]		RPZ NSIP and NSDNAME rule processing didn't handle stub
			and static-stub zones at or above the query name.  This
			has now been addressed. [GL #3232]

5881.	[bug]		dig +nssearch could hang in rare cases when recv_done()
			callback was being called earlier than send_done().
			[GL #3278]

5880.	[func]		Add new named command-line option -C to print built-in
			defaults. [GL #1326]

5879.	[contrib]	dlz: Add FALLTHROUGH and UNREACHABLE macros. [GL #3306]

5874.	[bug]		keymgr didn't work with python 3.11. [GL !6157]

5866.	[bug]		Work around a jemalloc quirk which could trigger an
			out-of-memory condition in named over time. [GL #3287]

5863.	[bug]		If there was a pending negative cache DS entry,
			validations depending upon it could fail. [GL #3279]

5858.	[bug]		Don't remove CDS/CDNSKEY DELETE records on zone sign
			when using 'auto-dnssec maintain;'. [GL #2931]

	--- 9.16.28 released ---

5856.	[bug]		The "starting maxtime timer" message related to outgoing
			zone transfers was incorrectly logged at the ERROR level
			instead of DEBUG(1). [GL #3208]

5852.	[func]		Add new "reuseport" option to enable/disable load
			balancing of sockets. [GL #3249]

5843.	[bug]		When an UPDATE targets a zone that is not configured,
			the requested zone name is now logged in the "not
			authoritative" error message, so that it is easier to
			track down problematic update clients. [GL #3209]

5836.	[bug]		Quote the dns64 prefix in error messages that complain
			about problems with it, to avoid confusion with the
			following dns64 ACLs. [GL #3210]

5834.	[cleanup]	C99 variable-length arrays are difficult to use safely,
			so avoid them except in test code. [GL #3201]

5828.	[bug]		Replace single TCP write timer with per-TCP write
			timers. [GL #3200]

5824.	[bug]		Invalid dnssec-policy definitions were being accepted
			where the defined keys did not cover both KSK and ZSK
			roles for a given algorithm.  This is now checked for
			and the dnssec-policy is rejected if both roles are
			not present for all algorithms in use. [GL #3142]

	--- 9.16.27 released ---

5818.	[security]	A synchronous call to closehandle_cb() caused
			isc__nm_process_sock_buffer() to be called recursively,
			which in turn left TCP connections hanging in the
			CLOSE_WAIT state blocking indefinitely when
			out-of-order processing was disabled. (CVE-2022-0396)
			[GL #3112]

5817.	[security]	The rules for acceptance of records into the cache
			have been tightened to prevent the possibility of
			poisoning if forwarders send records outside
			the configured bailiwick. (CVE-2021-25220) [GL #2950]

5816.	[bug]		Make BIND compile with LibreSSL 3.5.0, as it was using
			not very accurate pre-processor checks for using shims.
			[GL #3172]

5815.	[bug]		If an oversized key name of a specific length was used
			in the text form of an HTTP or SVBC record, an INSIST
			could be triggered when parsing it. [GL #3175]

5814.	[bug]		The RecursClients statistics counter could underflow
			in certain resolution scenarios. [GL #3147]

5811.	[bug]		Reimplement the maximum and idle timeouts for outgoing
			zone transfers. [GL #1897]

5807.	[bug]		Add a TCP "write" timer, and time out writing
			connections after the "tcp-idle-timeout" period
			has elapsed. [GL #3132]

5804.	[func]		Add a debug log message when starting and ending
			the task exclusive mode. [GL #3137]

	--- 9.16.26 released ---

5801.	[bug]		Log "quota reached" message when hard quota
			is reached when accepting a connection. [GL #3125]

5800.	[func]		Add ECS support to the DLZ interface. [GL #3082]

5797.	[bug]		A failed view configuration during a named
			reconfiguration procedure could cause inconsistencies
			in BIND internal structures, causing a crash or other
			unexpected errors. [GL #3060]

5795.	[bug]		rndc could crash when interrupted by a signal
			before receiving a response. [GL #3080]

5793.	[bug]		Correctly detect and enable UDP recvmmsg support
			in all versions of libuv that support it. [GL #3095]

	--- 9.16.25 released ---

5789.	[bug]		Allow replacing expired zone signatures with
			signatures created by the KSK. [GL #3049]

5788.	[bug]		An assertion could occur if a catalog zone event was
			scheduled while the task manager was being shut
			down. [GL #3074]

5787.	[doc]		Update 'auto-dnssec' documentation, it may only be
			activated at zone level. [GL #3023]

5786.	[bug]		Defer detaching from zone->raw in zone_shutdown() if
			the zone is in the process of being dumped to disk, to
			ensure that the unsigned serial number information is
			always written in the raw-format header of the signed
			version on an inline-signed zone. [GL #3071]

5785.	[bug]		named could leak memory when two dnssec-policy clauses
			had the same name. named failed to log this error.
			[GL #3085]

5776.	[bug]		Add a missing isc_condition_destroy() for nmsocket
			condition variable and add missing isc_mutex_destroy()
			for nmworker lock. [GL #3051]

5676.	[func]		Memory use in named was excessive. This has been
			addressed by:
			- Replacing locked memory pools with normal memory
			  allocations.
			- Reducing the number of retained free items in
			  unlocked memory pools.
			- Disabling the internal allocator by default.
			  "named -M internal" turns it back on.
			[GL #2398]

	--- 9.16.24 released ---

5773.	[func]		Change the message when accepting TCP connection has
			failed to say "Accepting TCP connection failed" and
			change the log level for ISC_R_NOTCONNECTED, ISC_R_QUOTA
			and ISC_R_SOFTQUOTA results codes from ERROR to INFO.
			[GL #2700]

5768.	[bug]		dnssec-dsfromkey failed to omit revoked keys. [GL #853]

5764.	[bug]		dns_sdlz_putrr failed to process some valid resource
			records. [GL #3021]

5762.	[bug]		Fix a "named" crash related to removing and restoring a
			`catalog-zone` entry in the configuration file and
			running `rndc reconfig`. [GL #1608]

5758.	[bug]		mdig now honors the operating system's preferred
			ephemeral port range. [GL #2374]

5757.	[test]		Replace sed in nsupdate system test with awk to
			construct the nsupdate command.  The sed expression
			was not reliably changing the ttl. [GL #3003]

	--- 9.16.23 released ---

5752.	[bug]		Fix an assertion failure caused by missing member zones
			during a reload of a catalog zone. [GL #2308]

5750.	[bug]		Fix a bug when comparing two RSA keys. There was a typo
			which caused the "p" prime factors to not being
			compared. [GL #2972]

5737.	[bug]		Address Coverity warning in lib/dns/dnssec.c.
			[GL #2935]

	--- 9.16.22 released ---

5736.	[security]	The "lame-ttl" option is now forcibly set to 0. This
			effectively disables the lame server cache, as it could
			previously be abused by an attacker to significantly
			degrade resolver performance. (CVE-2021-25219)
			[GL #2899]

5724.	[bug]		Address a potential deadlock when checking zone content
			consistency. [GL #2908]

5723.	[bug]		Change 5709 broke backward compatibility for the
			"check-names master ..." and "check-names slave ..."
			options. This has been fixed. [GL #2911]

5720.	[contrib]	Old-style DLZ drivers that had to be enabled at
			build-time have been marked as deprecated. [GL #2814]

5719.	[func]		The "map" zone file format has been marked as
			deprecated. [GL #2882]

5717.	[func]		The "cache-file" option, which was documented as "for
			testing purposes only" and not to be used, has been
			removed. [GL #2903]

5716.	[bug]		Multiple library names were mistakenly passed to the
			krb5-config utility when ./configure was invoked with
			the --with-gssapi=[/path/to/]krb5-config option. This
			has been fixed by invoking krb5-config separately for
			each required library. [GL #2866]

5715.	[func]		Add a check for ports specified in "*-source(-v6)"
			options clashing with a global listening port. Such a
			configuration was already unsupported, but it failed
			silently; it is now treated as an error. [GL #2888]

5714.	[bug]		Remove the "adjust interface" mechanism which was
			responsible for setting up listeners on interfaces when
			the "*-source(-v6)" address and port were the same as
			the "listen-on(-v6)" address and port. Such a
			configuration is no longer supported; under certain
			timing conditions, that mechanism could prevent named
			from listening on some TCP ports. This has been fixed.
			[GL #2852]

5712.	[doc]		Add deprecation notice about removing native PKCS#11
			support in the next major BIND 9 release. [GL #2691]

	--- 9.16.21 released ---

5711.	[bug]		"map" files exceeding 2GB in size failed to load due to
			a size comparison that incorrectly treated the file size
			as a signed integer. [GL #2878]

5710.	[port]		win32: incorrect parentheses resulted in the wrong
			sizeof() tests being used to pick the appropriate
			Windows atomic operations for the object's size.
			[GL #2891]

5709.	[cleanup]	Enum values throughout the code have been updated
			to use the terms "primary" and "secondary" instead of
			"master" and "slave", respectively. [GL #1944]

5708.	[bug]		The thread-local isc_tid_v variable was not properly
			initialized when running BIND 9 as a Windows Service,
			leading to a crash on startup. [GL #2837]

5705.	[bug]		Change #5686 altered the internal memory structure of
			zone databases, but neglected to update the MAPAPI value
			for zone files in "map" format. This caused named to
			attempt to load incompatible map files, triggering an
			assertion failure on startup. The MAPAPI value has now
			been updated, so named rejects outdated files when
			encountering them. [GL #2872]

5704.	[bug]		Change #5317 caused the EDNS TCP Keepalive option to be
			ignored inadvertently in client requests. It has now
			been fixed and this option is handled properly again.
			[GL #1927]

5701.	[bug]		named-checkconf failed to detect syntactically invalid
			values of the "key" and "tls" parameters used to define
			members of remote server lists. [GL #2461]

5700.	[bug]		When a member zone was removed from a catalog zone,
			journal files for the former were not deleted.
			[GL #2842]

5699.	[func]		Data structures holding DNSSEC signing statistics are
			now grown and shrunk as necessary upon key rollover
			events. [GL #1721]

5698.	[bug]		When a DNSSEC-signed zone which only has a single
			signing key available is migrated to use KASP, that key
			is now treated as a Combined Signing Key (CSK).
			[GL #2857]

5696.	[protocol]	Support for HTTPS and SVCB record types has been added.
			(This does not include ADDITIONAL section processing for
			these record types, only basic support for RR type
			parsing and printing.) [GL #1132]

5694.	[bug]		Stale data in the cache could cause named to send
			non-minimized queries despite QNAME minimization being
			enabled. [GL #2665]

5691.	[bug]		When a dynamic zone was made available in another view
			using the "in-view" statement, running "rndc freeze"
			always reported an "already frozen" error even though
			the zone was successfully frozen. [GL #2844]

5690.	[func]		dnssec-signzone now honors Predecessor and Successor
			metadata found in private key files: if a signature for
			an RRset generated by the inactive predecessor exists
			and does not need to be replaced, no additional
			signature is now created for that RRset using the
			successor key. This enables dnssec-signzone to gradually
			replace RRSIGs during a ZSK rollover. [GL #1551]

/*	$NetBSD: dnssec-cds.c,v 1.1.1.7 2022/09/23 12:09:08 christos Exp $	*/

/*
 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
 *
 * SPDX-License-Identifier: MPL-2.0
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, you can obtain one at https://mozilla.org/MPL/2.0/.
 *
 * See the COPYRIGHT file distributed with this work for additional
 * information regarding copyright ownership.
 */

/*
 * Written by Tony Finch <dot@dotat.at> <fanf2@cam.ac.uk>
 * at Cambridge University Information Services
 */

/*! \file */

#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdlib.h>

#include <isc/buffer.h>
#include <isc/commandline.h>
#include <isc/file.h>
#include <isc/hash.h>
#include <isc/mem.h>
#include <isc/print.h>
#include <isc/serial.h>
#include <isc/string.h>
#include <isc/time.h>
#include <isc/util.h>

#include <dns/callbacks.h>
#include <dns/db.h>
#include <dns/dbiterator.h>
#include <dns/dnssec.h>
#include <dns/ds.h>
#include <dns/fixedname.h>
#include <dns/keyvalues.h>
#include <dns/log.h>
#include <dns/master.h>
#include <dns/name.h>
#include <dns/rdata.h>
#include <dns/rdataclass.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdatasetiter.h>
#include <dns/rdatatype.h>
#include <dns/result.h>
#include <dns/time.h>

#include <dst/dst.h>

#if USE_PKCS11
#include <pk11/result.h>
#endif /* if USE_PKCS11 */

#include "dnssectool.h"

const char *program = "dnssec-cds";

/*
 * Infrastructure
 */
static isc_log_t *lctx = NULL;
static isc_mem_t *mctx = NULL;

/*
 * The domain we are working on
 */
static const char *namestr = NULL;
static dns_fixedname_t fixed;
static dns_name_t *name = NULL;
static dns_rdataclass_t rdclass = dns_rdataclass_in;

static const char *startstr = NULL; /* from which we derive notbefore */
static isc_stdtime_t notbefore = 0; /* restrict sig inception times */
static dns_rdata_rrsig_t oldestsig; /* for recording inception time */

static int nkey; /* number of child zone DNSKEY records */

/*
 * The validation strategy of this program is top-down.
 *
 * We start with an implicitly trusted authoritative dsset.
 *
 * The child DNSKEY RRset is scanned to find out which keys are
 * authenticated by DS records, and the result is recorded in a key
 * table as described later in this comment.
 *
 * The key table is used up to three times to verify the signatures on
 * the child DNSKEY, CDNSKEY, and CDS RRsets. In this program, only keys
 * that have matching DS records are used for validating signatures.
 *
 * For replay attack protection, signatures are ignored if their inception
 * time is before the previously recorded inception time. We use the earliest
 * signature so that another run of dnssec-cds with the same records will
 * still accept all the signatures.
 *
 * A key table is an array of nkey keyinfo structures, like
 *
 *	keyinfo_t key_tbl[nkey];
 *
 * Each key is decoded into more useful representations, held in
 *	keyinfo->rdata
 *	keyinfo->dst
 *
 * If a key has no matching DS record then keyinfo->dst is NULL.
 *
 * The key algorithm and ID are saved in keyinfo->algo and
 * keyinfo->tag for quicky skipping DS and RRSIG records that can't
 * match.
 */
typedef struct keyinfo {
	dns_rdata_t rdata;
	dst_key_t *dst;
	dns_secalg_t algo;
	dns_keytag_t tag;
} keyinfo_t;

/* A replaceable function that can generate a DS RRset from some input */
typedef isc_result_t
ds_maker_func_t(dns_rdatalist_t *dslist, isc_buffer_t *buf, dns_rdata_t *rdata);

static dns_rdataset_t cdnskey_set, cdnskey_sig;
static dns_rdataset_t cds_set, cds_sig;
static dns_rdataset_t dnskey_set, dnskey_sig;
static dns_rdataset_t old_ds_set, new_ds_set;

static keyinfo_t *old_key_tbl, *new_key_tbl;

isc_buffer_t *new_ds_buf = NULL; /* backing store for new_ds_set */

static void
verbose_time(int level, const char *msg, isc_stdtime_t time) {
	isc_result_t result;
	isc_buffer_t timebuf;
	char timestr[32];

	if (verbose < level) {
		return;
	}

	isc_buffer_init(&timebuf, timestr, sizeof(timestr));
	result = dns_time64_totext(time, &timebuf);
	check_result(result, "dns_time64_totext()");
	isc_buffer_putuint8(&timebuf, 0);
	if (verbose < 3) {
		vbprintf(level, "%s %s\n", msg, timestr);
	} else {
		vbprintf(level, "%s %s (%" PRIu32 ")\n", msg, timestr, time);
	}
}

static void
initname(char *setname) {
	isc_result_t result;
	isc_buffer_t buf;

	name = dns_fixedname_initname(&fixed);
	namestr = setname;

	isc_buffer_init(&buf, setname, strlen(setname));
	isc_buffer_add(&buf, strlen(setname));
	result = dns_name_fromtext(name, &buf, dns_rootname, 0, NULL);
	if (result != ISC_R_SUCCESS) {
		fatal("could not initialize name %s", setname);
	}
}

static void
findset(dns_db_t *db, dns_dbnode_t *node, dns_rdatatype_t type,
	dns_rdataset_t *rdataset, dns_rdataset_t *sigrdataset) {
	isc_result_t result;

	dns_rdataset_init(rdataset);
	if (sigrdataset != NULL) {
		dns_rdataset_init(sigrdataset);
	}
	result = dns_db_findrdataset(db, node, NULL, type, 0, 0, rdataset,
				     sigrdataset);
	if (result != ISC_R_NOTFOUND) {
		check_result(result, "dns_db_findrdataset()");
	}
}

static void
freeset(dns_rdataset_t *rdataset) {
	if (dns_rdataset_isassociated(rdataset)) {
		dns_rdataset_disassociate(rdataset);
	}
}

static void
freelist(dns_rdataset_t *rdataset) {
	dns_rdatalist_t *rdlist;
	dns_rdata_t *rdata;

	if (!dns_rdataset_isassociated(rdataset)) {
		return;
	}

	dns_rdatalist_fromrdataset(rdataset, &rdlist);

	for (rdata = ISC_LIST_HEAD(rdlist->rdata); rdata != NULL;
	     rdata = ISC_LIST_HEAD(rdlist->rdata))
	{
		ISC_LIST_UNLINK(rdlist->rdata, rdata, link);
		isc_mem_put(mctx, rdata, sizeof(*rdata));
	}
	isc_mem_put(mctx, rdlist, sizeof(*rdlist));
	dns_rdataset_disassociate(rdataset);
}

static void
free_all_sets(void) {
	freeset(&cdnskey_set);
	freeset(&cdnskey_sig);
	freeset(&cds_set);
	freeset(&cds_sig);
	freeset(&dnskey_set);
	freeset(&dnskey_sig);
	freeset(&old_ds_set);
	freelist(&new_ds_set);
	if (new_ds_buf != NULL) {
		isc_buffer_free(&new_ds_buf);
	}
}

static void
load_db(const char *filename, dns_db_t **dbp, dns_dbnode_t **nodep) {
	isc_result_t result;

	result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone, rdclass, 0,
			       NULL, dbp);
	check_result(result, "dns_db_create()");

	result = dns_db_load(*dbp, filename, dns_masterformat_text,
			     DNS_MASTER_HINT);
	if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) {
		fatal("can't load %s: %s", filename, isc_result_totext(result));
	}

	result = dns_db_findnode(*dbp, name, false, nodep);
	if (result != ISC_R_SUCCESS) {
		fatal("can't find %s node in %s", namestr, filename);
	}
}

static void
free_db(dns_db_t **dbp, dns_dbnode_t **nodep) {
	dns_db_detachnode(*dbp, nodep);
	dns_db_detach(dbp);
}

static void
load_child_sets(const char *file) {
	dns_db_t *db = NULL;
	dns_dbnode_t *node = NULL;

	load_db(file, &db, &node);
	findset(db, node, dns_rdatatype_dnskey, &dnskey_set, &dnskey_sig);
	findset(db, node, dns_rdatatype_cdnskey, &cdnskey_set, &cdnskey_sig);
	findset(db, node, dns_rdatatype_cds, &cds_set, &cds_sig);
	free_db(&db, &node);
}

static void
get_dsset_name(char *filename, size_t size, const char *path,
	       const char *suffix) {
	isc_result_t result;
	isc_buffer_t buf;
	size_t len;

	isc_buffer_init(&buf, filename, size);

	len = strlen(path);

	/* allow room for a trailing slash */
	if (isc_buffer_availablelength(&buf) <= len) {
		fatal("%s: pathname too long", path);
	}
	isc_buffer_putstr(&buf, path);

	if (isc_file_isdirectory(path) == ISC_R_SUCCESS) {
		const char *prefix = "dsset-";

		if (path[len - 1] != '/') {
			isc_buffer_putstr(&buf, "/");
		}

		if (isc_buffer_availablelength(&buf) < strlen(prefix)) {
			fatal("%s: pathname too long", path);
		}
		isc_buffer_putstr(&buf, prefix);

		result = dns_name_tofilenametext(name, false, &buf);
		check_result(result, "dns_name_tofilenametext()");
		if (isc_buffer_availablelength(&buf) == 0) {
			fatal("%s: pathname too long", path);
		}
	}
	/* allow room for a trailing nul */
	if (isc_buffer_availablelength(&buf) <= strlen(suffix)) {
		fatal("%s: pathname too long", path);
	}
	isc_buffer_putstr(&buf, suffix);
	isc_buffer_putuint8(&buf, 0);
}

static void
load_parent_set(const char *path) {
	isc_result_t result;
	dns_db_t *db = NULL;
	dns_dbnode_t *node = NULL;
	isc_time_t modtime;
	char filename[PATH_MAX + 1];

	get_dsset_name(filename, sizeof(filename), path, "");

	result = isc_file_getmodtime(filename, &modtime);
	if (result != ISC_R_SUCCESS) {
		fatal("could not get modification time of %s: %s", filename,
		      isc_result_totext(result));
	}
	notbefore = isc_time_seconds(&modtime);
	if (startstr != NULL) {
		isc_stdtime_t now;
		isc_stdtime_get(&now);
		notbefore = strtotime(startstr, now, notbefore, NULL);
	}
	verbose_time(1, "child records must not be signed before", notbefore);

	load_db(filename, &db, &node);
	findset(db, node, dns_rdatatype_ds, &old_ds_set, NULL);

	if (!dns_rdataset_isassociated(&old_ds_set)) {
		fatal("could not find DS records for %s in %s", namestr,
		      filename);
	}

	free_db(&db, &node);
}

#define MAX_CDS_RDATA_TEXT_SIZE DNS_RDATA_MAXLENGTH * 2

static isc_buffer_t *
formatset(dns_rdataset_t *rdataset) {
	isc_result_t result;
	isc_buffer_t *buf = NULL;
	dns_master_style_t *style = NULL;
	unsigned int styleflags;

	styleflags = (rdataset->ttl == 0) ? DNS_STYLEFLAG_NO_TTL : 0;

	/*
	 * This style is for consistency with the output of dnssec-dsfromkey
	 * which just separates fields with spaces. The huge tab stop width
	 * eliminates any tab characters.
	 */
	result = dns_master_stylecreate(&style, styleflags, 0, 0, 0, 0, 0,
					1000000, 0, mctx);
	check_result(result, "dns_master_stylecreate2 failed");

	isc_buffer_allocate(mctx, &buf, MAX_CDS_RDATA_TEXT_SIZE);
	result = dns_master_rdatasettotext(name, rdataset, style, NULL, buf);

	if ((result == ISC_R_SUCCESS) && isc_buffer_availablelength(buf) < 1) {
		result = ISC_R_NOSPACE;
	}

	check_result(result, "dns_rdataset_totext()");

	isc_buffer_putuint8(buf, 0);

	dns_master_styledestroy(&style, mctx);

	return (buf);
}

static void
write_parent_set(const char *path, const char *inplace, bool nsupdate,
		 dns_rdataset_t *rdataset) {
	isc_result_t result;
	isc_buffer_t *buf = NULL;
	isc_region_t r;
	isc_time_t filetime;
	char backname[PATH_MAX + 1];
	char filename[PATH_MAX + 1];
	char tmpname[PATH_MAX + 1];
	FILE *fp = NULL;

	if (nsupdate && inplace == NULL) {
		return;
	}

	buf = formatset(rdataset);
	isc_buffer_usedregion(buf, &r);

	/*
	 * Try to ensure a write error doesn't make a zone go insecure!
	 */
	if (inplace == NULL) {
		printf("%s", (char *)r.base);
		isc_buffer_free(&buf);
		if (fflush(stdout) == EOF) {
			fatal("error writing to stdout: %s", strerror(errno));
		}
		return;
	}

	if (inplace[0] != '\0') {
		get_dsset_name(backname, sizeof(backname), path, inplace);
	}
	get_dsset_name(filename, sizeof(filename), path, "");
	get_dsset_name(tmpname, sizeof(tmpname), path, "-XXXXXXXXXX");

	result = isc_file_openunique(tmpname, &fp);
	if (result != ISC_R_SUCCESS) {
		fatal("open %s: %s", tmpname, isc_result_totext(result));
	}
	fprintf(fp, "%s", (char *)r.base);
	isc_buffer_free(&buf);
	if (fclose(fp) == EOF) {
		int err = errno;
		isc_file_remove(tmpname);
		fatal("error writing to %s: %s", tmpname, strerror(err));
	}

	isc_time_set(&filetime, oldestsig.timesigned, 0);
	result = isc_file_settime(tmpname, &filetime);
	if (result != ISC_R_SUCCESS) {
		isc_file_remove(tmpname);
		fatal("can't set modification time of %s: %s", tmpname,
		      isc_result_totext(result));
	}

	if (inplace[0] != '\0') {
		isc_file_rename(filename, backname);
	}
	isc_file_rename(tmpname, filename);
}

typedef enum { LOOSE, TIGHT } strictness_t;

/*
 * Find out if any (C)DS record matches a particular (C)DNSKEY.
 */
static bool
match_key_dsset(keyinfo_t *ki, dns_rdataset_t *dsset, strictness_t strictness) {
	isc_result_t result;
	unsigned char dsbuf[DNS_DS_BUFFERSIZE];

	for (result = dns_rdataset_first(dsset); result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(dsset))
	{
		dns_rdata_ds_t ds;
		dns_rdata_t dsrdata = DNS_RDATA_INIT;
		dns_rdata_t newdsrdata = DNS_RDATA_INIT;
		bool c;

		dns_rdataset_current(dsset, &dsrdata);
		result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
		check_result(result, "dns_rdata_tostruct(DS)");

		if (ki->tag != ds.key_tag || ki->algo != ds.algorithm) {
			continue;
		}

		result = dns_ds_buildrdata(name, &ki->rdata, ds.digest_type,
					   dsbuf, &newdsrdata);
		if (result != ISC_R_SUCCESS) {
			vbprintf(3,
				 "dns_ds_buildrdata("
				 "keytag=%d, algo=%d, digest=%d): %s\n",
				 ds.key_tag, ds.algorithm, ds.digest_type,
				 dns_result_totext(result));
			continue;
		}
		/* allow for both DS and CDS */
		c = dsrdata.type != dns_rdatatype_ds;
		dsrdata.type = dns_rdatatype_ds;
		if (dns_rdata_compare(&dsrdata, &newdsrdata) == 0) {
			vbprintf(1, "found matching %s %d %d %d\n",
				 c ? "CDS" : "DS", ds.key_tag, ds.algorithm,
				 ds.digest_type);
			return (true);
		} else if (strictness == TIGHT) {
			vbprintf(0,
				 "key does not match %s %d %d %d "
				 "when it looks like it should\n",
				 c ? "CDS" : "DS", ds.key_tag, ds.algorithm,
				 ds.digest_type);
			return (false);
		}
	}

	vbprintf(1, "no matching %s for %s %d %d\n",
		 dsset->type == dns_rdatatype_cds ? "CDS" : "DS",
		 ki->rdata.type == dns_rdatatype_cdnskey ? "CDNSKEY" : "DNSKEY",
		 ki->tag, ki->algo);

	return (false);
}

/*
 * Find which (C)DNSKEY records match a (C)DS RRset.
 * This creates a keyinfo_t key_tbl[nkey] array.
 */
static keyinfo_t *
match_keyset_dsset(dns_rdataset_t *keyset, dns_rdataset_t *dsset,
		   strictness_t strictness) {
	isc_result_t result;
	keyinfo_t *keytable;
	int i;

	nkey = dns_rdataset_count(keyset);

	keytable = isc_mem_get(mctx, sizeof(keyinfo_t) * nkey);

	for (result = dns_rdataset_first(keyset), i = 0;
	     result == ISC_R_SUCCESS; result = dns_rdataset_next(keyset), i++)
	{
		keyinfo_t *ki;
		dns_rdata_dnskey_t dnskey;
		dns_rdata_t *keyrdata;
		isc_region_t r;

		INSIST(i < nkey);
		ki = &keytable[i];
		keyrdata = &ki->rdata;

		dns_rdata_init(keyrdata);
		dns_rdataset_current(keyset, keyrdata);

		result = dns_rdata_tostruct(keyrdata, &dnskey, NULL);
		check_result(result, "dns_rdata_tostruct(DNSKEY)");
		ki->algo = dnskey.algorithm;

		dns_rdata_toregion(keyrdata, &r);
		ki->tag = dst_region_computeid(&r);

		ki->dst = NULL;
		if (!match_key_dsset(ki, dsset, strictness)) {
			continue;
		}

		result = dns_dnssec_keyfromrdata(name, keyrdata, mctx,
						 &ki->dst);
		if (result != ISC_R_SUCCESS) {
			vbprintf(3,
				 "dns_dnssec_keyfromrdata("
				 "keytag=%d, algo=%d): %s\n",
				 ki->tag, ki->algo, dns_result_totext(result));
		}
	}

	return (keytable);
}

static void
free_keytable(keyinfo_t **keytable_p) {
	keyinfo_t *keytable = *keytable_p;
	*keytable_p = NULL;
	keyinfo_t *ki;
	int i;

	for (i = 0; i < nkey; i++) {
		ki = &keytable[i];
		if (ki->dst != NULL) {
			dst_key_free(&ki->dst);
		}
	}

	isc_mem_put(mctx, keytable, sizeof(keyinfo_t) * nkey);
}

/*
 * Find out which keys have signed an RRset. Keys that do not match a
 * DS record are skipped.
 *
 * The return value is an array with nkey elements, one for each key,
 * either zero if the key was skipped or did not sign the RRset, or
 * otherwise the key algorithm. This is used by the signature coverage
 * check functions below.
 */
static dns_secalg_t *
matching_sigs(keyinfo_t *keytbl, dns_rdataset_t *rdataset,
	      dns_rdataset_t *sigset) {
	isc_result_t result;
	dns_secalg_t *algo;
	int i;

	algo = isc_mem_get(mctx, nkey);
	memset(algo, 0, nkey);

	for (result = dns_rdataset_first(sigset); result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(sigset))
	{
		dns_rdata_t sigrdata = DNS_RDATA_INIT;
		dns_rdata_rrsig_t sig;

		dns_rdataset_current(sigset, &sigrdata);
		result = dns_rdata_tostruct(&sigrdata, &sig, NULL);
		check_result(result, "dns_rdata_tostruct(RRSIG)");

		/*
		 * Replay attack protection: check against current age limit
		 */
		if (isc_serial_lt(sig.timesigned, notbefore)) {
			vbprintf(1, "skip RRSIG by key %d: too old\n",
				 sig.keyid);
			continue;
		}

		for (i = 0; i < nkey; i++) {
			keyinfo_t *ki = &keytbl[i];
			if (sig.keyid != ki->tag || sig.algorithm != ki->algo ||
			    !dns_name_equal(&sig.signer, name))
			{
				continue;
			}
			if (ki->dst == NULL) {
				vbprintf(1,
					 "skip RRSIG by key %d:"
					 " no matching (C)DS\n",
					 sig.keyid);
				continue;
			}

			result = dns_dnssec_verify(name, rdataset, ki->dst,
						   false, 0, mctx, &sigrdata,
						   NULL);

			if (result != ISC_R_SUCCESS &&
			    result != DNS_R_FROMWILDCARD) {
				vbprintf(1,
					 "skip RRSIG by key %d:"
					 " verification failed: %s\n",
					 sig.keyid, isc_result_totext(result));
				continue;
			}

			vbprintf(1, "found RRSIG by key %d\n", ki->tag);
			algo[i] = sig.algorithm;

			/*
			 * Replay attack protection: work out next age limit,
			 * only after the signature has been verified
			 */
			if (oldestsig.timesigned == 0 ||
			    isc_serial_lt(sig.timesigned, oldestsig.timesigned))
			{
				verbose_time(2, "this is the oldest so far",
					     sig.timesigned);
				oldestsig = sig;
			}
		}
	}

	return (algo);
}

/*
 * Consume the result of matching_sigs(). When checking records
 * fetched from the child zone, any working signature is enough.
 */
static bool
signed_loose(dns_secalg_t *algo) {
	bool ok = false;
	int i;
	for (i = 0; i < nkey; i++) {
		if (algo[i] != 0) {
			ok = true;
		}
	}
	isc_mem_put(mctx, algo, nkey);
	return (ok);
}

/*
 * Consume the result of matching_sigs(). To ensure that the new DS
 * RRset does not break the chain of trust to the DNSKEY RRset, every
 * key algorithm in the DS RRset must have a signature in the DNSKEY
 * RRset.
 */
static bool
signed_strict(dns_rdataset_t *dsset, dns_secalg_t *algo) {
	isc_result_t result;
	bool all_ok = true;

	for (result = dns_rdataset_first(dsset); result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(dsset))
	{
		dns_rdata_t dsrdata = DNS_RDATA_INIT;
		dns_rdata_ds_t ds;
		bool ds_ok;
		int i;

		dns_rdataset_current(dsset, &dsrdata);
		result = dns_rdata_tostruct(&dsrdata, &ds, NULL);
		check_result(result, "dns_rdata_tostruct(DS)");

		ds_ok = false;
		for (i = 0; i < nkey; i++) {
			if (algo[i] == ds.algorithm) {
				ds_ok = true;
			}
		}
		if (!ds_ok) {
			vbprintf(0,
				 "missing signature for algorithm %d "
				 "(key %d)\n",
				 ds.algorithm, ds.key_tag);
			all_ok = false;
		}
	}

	isc_mem_put(mctx, algo, nkey);
	return (all_ok);
}

static dns_rdata_t *
rdata_get(void) {
	dns_rdata_t *rdata;

	rdata = isc_mem_get(mctx, sizeof(*rdata));
	dns_rdata_init(rdata);

	return (rdata);
}

static isc_result_t
rdata_put(isc_result_t result, dns_rdatalist_t *rdlist, dns_rdata_t *rdata) {
	if (result == ISC_R_SUCCESS) {
		ISC_LIST_APPEND(rdlist->rdata, rdata, link);
	} else {
		isc_mem_put(mctx, rdata, sizeof(*rdata));
	}

	return (result);
}

/*
 * This basically copies the rdata into the buffer, but going via the
 * unpacked struct has the side-effect of changing the rdatatype. The
 * dns_rdata_cds_t and dns_rdata_ds_t types are aliases.
 */
static isc_result_t
ds_from_cds(dns_rdatalist_t *dslist, isc_buffer_t *buf, dns_rdata_t *cds) {
	isc_result_t result;
	dns_rdata_ds_t ds;
	dns_rdata_t *rdata;

	REQUIRE(buf != NULL);

	rdata = rdata_get();

	result = dns_rdata_tostruct(cds, &ds, NULL);
	check_result(result, "dns_rdata_tostruct(CDS)");
	ds.common.rdtype = dns_rdatatype_ds;

	result = dns_rdata_fromstruct(rdata, rdclass, dns_rdatatype_ds, &ds,
				      buf);

	return (rdata_put(result, dslist, rdata));
}

static isc_result_t
ds_from_cdnskey(dns_rdatalist_t *dslist, isc_buffer_t *buf,
		dns_rdata_t *cdnskey) {
	isc_result_t result;
	unsigned i, n;

	REQUIRE(buf != NULL);

	n = sizeof(dtype) / sizeof(dtype[0]);
	for (i = 0; i < n; i++) {
		if (dtype[i] != 0) {
			dns_rdata_t *rdata;
			isc_region_t r;

			isc_buffer_availableregion(buf, &r);
			if (r.length < DNS_DS_BUFFERSIZE) {
				return (ISC_R_NOSPACE);
			}

			rdata = rdata_get();
			result = dns_ds_buildrdata(name, cdnskey, dtype[i],
						   r.base, rdata);
			if (result == ISC_R_SUCCESS) {
				isc_buffer_add(buf, DNS_DS_BUFFERSIZE);
			}

			result = rdata_put(result, dslist, rdata);
			if (result != ISC_R_SUCCESS) {
				return (result);
			}
		}
	}

	return (ISC_R_SUCCESS);
}

static void
make_new_ds_set(ds_maker_func_t *ds_from_rdata, uint32_t ttl,
		dns_rdataset_t *rdset) {
	unsigned int size = 16;
	for (;;) {
		isc_result_t result;
		dns_rdatalist_t *dslist;

		dslist = isc_mem_get(mctx, sizeof(*dslist));

		dns_rdatalist_init(dslist);
		dslist->rdclass = rdclass;
		dslist->type = dns_rdatatype_ds;
		dslist->ttl = ttl;

		dns_rdataset_init(&new_ds_set);
		result = dns_rdatalist_tordataset(dslist, &new_ds_set);
		check_result(result, "dns_rdatalist_tordataset(dslist)");

		isc_buffer_allocate(mctx, &new_ds_buf, size);

		for (result = dns_rdataset_first(rdset);
		     result == ISC_R_SUCCESS; result = dns_rdataset_next(rdset))
		{
			isc_result_t tresult;
			dns_rdata_t rdata = DNS_RDATA_INIT;

			dns_rdataset_current(rdset, &rdata);

			tresult = ds_from_rdata(dslist, new_ds_buf, &rdata);
			if (tresult == ISC_R_NOSPACE) {
				vbprintf(20, "DS list buffer size %u\n", size);
				freelist(&new_ds_set);
				isc_buffer_free(&new_ds_buf);
				size *= 2;
				break;
			}

			check_result(tresult, "ds_from_rdata()");
		}

		if (result == ISC_R_NOMORE) {
			break;
		}
	}
}

static int
rdata_cmp(const void *rdata1, const void *rdata2) {
	return (dns_rdata_compare((const dns_rdata_t *)rdata1,
				  (const dns_rdata_t *)rdata2));
}

/*
 * Ensure that every key identified by the DS RRset has the same set of
 * digest types.
 */
static bool
consistent_digests(dns_rdataset_t *dsset) {
	isc_result_t result;
	dns_rdata_t *arrdata;
	dns_rdata_ds_t *ds;
	dns_keytag_t key_tag;
	dns_secalg_t algorithm;
	bool match;
	int i, j, n, d;

	/*
	 * First sort the dsset. DS rdata fields are tag, algorithm, digest,
	 * so sorting them brings together all the records for each key.
	 */

	n = dns_rdataset_count(dsset);

	arrdata = isc_mem_get(mctx, n * sizeof(dns_rdata_t));

	for (result = dns_rdataset_first(dsset), i = 0; result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(dsset), i++)
	{
		dns_rdata_init(&arrdata[i]);
		dns_rdataset_current(dsset, &arrdata[i]);
	}

	qsort(arrdata, n, sizeof(dns_rdata_t), rdata_cmp);

	/*
	 * Convert sorted arrdata to more accessible format
	 */
	ds = isc_mem_get(mctx, n * sizeof(dns_rdata_ds_t));

	for (i = 0; i < n; i++) {
		result = dns_rdata_tostruct(&arrdata[i], &ds[i], NULL);
		check_result(result, "dns_rdata_tostruct(DS)");
	}

	/*
	 * Count number of digest types (d) for first key
	 */
	key_tag = ds[0].key_tag;
	algorithm = ds[0].algorithm;
	for (d = 0, i = 0; i < n; i++, d++) {
		if (ds[i].key_tag != key_tag || ds[i].algorithm != algorithm) {
			break;
		}
	}

	/*
	 * Check subsequent keys match the first one
	 */
	match = true;
	while (i < n) {
		key_tag = ds[i].key_tag;
		algorithm = ds[i].algorithm;
		for (j = 0; j < d && i + j < n; j++) {
			if (ds[i + j].key_tag != key_tag ||
			    ds[i + j].algorithm != algorithm ||
			    ds[i + j].digest_type != ds[j].digest_type)
			{
				match = false;
			}
		}
		i += d;
	}

	/*
	 * Done!
	 */
	isc_mem_put(mctx, ds, n * sizeof(dns_rdata_ds_t));
	isc_mem_put(mctx, arrdata, n * sizeof(dns_rdata_t));

	return (match);
}

static void
print_diff(const char *cmd, dns_rdataset_t *rdataset) {
	isc_buffer_t *buf;
	isc_region_t r;
	unsigned char *nl;
	size_t len;

	buf = formatset(rdataset);
	isc_buffer_usedregion(buf, &r);

	while ((nl = memchr(r.base, '\n', r.length)) != NULL) {
		len = nl - r.base + 1;
		printf("update %s %.*s", cmd, (int)len, (char *)r.base);
		isc_region_consume(&r, len);
	}

	isc_buffer_free(&buf);
}

static void
update_diff(const char *cmd, uint32_t ttl, dns_rdataset_t *addset,
	    dns_rdataset_t *delset) {
	isc_result_t result;
	dns_db_t *db;
	dns_dbnode_t *node;
	dns_dbversion_t *ver;
	dns_rdataset_t diffset;
	uint32_t save;

	db = NULL;
	result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone, rdclass, 0,
			       NULL, &db);
	check_result(result, "dns_db_create()");

	ver = NULL;
	result = dns_db_newversion(db, &ver);
	check_result(result, "dns_db_newversion()");

	node = NULL;
	result = dns_db_findnode(db, name, true, &node);
	check_result(result, "dns_db_findnode()");

	dns_rdataset_init(&diffset);

	result = dns_db_addrdataset(db, node, ver, 0, addset, DNS_DBADD_MERGE,
				    NULL);
	check_result(result, "dns_db_addrdataset()");

	result = dns_db_subtractrdataset(db, node, ver, delset, 0, &diffset);
	if (result == DNS_R_UNCHANGED) {
		save = addset->ttl;
		addset->ttl = ttl;
		print_diff(cmd, addset);
		addset->ttl = save;
	} else if (result != DNS_R_NXRRSET) {
		check_result(result, "dns_db_subtractrdataset()");
		diffset.ttl = ttl;
		print_diff(cmd, &diffset);
		dns_rdataset_disassociate(&diffset);
	}

	dns_db_detachnode(db, &node);
	dns_db_closeversion(db, &ver, false);
	dns_db_detach(&db);
}

static void
nsdiff(uint32_t ttl, dns_rdataset_t *oldset, dns_rdataset_t *newset) {
	if (ttl == 0) {
		vbprintf(1, "warning: no TTL in nsupdate script\n");
	}
	update_diff("add", ttl, newset, oldset);
	update_diff("del", 0, oldset, newset);
	if (verbose > 0) {
		printf("show\nsend\nanswer\n");
	} else {
		printf("send\n");
	}
	if (fflush(stdout) == EOF) {
		fatal("write stdout: %s", strerror(errno));
	}
}

ISC_PLATFORM_NORETURN_PRE static void
usage(void) ISC_PLATFORM_NORETURN_POST;

static void
usage(void) {
	fprintf(stderr, "Usage:\n");
	fprintf(stderr,
		"    %s options [options] -f <file> -d <path> <domain>\n",
		program);
	fprintf(stderr, "Version: %s\n", VERSION);
	fprintf(stderr, "Options:\n"
			"    -a <algorithm>     digest algorithm (SHA-1 / "
			"SHA-256 / SHA-384)\n"
			"    -c <class>         of domain (default IN)\n"
			"    -D                 prefer CDNSKEY records instead "
			"of CDS\n"
			"    -d <file|dir>      where to find parent dsset- "
			"file\n"
			"    -f <file>          child DNSKEY+CDNSKEY+CDS+RRSIG "
			"records\n"
			"    -i[extension]      update dsset- file in place\n"
			"    -s <start-time>    oldest permitted child "
			"signatures\n"
			"    -u                 emit nsupdate script\n"
			"    -T <ttl>           TTL of DS records\n"
			"    -V                 print version\n"
			"    -v <verbosity>\n");
	exit(1);
}

int
main(int argc, char *argv[]) {
	const char *child_path = NULL;
	const char *ds_path = NULL;
	const char *inplace = NULL;
	isc_result_t result;
	bool prefer_cdnskey = false;
	bool nsupdate = false;
	uint32_t ttl = 0;
	int ch;
	char *endp;

	isc_mem_create(&mctx);

#if USE_PKCS11
	pk11_result_register();
#endif /* if USE_PKCS11 */
	dns_result_register();

	isc_commandline_errprint = false;

#define OPTIONS "a:c:Dd:f:i:ms:T:uv:V"
	while ((ch = isc_commandline_parse(argc, argv, OPTIONS)) != -1) {
		switch (ch) {
		case 'a':
			add_dtype(strtodsdigest(isc_commandline_argument));
			break;
		case 'c':
			rdclass = strtoclass(isc_commandline_argument);
			break;
		case 'D':
			prefer_cdnskey = true;
			break;
		case 'd':
			ds_path = isc_commandline_argument;
			break;
		case 'f':
			child_path = isc_commandline_argument;
			break;
		case 'i':
			/*
			 * This is a bodge to make the argument optional,
			 * so that it works just like sed(1).
			 */
			if (isc_commandline_argument ==
			    argv[isc_commandline_index - 1]) {
				isc_commandline_index--;
				inplace = "";
			} else {
				inplace = isc_commandline_argument;
			}
			break;
		case 'm':
			isc_mem_debugging = ISC_MEM_DEBUGTRACE |
					    ISC_MEM_DEBUGRECORD;
			break;
		case 's':
			startstr = isc_commandline_argument;
			break;
		case 'T':
			ttl = strtottl(isc_commandline_argument);
			break;
		case 'u':
			nsupdate = true;
			break;
		case 'V':
			/* Does not return. */
			version(program);
			break;
		case 'v':
			verbose = strtoul(isc_commandline_argument, &endp, 0);
			if (*endp != '\0') {
				fatal("-v must be followed by a number");
			}
			break;
		default:
			usage();
			break;
		}
	}
	argv += isc_commandline_index;
	argc -= isc_commandline_index;

	if (argc != 1) {
		usage();
	}
	initname(argv[0]);

	/*
	 * Default digest type if none specified.
	 */
	if (dtype[0] == 0) {
		dtype[0] = DNS_DSDIGEST_SHA256;
	}

	setup_logging(mctx, &lctx);

	result = dst_lib_init(mctx, NULL);
	if (result != ISC_R_SUCCESS) {
		fatal("could not initialize dst: %s",
		      isc_result_totext(result));
	}

	if (ds_path == NULL) {
		fatal("missing -d DS pathname");
	}
	load_parent_set(ds_path);

	/*
	 * Preserve the TTL if it wasn't overridden.
	 */
	if (ttl == 0) {
		ttl = old_ds_set.ttl;
	}

	if (child_path == NULL) {
		fatal("path to file containing child data must be specified");
	}

	load_child_sets(child_path);

	/*
	 * Check child records have accompanying RRSIGs and DNSKEYs
	 */

	if (!dns_rdataset_isassociated(&dnskey_set) ||
	    !dns_rdataset_isassociated(&dnskey_sig))
	{
		fatal("could not find signed DNSKEY RRset for %s", namestr);
	}

	if (dns_rdataset_isassociated(&cdnskey_set) &&
	    !dns_rdataset_isassociated(&cdnskey_sig))
	{
		fatal("missing RRSIG CDNSKEY records for %s", namestr);
	}
	if (dns_rdataset_isassociated(&cds_set) &&
	    !dns_rdataset_isassociated(&cds_sig)) {
		fatal("missing RRSIG CDS records for %s", namestr);
	}

	vbprintf(1, "which child DNSKEY records match parent DS records?\n");
	old_key_tbl = match_keyset_dsset(&dnskey_set, &old_ds_set, LOOSE);

	/*
	 * We have now identified the keys that are allowed to authenticate
	 * the DNSKEY RRset (RFC 4035 section 5.2 bullet 2), and CDNSKEY and
	 * CDS RRsets (RFC 7344 section 4.1 bullet 2).
	 */

	vbprintf(1, "verify DNSKEY signature(s)\n");
	if (!signed_loose(matching_sigs(old_key_tbl, &dnskey_set, &dnskey_sig)))
	{
		fatal("could not validate child DNSKEY RRset for %s", namestr);
	}

	if (dns_rdataset_isassociated(&cdnskey_set)) {
		vbprintf(1, "verify CDNSKEY signature(s)\n");
		if (!signed_loose(matching_sigs(old_key_tbl, &cdnskey_set,
						&cdnskey_sig))) {
			fatal("could not validate child CDNSKEY RRset for %s",
			      namestr);
		}
	}
	if (dns_rdataset_isassociated(&cds_set)) {
		vbprintf(1, "verify CDS signature(s)\n");
		if (!signed_loose(
			    matching_sigs(old_key_tbl, &cds_set, &cds_sig))) {
			fatal("could not validate child CDS RRset for %s",
			      namestr);
		}
	}

	free_keytable(&old_key_tbl);

	/*
	 * Report the result of the replay attack protection checks
	 * used for the output file timestamp
	 */
	if (oldestsig.timesigned != 0 && verbose > 0) {
		char type[32];
		dns_rdatatype_format(oldestsig.covered, type, sizeof(type));
		verbose_time(1, "child signature inception time",
			     oldestsig.timesigned);
		vbprintf(2, "from RRSIG %s by key %d\n", type, oldestsig.keyid);
	}

	/*
	 * Successfully do nothing if there's neither CDNSKEY nor CDS
	 * RFC 7344 section 4.1 first paragraph
	 */
	if (!dns_rdataset_isassociated(&cdnskey_set) &&
	    !dns_rdataset_isassociated(&cds_set))
	{
		vbprintf(1, "%s has neither CDS nor CDNSKEY records\n",
			 namestr);
		write_parent_set(ds_path, inplace, nsupdate, &old_ds_set);
		exit(0);
	}

	/*
	 * Make DS records from the CDS or CDNSKEY records
	 * Prefer CDS if present, unless run with -D
	 */
	if (prefer_cdnskey && dns_rdataset_isassociated(&cdnskey_set)) {
		make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set);
	} else if (dns_rdataset_isassociated(&cds_set)) {
		make_new_ds_set(ds_from_cds, ttl, &cds_set);
	} else {
		make_new_ds_set(ds_from_cdnskey, ttl, &cdnskey_set);
	}

	/*
	 * Now we have a candidate DS RRset, we need to check it
	 * won't break the delegation.
	 */
	vbprintf(1, "which child DNSKEY records match new DS records?\n");
	new_key_tbl = match_keyset_dsset(&dnskey_set, &new_ds_set, TIGHT);

	if (!consistent_digests(&new_ds_set)) {
		fatal("CDS records at %s do not cover each key "
		      "with the same set of digest types",
		      namestr);
	}

	vbprintf(1, "verify DNSKEY signature(s)\n");
	if (!signed_strict(&new_ds_set, matching_sigs(new_key_tbl, &dnskey_set,
						      &dnskey_sig)))
	{
		fatal("could not validate child DNSKEY RRset "
		      "with new DS records for %s",
		      namestr);
	}

	free_keytable(&new_key_tbl);

	/*
	 * OK, it's all good!
	 */
	if (nsupdate) {
		nsdiff(ttl, &old_ds_set, &new_ds_set);
	}

	write_parent_set(ds_path, inplace, nsupdate, &new_ds_set);

	free_all_sets();
	cleanup_logging(&lctx);
	dst_lib_destroy();
	if (verbose > 10) {
		isc_mem_stats(mctx, stdout);
	}
	isc_mem_destroy(&mctx);

	exit(0);
}