<commit_before>/*
 * Portions Copyright (C) 2004-2009  Internet Systems Consortium, Inc. ("ISC")
 * Portions Copyright (C) 1999-2003  Internet Software Consortium.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE
 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Portions Copyright (C) 1995-2000 by Network Associates, Inc.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE
 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/* $Id: dnssec-signzone.c,v 1.218 2009/06/05 06:59:03 marka Exp $ */

/*! \file */

#include <config.h>

#include <stdlib.h>
#include <time.h>

#include <isc/app.h>
#include <isc/base32.h>
#include <isc/commandline.h>
#include <isc/entropy.h>
#include <isc/event.h>
#include <isc/file.h>
#include <isc/hash.h>
#include <isc/hex.h>
#include <isc/mem.h>
#include <isc/mutex.h>
#include <isc/os.h>
#include <isc/print.h>
#include <isc/random.h>
#include <isc/rwlock.h>
#include <isc/serial.h>
#include <isc/stdio.h>
#include <isc/stdlib.h>
#include <isc/string.h>
#include <isc/task.h>
#include <isc/time.h>
#include <isc/util.h>

#include <dns/db.h>
#include <dns/dbiterator.h>
#include <dns/diff.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/masterdump.h>
#include <dns/nsec.h>
#include <dns/nsec3.h>
#include <dns/rdata.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdataclass.h>
#include <dns/rdatasetiter.h>
#include <dns/rdatastruct.h>
#include <dns/rdatatype.h>
#include <dns/result.h>
#include <dns/soa.h>
#include <dns/time.h>

#include <dst/dst.h>

#include "dnssectool.h"

const char *program = "dnssec-signzone";
int verbose;

typedef struct hashlist hashlist_t;

static int nsec_datatype = dns_rdatatype_nsec;

#define IS_NSEC3	(nsec_datatype == dns_rdatatype_nsec3)
#define OPTOUT(x)	(((x) & DNS_NSEC3FLAG_OPTOUT) != 0)

#define BUFSIZE 2048
#define MAXDSKEYS 8

typedef struct signer_key_struct signer_key_t;

struct signer_key_struct {
	dst_key_t *key;
	isc_boolean_t issigningkey;
	isc_boolean_t isdsk;
	isc_boolean_t isksk;
	isc_boolean_t wasused;
	isc_boolean_t commandline;
	unsigned int position;
	ISC_LINK(signer_key_t) link;
};

#define SIGNER_EVENTCLASS	ISC_EVENTCLASS(0x4453)
#define SIGNER_EVENT_WRITE	(SIGNER_EVENTCLASS + 0)
#define SIGNER_EVENT_WORK	(SIGNER_EVENTCLASS + 1)

#define SOA_SERIAL_KEEP		0
#define SOA_SERIAL_INCREMENT	1
#define SOA_SERIAL_UNIXTIME	2

typedef struct signer_event sevent_t;
struct signer_event {
	ISC_EVENT_COMMON(sevent_t);
	dns_fixedname_t *fname;
	dns_dbnode_t *node;
};

static ISC_LIST(signer_key_t) keylist;
static unsigned int keycount = 0;
isc_rwlock_t keylist_lock;
static isc_stdtime_t starttime = 0, endtime = 0, now;
static int cycle = -1;
static int jitter = 0;
static isc_boolean_t tryverify = ISC_FALSE;
static isc_boolean_t printstats = ISC_FALSE;
static isc_mem_t *mctx = NULL;
static isc_entropy_t *ectx = NULL;
static dns_ttl_t zonettl;
static FILE *fp;
static char *tempfile = NULL;
static const dns_master_style_t *masterstyle;
static dns_masterformat_t inputformat = dns_masterformat_text;
static dns_masterformat_t outputformat = dns_masterformat_text;
static unsigned int nsigned = 0, nretained = 0, ndropped = 0;
static unsigned int nverified = 0, nverifyfailed = 0;
static const char *directory;
static isc_mutex_t namelock, statslock;
static isc_taskmgr_t *taskmgr = NULL;
static dns_db_t *gdb;			/* The database */
static dns_dbversion_t *gversion;	/* The database version */
static dns_dbiterator_t *gdbiter;	/* The database iterator */
static dns_rdataclass_t gclass;		/* The class */
static dns_name_t *gorigin;		/* The database origin */
static int nsec3flags = 0;
static isc_task_t *master = NULL;
static unsigned int ntasks = 0;
static isc_boolean_t shuttingdown = ISC_FALSE, finished = ISC_FALSE;
static isc_boolean_t nokeys = ISC_FALSE;
static isc_boolean_t removefile = ISC_FALSE;
static isc_boolean_t generateds = ISC_FALSE;
static isc_boolean_t ignoreksk = ISC_FALSE;
static dns_name_t *dlv = NULL;
static dns_fixedname_t dlv_fixed;
static dns_master_style_t *dsstyle = NULL;
static unsigned int serialformat = SOA_SERIAL_KEEP;
static unsigned int hash_length = 0;
static isc_boolean_t unknownalg = ISC_FALSE;
static isc_boolean_t disable_zone_check = ISC_FALSE;

#define INCSTAT(counter)		\
	if (printstats) {		\
		LOCK(&statslock);	\
		counter++;		\
		UNLOCK(&statslock);	\
	}

static void
sign(isc_task_t *task, isc_event_t *event);

static isc_boolean_t
nsec3only(dns_dbnode_t *node);

static void
dumpnode(dns_name_t *name, dns_dbnode_t *node) {
	isc_result_t result;

	if (outputformat != dns_masterformat_text)
		return;
	result = dns_master_dumpnodetostream(mctx, gdb, gversion, node, name,
					     masterstyle, fp);
	check_result(result, "dns_master_dumpnodetostream");
}

static signer_key_t *
newkeystruct(dst_key_t *dstkey, isc_boolean_t signwithkey) {
	signer_key_t *key;

	key = isc_mem_get(mctx, sizeof(signer_key_t));
	if (key == NULL)
		fatal("out of memory");
	key->key = dstkey;
	if ((dst_key_flags(dstkey) & DNS_KEYFLAG_KSK) != 0) {
		key->issigningkey = signwithkey;
		key->isksk = ISC_TRUE;
		key->isdsk = ISC_FALSE;
	} else {
		key->issigningkey = signwithkey;
		key->isksk = ISC_FALSE;
		key->isdsk = ISC_TRUE;
	}
	key->wasused = ISC_FALSE;
	key->commandline = ISC_FALSE;
	key->position = keycount++;
	ISC_LINK_INIT(key, link);
	return (key);
}

static void
signwithkey(dns_name_t *name, dns_rdataset_t *rdataset, dns_rdata_t *rdata,
	    dst_key_t *key, isc_buffer_t *b)
{
	isc_result_t result;
	isc_stdtime_t jendtime;

	jendtime = (jitter != 0) ? isc_random_jitter(endtime, jitter) : endtime;
	result = dns_dnssec_sign(name, rdataset, key, &starttime, &jendtime,
				 mctx, b, rdata);
	isc_entropy_stopcallbacksources(ectx);
	if (result != ISC_R_SUCCESS) {
		char keystr[KEY_FORMATSIZE];
		key_format(key, keystr, sizeof(keystr));
		fatal("dnskey '%s' failed to sign data: %s",
		      keystr, isc_result_totext(result));
	}
	INCSTAT(nsigned);

	if (tryverify) {
		result = dns_dnssec_verify(name, rdataset, key,
					   ISC_TRUE, mctx, rdata);
		if (result == ISC_R_SUCCESS) {
			vbprintf(3, "\tsignature verified\n");
			INCSTAT(nverified);
		} else {
			vbprintf(3, "\tsignature failed to verify\n");
			INCSTAT(nverifyfailed);
		}
	}
}

static inline isc_boolean_t
issigningkey(signer_key_t *key) {
	return (key->issigningkey);
}

static inline isc_boolean_t
iszonekey(signer_key_t *key) {
	return (ISC_TF(dns_name_equal(dst_key_name(key->key), gorigin) &&
		       dst_key_iszonekey(key->key)));
}

/*%
 * Find the key if it is in our list.  If it is, return it, otherwise null.
 * No locking is performed here, this must be done by the caller.
 */
static signer_key_t *
keythatsigned_unlocked(dns_rdata_rrsig_t *rrsig) {
	signer_key_t *key;

	key = ISC_LIST_HEAD(keylist);
	while (key != NULL) {
		if (rrsig->keyid == dst_key_id(key->key) &&
		    rrsig->algorithm == dst_key_alg(key->key) &&
		    dns_name_equal(&rrsig->signer, dst_key_name(key->key)))
			return (key);
		key = ISC_LIST_NEXT(key, link);
	}
	return (NULL);
}

/*%
 * Finds the key that generated a RRSIG, if possible.  First look at the keys
 * that we've loaded already, and then see if there's a key on disk.
 */
static signer_key_t *
keythatsigned(dns_rdata_rrsig_t *rrsig) {
	isc_result_t result;
	dst_key_t *pubkey = NULL, *privkey = NULL;
	signer_key_t *key;

	isc_rwlock_lock(&keylist_lock, isc_rwlocktype_read);
	key = keythatsigned_unlocked(rrsig);
	isc_rwlock_unlock(&keylist_lock, isc_rwlocktype_read);
	if (key != NULL)
		return (key);

	/*
	 * We did not find the key in our list.  Get a write lock now, since
	 * we may be modifying the bits.  We could do the tryupgrade() dance,
	 * but instead just get a write lock and check once again to see if
	 * it is on our list.  It's possible someone else may have added it
	 * after all.
	 */
	isc_rwlock_lock(&keylist_lock, isc_rwlocktype_write);

	key = keythatsigned_unlocked(rrsig);
	if (key != NULL) {
		isc_rwlock_unlock(&keylist_lock, isc_rwlocktype_write);
		return (key);
	}

	result = dst_key_fromfile(&rrsig->signer, rrsig->keyid,
				  rrsig->algorithm, DST_TYPE_PUBLIC,
				  NULL, mctx, &pubkey);
	if (result != ISC_R_SUCCESS) {
		isc_rwlock_unlock(&keylist_lock, isc_rwlocktype_write);
		return (NULL);
	}

	result = dst_key_fromfile(&rrsig->signer, rrsig->keyid,
				  rrsig->algorithm,
				  DST_TYPE_PUBLIC | DST_TYPE_PRIVATE,
				  NULL, mctx, &privkey);
	if (result == ISC_R_SUCCESS) {
		dst_key_free(&pubkey);
		key = newkeystruct(privkey, ISC_FALSE);
	} else
		key = newkeystruct(pubkey, ISC_FALSE);
	ISC_LIST_APPEND(keylist, key, link);

	isc_rwlock_unlock(&keylist_lock, isc_rwlocktype_write);
	return (key);
}

/*%
 * Check to see if we expect to find a key at this name.  If we see a RRSIG
 * and can't find the signing key that we expect to find, we drop the rrsig.
 * I'm not sure if this is completely correct, but it seems to work.
 */
static isc_boolean_t
expecttofindkey(dns_name_t *name) {
	unsigned int options = DNS_DBFIND_NOWILD;
	dns_fixedname_t fname;
	isc_result_t result;
	char namestr[DNS_NAME_FORMATSIZE];

	dns_fixedname_init(&fname);
	result = dns_db_find(gdb, name, gversion, dns_rdatatype_dnskey, options,
			     0, NULL, dns_fixedname_name(&fname), NULL, NULL);
	switch (result) {
	case ISC_R_SUCCESS:
	case DNS_R_NXDOMAIN:
	case DNS_R_NXRRSET:
		return (ISC_TRUE);
	case DNS_R_DELEGATION:
	case DNS_R_CNAME:
	case DNS_R_DNAME:
		return (ISC_FALSE);
	}
	dns_name_format(name, namestr, sizeof(namestr));
	fatal("failure looking for '%s DNSKEY' in database: %s",
	      namestr, isc_result_totext(result));
	return (ISC_FALSE); /* removes a warning */
}

static inline isc_boolean_t
setverifies(dns_name_t *name, dns_rdataset_t *set, signer_key_t *key,
	    dns_rdata_t *rrsig)
{
	isc_result_t result;
	result = dns_dnssec_verify(name, set, key->key, ISC_FALSE, mctx, rrsig);
	if (result == ISC_R_SUCCESS) {
		INCSTAT(nverified);
		return (ISC_TRUE);
	} else {
		INCSTAT(nverifyfailed);
		return (ISC_FALSE);
	}
}

/*%
 * Signs a set.  Goes through contortions to decide if each RRSIG should
 * be dropped or retained, and then determines if any new SIGs need to
 * be generated.
 */
static void
signset(dns_diff_t *del, dns_diff_t *add, dns_dbnode_t *node, dns_name_t *name,
	dns_rdataset_t *set)
{
	dns_rdataset_t sigset;
	dns_rdata_t sigrdata = DNS_RDATA_INIT;
	dns_rdata_rrsig_t rrsig;
	signer_key_t *key;
	isc_result_t result;
	isc_boolean_t nosigs = ISC_FALSE;
	isc_boolean_t *wassignedby, *nowsignedby;
	int arraysize;
	dns_difftuple_t *tuple;
	dns_ttl_t ttl;
	int i;
	char namestr[DNS_NAME_FORMATSIZE];
	char typestr[TYPE_FORMATSIZE];
	char sigstr[SIG_FORMATSIZE];

	dns_name_format(name, namestr, sizeof(namestr));
	type_format(set->type, typestr, sizeof(typestr));

	ttl = ISC_MIN(set->ttl, endtime - starttime);

	dns_rdataset_init(&sigset);
	result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_rrsig,
				     set->type, 0, &sigset, NULL);
	if (result == ISC_R_NOTFOUND) {
		result = ISC_R_SUCCESS;
		nosigs = ISC_TRUE;
	}
	if (result != ISC_R_SUCCESS)
		fatal("failed while looking for '%s RRSIG %s': %s",
		      namestr, typestr, isc_result_totext(result));

	vbprintf(1, "%s/%s:\n", namestr, typestr);

	arraysize = keycount;
	if (!nosigs)
		arraysize += dns_rdataset_count(&sigset);
	wassignedby = isc_mem_get(mctx, arraysize * sizeof(isc_boolean_t));
	nowsignedby = isc_mem_get(mctx, arraysize * sizeof(isc_boolean_t));
	if (wassignedby == NULL || nowsignedby == NULL)
		fatal("out of memory");

	for (i = 0; i < arraysize; i++)
		wassignedby[i] = nowsignedby[i] = ISC_FALSE;

	if (nosigs)
		result = ISC_R_NOMORE;
	else
		result = dns_rdataset_first(&sigset);

	while (result == ISC_R_SUCCESS) {
		isc_boolean_t expired, future;
		isc_boolean_t keep = ISC_FALSE, resign = ISC_FALSE;

		dns_rdataset_current(&sigset, &sigrdata);

		result = dns_rdata_tostruct(&sigrdata, &rrsig, NULL);
		check_result(result, "dns_rdata_tostruct");

		future = isc_serial_lt(now, rrsig.timesigned);

		key = keythatsigned(&rrsig);
		sig_format(&rrsig, sigstr, sizeof(sigstr));
		if (key != NULL && issigningkey(key))
			expired = isc_serial_gt(now + cycle, rrsig.timeexpire);
		else
			expired = isc_serial_gt(now, rrsig.timeexpire);

		if (isc_serial_gt(rrsig.timesigned, rrsig.timeexpire)) {
			/* rrsig is dropped and not replaced */
			vbprintf(2, "\trrsig by %s dropped - "
				 "invalid validity period\n",
				 sigstr);
		} else if (key == NULL && !future &&
			 expecttofindkey(&rrsig.signer))
		{
			/* rrsig is dropped and not replaced */
			vbprintf(2, "\trrsig by %s dropped - "
				 "private dnskey not found\n",
				 sigstr);
		} else if (key == NULL || future) {
			vbprintf(2, "\trrsig by %s %s - dnskey not found\n",
				 expired ? "retained" : "dropped", sigstr);
			if (!expired)
				keep = ISC_TRUE;
		} else if (issigningkey(key)) {
			if (!expired && setverifies(name, set, key, &sigrdata))
			{
				vbprintf(2, "\trrsig by %s retained\n", sigstr);
				keep = ISC_TRUE;
				wassignedby[key->position] = ISC_TRUE;
				nowsignedby[key->position] = ISC_TRUE;
				key->wasused = ISC_TRUE;
			} else {
				vbprintf(2, "\trrsig by %s dropped - %s\n",
					 sigstr,
					 expired ? "expired" :
						   "failed to verify");
				wassignedby[key->position] = ISC_TRUE;
				resign = ISC_TRUE;
			}
		} else if (iszonekey(key)) {
			if (!expired && setverifies(name, set, key, &sigrdata))
			{
				vbprintf(2, "\trrsig by %s retained\n", sigstr);
				keep = ISC_TRUE;
				wassignedby[key->position] = ISC_TRUE;
				nowsignedby[key->position] = ISC_TRUE;
				key->wasused = ISC_TRUE;
			} else {
				vbprintf(2, "\trrsig by %s dropped - %s\n",
					 sigstr,
					 expired ? "expired" :
						   "failed to verify");
				wassignedby[key->position] = ISC_TRUE;
			}
		} else if (!expired) {
			vbprintf(2, "\trrsig by %s retained\n", sigstr);
			keep = ISC_TRUE;
		} else {
			vbprintf(2, "\trrsig by %s expired\n", sigstr);
		}

		if (keep) {
			nowsignedby[key->position] = ISC_TRUE;
			INCSTAT(nretained);
			if (sigset.ttl != ttl) {
				vbprintf(2, "\tfixing ttl %s\n", sigstr);
				tuple = NULL;
				result = dns_difftuple_create(mctx,
							      DNS_DIFFOP_DEL,
							      name, sigset.ttl,
							      &sigrdata,
							      &tuple);
				check_result(result, "dns_difftuple_create");
				dns_diff_append(del, &tuple);
				result = dns_difftuple_create(mctx,
							      DNS_DIFFOP_ADD,
							      name, ttl,
							      &sigrdata,
							      &tuple);
				check_result(result, "dns_difftuple_create");
				dns_diff_append(add, &tuple);
			}
		} else {
			tuple = NULL;
			result = dns_difftuple_create(mctx, DNS_DIFFOP_DEL,
						      name, sigset.ttl,
						      &sigrdata, &tuple);
			check_result(result, "dns_difftuple_create");
			dns_diff_append(del, &tuple);
			INCSTAT(ndropped);
		}

		if (resign) {
			isc_buffer_t b;
			dns_rdata_t trdata = DNS_RDATA_INIT;
			unsigned char array[BUFSIZE];
			char keystr[KEY_FORMATSIZE];

			INSIST(!keep);

			key_format(key->key, keystr, sizeof(keystr));
			vbprintf(1, "\tresigning with dnskey %s\n", keystr);
			isc_buffer_init(&b, array, sizeof(array));
			signwithkey(name, set, &trdata, key->key, &b);
			nowsignedby[key->position] = ISC_TRUE;
			key->wasused = ISC_TRUE;
			tuple = NULL;
			result = dns_difftuple_create(mctx, DNS_DIFFOP_ADD,
						      name, ttl, &trdata,
						      &tuple);
			check_result(result, "dns_difftuple_create");
			dns_diff_append(add, &tuple);
		}

		dns_rdata_reset(&sigrdata);
		dns_rdata_freestruct(&rrsig);
		result = dns_rdataset_next(&sigset);
	}
	if (result == ISC_R_NOMORE)
		result = ISC_R_SUCCESS;

	check_result(result, "dns_rdataset_first/next");
	if (dns_rdataset_isassociated(&sigset))
		dns_rdataset_disassociate(&sigset);

	for (key = ISC_LIST_HEAD(keylist);
	     key != NULL;
	     key = ISC_LIST_NEXT(key, link))
	{
		isc_buffer_t b;
		dns_rdata_t trdata;
		unsigned char array[BUFSIZE];
		char keystr[KEY_FORMATSIZE];

		if (nowsignedby[key->position])
			continue;

		if (!key->issigningkey)
			continue;
		if (!(ignoreksk || key->isdsk ||
		      (key->isksk &&
		       set->type == dns_rdatatype_dnskey &&
		       dns_name_equal(name, gorigin))))
			continue;

		key_format(key->key, keystr, sizeof(keystr));
		vbprintf(1, "\tsigning with dnskey %s\n", keystr);
		dns_rdata_init(&trdata);
		isc_buffer_init(&b, array, sizeof(array));
		signwithkey(name, set, &trdata, key->key, &b);
		key->wasused = ISC_TRUE;
		tuple = NULL;
		result = dns_difftuple_create(mctx, DNS_DIFFOP_ADD, name,
					      ttl, &trdata, &tuple);
		check_result(result, "dns_difftuple_create");
		dns_diff_append(add, &tuple);
	}

	isc_mem_put(mctx, wassignedby, arraysize * sizeof(isc_boolean_t));
	isc_mem_put(mctx, nowsignedby, arraysize * sizeof(isc_boolean_t));
}

struct hashlist {
	unsigned char *hashbuf;
	size_t entries;
	size_t size;
	size_t length;
};

static void
hashlist_init(hashlist_t *l, unsigned int nodes, unsigned int length) {

	l->entries = 0;
	l->length = length + 1;

	if (nodes != 0) {
		l->size = nodes;
		l->hashbuf = malloc(l->size * l->length);
		if (l->hashbuf == NULL)
			l->size = 0;
	} else {
		l->size = 0;
		l->hashbuf = NULL;
	}
}

static void
hashlist_add(hashlist_t *l, const unsigned char *hash, size_t len)
{

	REQUIRE(len <= l->length);

	if (l->entries == l->size) {
		l->size = l->size * 2 + 100;
		l->hashbuf = realloc(l->hashbuf, l->size * l->length);
	}
	memset(l->hashbuf + l->entries * l->length, 0, l->length);
	memcpy(l->hashbuf + l->entries * l->length, hash, len);
	l->entries++;
}

static void
hashlist_add_dns_name(hashlist_t *l, /*const*/ dns_name_t *name,
		      unsigned int hashalg, unsigned int iterations,
		      const unsigned char *salt, size_t salt_length,
		      isc_boolean_t speculative)
{
	char nametext[DNS_NAME_FORMATSIZE];
	unsigned char hash[NSEC3_MAX_HASH_LENGTH + 1];
	unsigned int len;
	size_t i;

	len = isc_iterated_hash(hash, hashalg, iterations, salt, salt_length,
				name->ndata, name->length);
	if (verbose) {
		dns_name_format(name, nametext, sizeof nametext);
		for (i = 0 ; i < len; i++)
			fprintf(stderr, "%02x", hash[i]);
		fprintf(stderr, " %s\n", nametext);
	}
	hash[len++] = speculative ? 1 : 0;
	hashlist_add(l, hash, len);
}

static int
hashlist_comp(const void *a, const void *b) {
	return (memcmp(a, b, hash_length + 1));
}

static void
hashlist_sort(hashlist_t *l) {
	qsort(l->hashbuf, l->entries, l->length, hashlist_comp);
}

static isc_boolean_t
hashlist_hasdup(hashlist_t *l) {
	unsigned char *current;
	unsigned char *next = l->hashbuf;
	size_t entries = l->entries;

	/*
	 * Skip initial speculative wild card hashs.
	 */
	while (entries > 0U && next[l->length-1] != 0U) {
		next += l->length;
		entries--;
	}

	current = next;
	while (entries-- > 1U) {
		next += l->length;
		if (next[l->length-1] != 0)
			continue;
		if (memcmp(current, next, l->length - 1) == 0)
			return (ISC_TRUE);
		current = next;
	}
	return (ISC_FALSE);
}

static const unsigned char *
hashlist_findnext(const hashlist_t *l,
		  const unsigned char hash[NSEC3_MAX_HASH_LENGTH])
{
	unsigned int entries = l->entries;
	const unsigned char *next = bsearch(hash, l->hashbuf, l->entries,
					    l->length, hashlist_comp);
	INSIST(next != NULL);

	do {
		if (next < l->hashbuf + (l->entries - 1) * l->length)
			next += l->length;
		else
			next = l->hashbuf;
		if (next[l->length - 1] == 0)
			break;
	} while (entries-- > 1);
	INSIST(entries != 0);
	return (next);
}

static isc_boolean_t
hashlist_exists(const hashlist_t *l,
		const unsigned char hash[NSEC3_MAX_HASH_LENGTH])
{
	if (bsearch(hash, l->hashbuf, l->entries, l->length, hashlist_comp))
		return (ISC_TRUE);
	else
		return (ISC_FALSE);
}

static void
addnowildcardhash(hashlist_t *l, /*const*/ dns_name_t *name,
		  unsigned int hashalg, unsigned int iterations,
		  const unsigned char *salt, size_t salt_length)
{
	dns_fixedname_t fixed;
	dns_name_t *wild;
	dns_dbnode_t *node = NULL;
	isc_result_t result;
	char namestr[DNS_NAME_FORMATSIZE];

	dns_fixedname_init(&fixed);
	wild = dns_fixedname_name(&fixed);

	result = dns_name_concatenate(dns_wildcardname, name, wild, NULL);
	if (result == ISC_R_NOSPACE)
		return;
	check_result(result,"addnowildcardhash: dns_name_concatenate()");

	result = dns_db_findnode(gdb, wild, ISC_FALSE, &node);
	if (result == ISC_R_SUCCESS) {
		dns_db_detachnode(gdb, &node);
		return;
	}

	if (verbose) {
		dns_name_format(wild, namestr, sizeof(namestr));
		fprintf(stderr, "adding no-wildcardhash for %s\n", namestr);
	}

	hashlist_add_dns_name(l, wild, hashalg, iterations, salt, salt_length,
			      ISC_TRUE);
}

static void
opendb(const char *prefix, dns_name_t *name, dns_rdataclass_t rdclass,
       dns_db_t **dbp)
{
	char filename[256];
	isc_buffer_t b;
	isc_result_t result;

	isc_buffer_init(&b, filename, sizeof(filename));
	if (directory != NULL) {
		isc_buffer_putstr(&b, directory);
		if (directory[strlen(directory) - 1] != '/')
			isc_buffer_putstr(&b, "/");
	}
	isc_buffer_putstr(&b, prefix);
	result = dns_name_tofilenametext(name, ISC_FALSE, &b);
	check_result(result, "dns_name_tofilenametext()");
	if (isc_buffer_availablelength(&b) == 0) {
		char namestr[DNS_NAME_FORMATSIZE];
		dns_name_format(name, namestr, sizeof(namestr));
		fatal("name '%s' is too long", namestr);
	}
	isc_buffer_putuint8(&b, 0);

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

	result = dns_db_load(*dbp, filename);
	if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE)
		dns_db_detach(dbp);
}

/*%
 * Loads the key set for a child zone, if there is one, and builds DS records.
 */
static isc_result_t
loadds(dns_name_t *name, isc_uint32_t ttl, dns_rdataset_t *dsset) {
	dns_db_t *db = NULL;
	dns_dbversion_t *ver = NULL;
	dns_dbnode_t *node = NULL;
	isc_result_t result;
	dns_rdataset_t keyset;
	dns_rdata_t key, ds;
	unsigned char dsbuf[DNS_DS_BUFFERSIZE];
	dns_diff_t diff;
	dns_difftuple_t *tuple = NULL;

	opendb("keyset-", name, gclass, &db);
	if (db == NULL)
		return (ISC_R_NOTFOUND);

	result = dns_db_findnode(db, name, ISC_FALSE, &node);
	if (result != ISC_R_SUCCESS) {
		dns_db_detach(&db);
		return (DNS_R_BADDB);
	}
	dns_rdataset_init(&keyset);
	result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_dnskey, 0, 0,
				     &keyset, NULL);
	if (result != ISC_R_SUCCESS) {
		dns_db_detachnode(db, &node);
		dns_db_detach(&db);
		return (result);
	}

	vbprintf(2, "found DNSKEY records\n");

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

	dns_diff_init(mctx, &diff);

	for (result = dns_rdataset_first(&keyset);
	     result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(&keyset))
	{
		dns_rdata_init(&key);
		dns_rdata_init(&ds);
		dns_rdataset_current(&keyset, &key);
		result = dns_ds_buildrdata(name, &key, DNS_DSDIGEST_SHA1,
					   dsbuf, &ds);
		check_result(result, "dns_ds_buildrdata");

		result = dns_difftuple_create(mctx, DNS_DIFFOP_ADD, name,
					      ttl, &ds, &tuple);
		check_result(result, "dns_difftuple_create");
		dns_diff_append(&diff, &tuple);

		dns_rdata_reset(&ds);
		result = dns_ds_buildrdata(name, &key, DNS_DSDIGEST_SHA256,
					   dsbuf, &ds);
		check_result(result, "dns_ds_buildrdata");

		result = dns_difftuple_create(mctx, DNS_DIFFOP_ADD, name,
					      ttl, &ds, &tuple);
		check_result(result, "dns_difftuple_create");
		dns_diff_append(&diff, &tuple);
	}
	result = dns_diff_apply(&diff, db, ver);
	check_result(result, "dns_diff_apply");
	dns_diff_clear(&diff);

	dns_db_closeversion(db, &ver, ISC_TRUE);

	result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_ds, 0, 0,
				     dsset, NULL);
	check_result(result, "dns_db_findrdataset");

	dns_rdataset_disassociate(&keyset);
	dns_db_detachnode(db, &node);
	dns_db_detach(&db);
	return (result);
}

static isc_boolean_t
delegation(dns_name_t *name, dns_dbnode_t *node, isc_uint32_t *ttlp) {
	dns_rdataset_t nsset;
	isc_result_t result;

	if (dns_name_equal(name, gorigin))
		return (ISC_FALSE);

	dns_rdataset_init(&nsset);
	result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_ns,
				     0, 0, &nsset, NULL);
	if (dns_rdataset_isassociated(&nsset)) {
		if (ttlp != NULL)
			*ttlp = nsset.ttl;
		dns_rdataset_disassociate(&nsset);
	}

	return (ISC_TF(result == ISC_R_SUCCESS));
}

static isc_boolean_t
secure(dns_name_t *name, dns_dbnode_t *node) {
	dns_rdataset_t dsset;
	isc_result_t result;

	if (dns_name_equal(name, gorigin))
		return (ISC_FALSE);

	dns_rdataset_init(&dsset);
	result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_ds,
				     0, 0, &dsset, NULL);
	if (dns_rdataset_isassociated(&dsset))
		dns_rdataset_disassociate(&dsset);

	return (ISC_TF(result == ISC_R_SUCCESS));
}

/*%
 * Signs all records at a name.
 */
static void
signname(dns_dbnode_t *node, dns_name_t *name) {
	isc_result_t result;
	dns_rdataset_t rdataset;
	dns_rdatasetiter_t *rdsiter;
	isc_boolean_t isdelegation = ISC_FALSE;
	dns_diff_t del, add;
	char namestr[DNS_NAME_FORMATSIZE];

	dns_rdataset_init(&rdataset);
	dns_name_format(name, namestr, sizeof(namestr));

	/*
	 * Determine if this is a delegation point.
	 */
	if (delegation(name, node, NULL))
		isdelegation = ISC_TRUE;

	/*
	 * Now iterate through the rdatasets.
	 */
	dns_diff_init(mctx, &del);
	dns_diff_init(mctx, &add);
	rdsiter = NULL;
	result = dns_db_allrdatasets(gdb, node, gversion, 0, &rdsiter);
	check_result(result, "dns_db_allrdatasets()");
	result = dns_rdatasetiter_first(rdsiter);
	while (result == ISC_R_SUCCESS) {
		dns_rdatasetiter_current(rdsiter, &rdataset);

		/* If this is a RRSIG set, skip it. */
		if (rdataset.type == dns_rdatatype_rrsig)
			goto skip;

		/*
		 * If this name is a delegation point, skip all records
		 * except NSEC and DS sets.  Otherwise check that there
		 * isn't a DS record.
		 */
		if (isdelegation) {
			if (rdataset.type != nsec_datatype &&
			    rdataset.type != dns_rdatatype_ds)
				goto skip;
		} else if (rdataset.type == dns_rdatatype_ds) {
			char namebuf[DNS_NAME_FORMATSIZE];
			dns_name_format(name, namebuf, sizeof(namebuf));
			fatal("'%s': found DS RRset without NS RRset\n",
			      namebuf);
		}

		signset(&del, &add, node, name, &rdataset);

 skip:
		dns_rdataset_disassociate(&rdataset);
		result = dns_rdatasetiter_next(rdsiter);
	}
	if (result != ISC_R_NOMORE)
		fatal("rdataset iteration for name '%s' failed: %s",
		      namestr, isc_result_totext(result));

	dns_rdatasetiter_destroy(&rdsiter);

	result = dns_diff_applysilently(&del, gdb, gversion);
	if (result != ISC_R_SUCCESS)
		fatal("failed to delete SIGs at node '%s': %s",
		      namestr, isc_result_totext(result));

	result = dns_diff_applysilently(&add, gdb, gversion);
	if (result != ISC_R_SUCCESS)
		fatal("failed to add SIGs at node '%s': %s",
		      namestr, isc_result_totext(result));

	dns_diff_clear(&del);
	dns_diff_clear(&add);
}

static inline isc_boolean_t
active_node(dns_dbnode_t *node) {
	dns_rdatasetiter_t *rdsiter = NULL;
	dns_rdatasetiter_t *rdsiter2 = NULL;
	isc_boolean_t active = ISC_FALSE;
	isc_result_t result;
	dns_rdataset_t rdataset;
	dns_rdatatype_t type;
	dns_rdatatype_t covers;
	isc_boolean_t found;

	dns_rdataset_init(&rdataset);
	result = dns_db_allrdatasets(gdb, node, gversion, 0, &rdsiter);
	check_result(result, "dns_db_allrdatasets()");
	result = dns_rdatasetiter_first(rdsiter);
	while (result == ISC_R_SUCCESS) {
		dns_rdatasetiter_current(rdsiter, &rdataset);
		if (rdataset.type != dns_rdatatype_nsec &&
		    rdataset.type != dns_rdatatype_nsec3 &&
		    rdataset.type != dns_rdatatype_rrsig)
			active = ISC_TRUE;
		dns_rdataset_disassociate(&rdataset);
		if (!active)
			result = dns_rdatasetiter_next(rdsiter);
		else
			result = ISC_R_NOMORE;
	}
	if (result != ISC_R_NOMORE)
		fatal("rdataset iteration failed: %s",
		      isc_result_totext(result));

	if (!active && nsec_datatype == dns_rdatatype_nsec) {
		/*%
		 * The node is empty of everything but NSEC / RRSIG records.
		 */
		for (result = dns_rdatasetiter_first(rdsiter);
		     result == ISC_R_SUCCESS;
		     result = dns_rdatasetiter_next(rdsiter)) {
			dns_rdatasetiter_current(rdsiter, &rdataset);
			result = dns_db_deleterdataset(gdb, node, gversion,
						       rdataset.type,
						       rdataset.covers);
			check_result(result, "dns_db_deleterdataset()");
			dns_rdataset_disassociate(&rdataset);
		}
		if (result != ISC_R_NOMORE)
			fatal("rdataset iteration failed: %s",
			      isc_result_totext(result));
	} else {
		/*
		 * Delete RRSIGs for types that no longer exist.
		 */
		result = dns_db_allrdatasets(gdb, node, gversion, 0, &rdsiter2);
		check_result(result, "dns_db_allrdatasets()");
		for (result = dns_rdatasetiter_first(rdsiter);
		     result == ISC_R_SUCCESS;
		     result = dns_rdatasetiter_next(rdsiter)) {
			dns_rdatasetiter_current(rdsiter, &rdataset);
			type = rdataset.type;
			covers = rdataset.covers;
			dns_rdataset_disassociate(&rdataset);
			if (type != dns_rdatatype_rrsig)
				continue;
			found = ISC_FALSE;
			for (result = dns_rdatasetiter_first(rdsiter2);
			     !found && result == ISC_R_SUCCESS;
			     result = dns_rdatasetiter_next(rdsiter2)) {
				dns_rdatasetiter_current(rdsiter2, &rdataset);
				if (rdataset.type == covers)
					found = ISC_TRUE;
				dns_rdataset_disassociate(&rdataset);
			}
			if (!found) {
				if (result != ISC_R_NOMORE)
					fatal("rdataset iteration failed: %s",
					      isc_result_totext(result));
				result = dns_db_deleterdataset(gdb, node,
							       gversion, type,
							       covers);
				check_result(result,
					     "dns_db_deleterdataset(rrsig)");
			} else if (result != ISC_R_NOMORE &&
				   result != ISC_R_SUCCESS)
				fatal("rdataset iteration failed: %s",
				      isc_result_totext(result));
		}
		if (result != ISC_R_NOMORE)
			fatal("rdataset iteration failed: %s",
			      isc_result_totext(result));
		dns_rdatasetiter_destroy(&rdsiter2);

#if 0
		/*
		 * Delete all NSEC records and RRSIG(NSEC) if we are in
		 * NSEC3 mode and vica versa.
		 */
		for (result = dns_rdatasetiter_first(rdsiter2);
		     result == ISC_R_SUCCESS;
		     result = dns_rdatasetiter_next(rdsiter2)) {
			dns_rdatasetiter_current(rdsiter, &rdataset);
			type = rdataset.type;
			covers = rdataset.covers;
			if (type == dns_rdatatype_rrsig)
				type = covers;
			dns_rdataset_disassociate(&rdataset);
			if (type == nsec_datatype ||
			    (type != dns_rdatatype_nsec &&
			     type != dns_rdatatype_nsec3))
				continue;
			if (covers != 0)
				type = dns_rdatatype_rrsig;
			result = dns_db_deleterdataset(gdb, node, gversion,
						       type, covers);
			check_result(result, "dns_db_deleterdataset()");
		}
#endif
	}
	dns_rdatasetiter_destroy(&rdsiter);

	return (active);
}

/*%
 * Extracts the TTL from the SOA.
 */
static dns_ttl_t
soattl(void) {
	dns_rdataset_t soaset;
	dns_fixedname_t fname;
	dns_name_t *name;
	isc_result_t result;
	dns_ttl_t ttl;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	dns_rdata_soa_t soa;

	dns_fixedname_init(&fname);
	name = dns_fixedname_name(&fname);
	dns_rdataset_init(&soaset);
	result = dns_db_find(gdb, gorigin, gversion, dns_rdatatype_soa,
			     0, 0, NULL, name, &soaset, NULL);
	if (result != ISC_R_SUCCESS)
		fatal("failed to find an SOA at the zone apex: %s",
		      isc_result_totext(result));

	result = dns_rdataset_first(&soaset);
	check_result(result, "dns_rdataset_first");
	dns_rdataset_current(&soaset, &rdata);
	result = dns_rdata_tostruct(&rdata, &soa, NULL);
	check_result(result, "dns_rdata_tostruct");
	ttl = soa.minimum;
	dns_rdataset_disassociate(&soaset);
	return (ttl);
}

/*%
 * Increment (or set if nonzero) the SOA serial
 */
static isc_result_t
setsoaserial(isc_uint32_t serial) {
	isc_result_t result;
	dns_dbnode_t *node = NULL;
	dns_rdataset_t rdataset;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	isc_uint32_t old_serial, new_serial;

	result = dns_db_getoriginnode(gdb, &node);
	if (result != ISC_R_SUCCESS)
		return result;

	dns_rdataset_init(&rdataset);

	result = dns_db_findrdataset(gdb, node, gversion,
				     dns_rdatatype_soa, 0,
				     0, &rdataset, NULL);
	if (result != ISC_R_SUCCESS)
		goto cleanup;

	result = dns_rdataset_first(&rdataset);
	RUNTIME_CHECK(result == ISC_R_SUCCESS);

	dns_rdataset_current(&rdataset, &rdata);

	old_serial = dns_soa_getserial(&rdata);

	if (serial) {
		/* Set SOA serial to the value provided. */
		new_serial = serial;
	} else {
		/* Increment SOA serial using RFC 1982 arithmetics */
		new_serial = (old_serial + 1) & 0xFFFFFFFF;
		if (new_serial == 0)
			new_serial = 1;
	}

	/* If the new serial is not likely to cause a zone transfer
	 * (a/ixfr) from servers having the old serial, warn the user.
	 *
	 * RFC1982 section 7 defines the maximum increment to be
	 * (2^(32-1))-1.  Using u_int32_t arithmetic, we can do a single
	 * comparison.  (5 - 6 == (2^32)-1, not negative-one)
	 */
	if (new_serial == old_serial ||
	    (new_serial - old_serial) > 0x7fffffffU)
		fprintf(stderr, "%s: warning: Serial number not advanced, "
			"zone may not transfer\n", program);

	dns_soa_setserial(new_serial, &rdata);

	result = dns_db_deleterdataset(gdb, node, gversion,
				       dns_rdatatype_soa, 0);
	check_result(result, "dns_db_deleterdataset");
	if (result != ISC_R_SUCCESS)
		goto cleanup;

	result = dns_db_addrdataset(gdb, node, gversion,
				    0, &rdataset, 0, NULL);
	check_result(result, "dns_db_addrdataset");
	if (result != ISC_R_SUCCESS)
		goto cleanup;

cleanup:
	dns_rdataset_disassociate(&rdataset);
	if (node != NULL)
		dns_db_detachnode(gdb, &node);
	dns_rdata_reset(&rdata);

	return (result);
}

/*%
 * Delete any RRSIG records at a node.
 */
static void
cleannode(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node) {
	dns_rdatasetiter_t *rdsiter = NULL;
	dns_rdataset_t set;
	isc_result_t result, dresult;

	if (outputformat != dns_masterformat_text || !disable_zone_check)
		return;

	dns_rdataset_init(&set);
	result = dns_db_allrdatasets(db, node, version, 0, &rdsiter);
	check_result(result, "dns_db_allrdatasets");
	result = dns_rdatasetiter_first(rdsiter);
	while (result == ISC_R_SUCCESS) {
		isc_boolean_t destroy = ISC_FALSE;
		dns_rdatatype_t covers = 0;
		dns_rdatasetiter_current(rdsiter, &set);
		if (set.type == dns_rdatatype_rrsig) {
			covers = set.covers;
			destroy = ISC_TRUE;
		}
		dns_rdataset_disassociate(&set);
		result = dns_rdatasetiter_next(rdsiter);
		if (destroy) {
			dresult = dns_db_deleterdataset(db, node, version,
							dns_rdatatype_rrsig,
							covers);
			check_result(dresult, "dns_db_deleterdataset");
		}
	}
	if (result != ISC_R_NOMORE)
		fatal("rdataset iteration failed: %s",
		      isc_result_totext(result));
	dns_rdatasetiter_destroy(&rdsiter);
}

/*%
 * Set up the iterator and global state before starting the tasks.
 */
static void
presign(void) {
	isc_result_t result;

	gdbiter = NULL;
	result = dns_db_createiterator(gdb, 0, &gdbiter);
	check_result(result, "dns_db_createiterator()");
}

/*%
 * Clean up the iterator and global state after the tasks complete.
 */
static void
postsign(void) {
	dns_dbiterator_destroy(&gdbiter);
}

static isc_boolean_t
goodsig(dns_rdata_t *sigrdata, dns_name_t *name, dns_rdataset_t *keyrdataset,
	dns_rdataset_t *rdataset)
{
	dns_rdata_dnskey_t key;
	dns_rdata_rrsig_t sig;
	dst_key_t *dstkey = NULL;
	isc_result_t result;

	dns_rdata_tostruct(sigrdata, &sig, NULL);

	for (result = dns_rdataset_first(keyrdataset);
	     result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(keyrdataset)) {
		dns_rdata_t rdata = DNS_RDATA_INIT;
		dns_rdataset_current(keyrdataset, &rdata);
		dns_rdata_tostruct(&rdata, &key, NULL);
		result = dns_dnssec_keyfromrdata(gorigin, &rdata, mctx,
						 &dstkey);
		if (result != ISC_R_SUCCESS)
			return (ISC_FALSE);
		if (sig.algorithm != key.algorithm ||
		    sig.keyid != dst_key_id(dstkey) ||
		    !dns_name_equal(&sig.signer, gorigin)) {
			dst_key_free(&dstkey);
			continue;
		}
		result = dns_dnssec_verify(name, rdataset, dstkey, ISC_FALSE,
					   mctx, sigrdata);
		dst_key_free(&dstkey);
		if (result == ISC_R_SUCCESS)
			return(ISC_TRUE);
	}
	return (ISC_FALSE);
}

static void
verifyset(dns_rdataset_t *rdataset, dns_name_t *name, dns_dbnode_t *node,
	  dns_rdataset_t *keyrdataset, unsigned char *ksk_algorithms,
	   char *bad_algorithms)
{
	unsigned char set_algorithms[256];
	char namebuf[DNS_NAME_FORMATSIZE];
	char algbuf[80];
	char typebuf[80];
	dns_rdataset_t sigrdataset;
	dns_rdatasetiter_t *rdsiter = NULL;
	isc_result_t result;
	int i;

	dns_rdataset_init(&sigrdataset);
	result = dns_db_allrdatasets(gdb, node, gversion, 0, &rdsiter);
	check_result(result, "dns_db_allrdatasets()");
	for (result = dns_rdatasetiter_first(rdsiter);
	     result == ISC_R_SUCCESS;
	     result = dns_rdatasetiter_next(rdsiter)) {
		dns_rdatasetiter_current(rdsiter, &sigrdataset);
		if (sigrdataset.type == dns_rdatatype_rrsig &&
		    sigrdataset.covers == rdataset->type)
			break;
		dns_rdataset_disassociate(&sigrdataset);
	}
	if (result != ISC_R_SUCCESS) {
		dns_name_format(name, namebuf, sizeof(namebuf));
		type_format(rdataset->type, typebuf, sizeof(typebuf));
		fprintf(stderr, "no signatures for %s/%s\n", namebuf, typebuf);
		for (i = 0; i < 256; i++)
			if (ksk_algorithms[i])
				bad_algorithms[i] = 1;
		return;
	}

	memset(set_algorithms, 0, sizeof(set_algorithms));
	for (result = dns_rdataset_first(&sigrdataset);
	     result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(&sigrdataset)) {
		dns_rdata_t rdata = DNS_RDATA_INIT;
		dns_rdata_rrsig_t sig;

		dns_rdataset_current(&sigrdataset, &rdata);
		dns_rdata_tostruct(&rdata, &sig, NULL);
		if (set_algorithms[sig.algorithm] ||
		    !ksk_algorithms[sig.algorithm])
			continue;
		if (goodsig(&rdata, name, keyrdataset, rdataset))
			set_algorithms[sig.algorithm] = 1;
	}
	dns_rdatasetiter_destroy(&rdsiter);
	if (memcmp(set_algorithms, ksk_algorithms, sizeof(set_algorithms))) {
		dns_name_format(name, namebuf, sizeof(namebuf));
		type_format(rdataset->type, typebuf, sizeof(typebuf));
		for (i = 0; i < 256; i++)
			if (ksk_algorithms[i] && !set_algorithms[i]) {
				alg_format(i, algbuf, sizeof(algbuf));
				fprintf(stderr, "Missing %s signature for "
					"%s %s\n", algbuf, namebuf, typebuf);
				bad_algorithms[i] = 1;
			}
	}
	dns_rdataset_disassociate(&sigrdataset);
}

static void
verifynode(dns_name_t *name, dns_dbnode_t *node, isc_boolean_t delegation,
	   dns_rdataset_t *keyrdataset, unsigned char *ksk_algorithms,
	   unsigned char *bad_algorithms)
{
	dns_rdataset_t rdataset;
	dns_rdatasetiter_t *rdsiter = NULL;
	isc_result_t result;

	result = dns_db_allrdatasets(gdb, node, gversion, 0, &rdsiter);
	check_result(result, "dns_db_allrdatasets()");
	result = dns_rdatasetiter_first(rdsiter);
	dns_rdataset_init(&rdataset);
	while (result == ISC_R_SUCCESS) {
		dns_rdatasetiter_current(rdsiter, &rdataset);
		if (rdataset.type != dns_rdatatype_rrsig &&
		    rdataset.type != dns_rdatatype_dnskey &&
		    (!delegation || rdataset.type == dns_rdatatype_ds ||
		     rdataset.type == dns_rdatatype_nsec)) {
			verifyset(&rdataset, name, node, keyrdataset,
				  ksk_algorithms, bad_algorithms);
		}
		dns_rdataset_disassociate(&rdataset);
		result = dns_rdatasetiter_next(rdsiter);
	}
	if (result != ISC_R_NOMORE)
		fatal("rdataset iteration failed: %s",
		      isc_result_totext(result));
	dns_rdatasetiter_destroy(&rdsiter);
}

/*%
 * Verify that certain things are sane:
 *
 *   The apex has a DNSKEY record with at least one KSK and at least
 *   one ZSK.
 *
 *   The DNSKEY record was signed with at least one of the KSKs in this
 *   set.
 *
 *   The rest of the zone was signed with at least one of the ZSKs
 *   present in the DNSKEY RRSET.
 */
static void
verifyzone(void) {
	char algbuf[80];
	dns_dbiterator_t *dbiter = NULL;
	dns_dbnode_t *node = NULL, *nextnode = NULL;
	dns_fixedname_t fname, fnextname, fzonecut;
	dns_name_t *name, *nextname, *zonecut;
	dns_rdata_dnskey_t dnskey;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	dns_rdataset_t rdataset;
	dns_rdataset_t sigrdataset;
	int i;
	isc_boolean_t done = ISC_FALSE;
	isc_boolean_t first = ISC_TRUE;
	isc_boolean_t goodksk = ISC_FALSE;
	isc_boolean_t goodzsk = ISC_FALSE;
	isc_result_t result;
	unsigned char revoked[256];
	unsigned char standby[256];
	unsigned char ksk_algorithms[256];
	unsigned char zsk_algorithms[256];
	unsigned char bad_algorithms[256];
#ifdef ALLOW_KSKLESS_ZONES
	isc_boolean_t allzsksigned = ISC_TRUE;
	unsigned char self_algorithms[256];
#endif

	if (disable_zone_check)
		return;

	result = dns_db_findnode(gdb, gorigin, ISC_FALSE, &node);
	if (result != ISC_R_SUCCESS)
		fatal("failed to find the zone's origin: %s",
		      isc_result_totext(result));

	dns_rdataset_init(&rdataset);
	dns_rdataset_init(&sigrdataset);
	result = dns_db_findrdataset(gdb, node, gversion,
				     dns_rdatatype_dnskey,
				     0, 0, &rdataset, &sigrdataset);
	dns_db_detachnode(gdb, &node);
	if (result != ISC_R_SUCCESS)
		fatal("cannot find DNSKEY rrset\n");

	if (!dns_rdataset_isassociated(&sigrdataset))
		fatal("cannot find DNSKEY RRSIGs\n");

	memset(revoked, 0, sizeof(revoked));
	memset(standby, 0, sizeof(revoked));
	memset(ksk_algorithms, 0, sizeof(ksk_algorithms));
	memset(zsk_algorithms, 0, sizeof(zsk_algorithms));
	memset(bad_algorithms, 0, sizeof(bad_algorithms));
#ifdef ALLOW_KSKLESS_ZONES
	memset(self_algorithms, 0, sizeof(self_algorithms));
#endif

	/*
	 * Check that the DNSKEY RR has at least one self signing KSK and
	 * one ZSK per algorithm in it.
	 */
	for (result = dns_rdataset_first(&rdataset);
	     result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(&rdataset)) {
		dns_rdataset_current(&rdataset, &rdata);
		result = dns_rdata_tostruct(&rdata, &dnskey, NULL);
		check_result(result, "dns_rdata_tostruct");

		if ((dnskey.flags & DNS_KEYOWNER_ZONE) == 0)
			;
		else if ((dnskey.flags & DNS_KEYFLAG_REVOKE) != 0) {
			if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 &&
			    !dns_dnssec_selfsigns(&rdata, gorigin, &rdataset,
						  &sigrdataset, ISC_FALSE,
						  mctx)) {
				char namebuf[DNS_NAME_FORMATSIZE];
				char buffer[1024];
				isc_buffer_t buf;

				dns_name_format(gorigin, namebuf,
						sizeof(namebuf));
				isc_buffer_init(&buf, buffer, sizeof(buffer));
				result = dns_rdata_totext(&rdata, NULL, &buf);
				check_result(result, "dns_rdata_totext");
				fatal("revoked KSK is not self signed:\n"
				      "%s DNSKEY %.*s", namebuf,
				      (int)isc_buffer_usedlength(&buf), buffer);
			}
			if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 &&
			     revoked[dnskey.algorithm] != 255)
				revoked[dnskey.algorithm]++;
		} else if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0) {
			if (dns_dnssec_selfsigns(&rdata, gorigin, &rdataset,
					      &sigrdataset, ISC_FALSE, mctx)) {
				if (ksk_algorithms[dnskey.algorithm] != 255)
					ksk_algorithms[dnskey.algorithm]++;
				goodksk = ISC_TRUE;
			} else {
				if (standby[dnskey.algorithm] != 255)
					standby[dnskey.algorithm]++;
			}
		} else if (dns_dnssec_selfsigns(&rdata, gorigin, &rdataset,
						&sigrdataset, ISC_FALSE,
						mctx)) {
#ifdef ALLOW_KSKLESS_ZONES
			if (self_algorithms[dnskey.algorithm] != 255)
				self_algorithms[dnskey.algorithm]++;
#endif
			if (zsk_algorithms[dnskey.algorithm] != 255)
				zsk_algorithms[dnskey.algorithm]++;
			goodzsk = ISC_TRUE;
		} else {
			if (zsk_algorithms[dnskey.algorithm] != 255)
				zsk_algorithms[dnskey.algorithm]++;
#ifdef ALLOW_KSKLESS_ZONES
			allzsksigned = ISC_FALSE;
#endif
		}
		dns_rdata_freestruct(&dnskey);
		dns_rdata_reset(&rdata);
	}
	dns_rdataset_disassociate(&sigrdataset);

	if (!goodksk) {
#ifdef ALLOW_KSKLESS_ZONES
		if (!goodzsk)
			fatal("no self signing keys found");
		fprintf(stderr, "No self signing KSK found. Using self signed "
			"ZSK's for active algorithm list.\n");
		memcpy(ksk_algorithms, self_algorithms, sizeof(ksk_algorithms));
		if (!allzsksigned)
			fprintf(stderr, "warning: not all ZSK's are self "
				"signed.\n");
#else
		fatal("no self signed KSK's found");
#endif
	}

	fprintf(stderr, "Verifying the zone using the following algorithms:");
	for (i = 0; i < 256; i++) {
		if (ksk_algorithms[i]) {
			alg_format(i, algbuf, sizeof(algbuf));
			fprintf(stderr, " %s", algbuf);
		}
	}
	fprintf(stderr, ".\n");

	if (memcmp(ksk_algorithms, zsk_algorithms, sizeof(ksk_algorithms))) {
		for (i = 0; i < 256; i++) {
			if (ksk_algorithms[i] == zsk_algorithms[i])
				continue;
			alg_format(i, algbuf, sizeof(algbuf));
			fprintf(stderr, "Missing %s for algorithm %s\n",
				ksk_algorithms[i] ? "ZSK" : "self signing KSK",
				algbuf);
			bad_algorithms[i] = 1;
		}
	}

	/*
	 * Check that all the other records were signed by keys that are
	 * present in the DNSKEY RRSET.
	 */

	dns_fixedname_init(&fname);
	name = dns_fixedname_name(&fname);
	dns_fixedname_init(&fnextname);
	nextname = dns_fixedname_name(&fnextname);
	dns_fixedname_init(&fzonecut);
	zonecut = NULL;

	result = dns_db_createiterator(gdb, DNS_DB_NONSEC3, &dbiter);
	check_result(result, "dns_db_createiterator()");

	result = dns_dbiterator_first(dbiter);
	check_result(result, "dns_dbiterator_first()");

	while (!done) {
		isc_boolean_t isdelegation = ISC_FALSE;

		dns_dbiterator_current(dbiter, &node, name);
		if (delegation(name, node, NULL)) {
			zonecut = dns_fixedname_name(&fzonecut);
			dns_name_copy(name, zonecut, NULL);
			isdelegation = ISC_TRUE;
		}
		verifynode(name, node, isdelegation, &rdataset,
			   ksk_algorithms, bad_algorithms);
		result = dns_dbiterator_next(dbiter);
		nextnode = NULL;
		while (result == ISC_R_SUCCESS) {
			result = dns_dbiterator_current(dbiter, &nextnode,
							nextname);
			if (result != ISC_R_SUCCESS)
				break;
			if (!dns_name_issubdomain(nextname, gorigin) ||
			    (zonecut != NULL &&
			     dns_name_issubdomain(nextname, zonecut)))
			{
				dns_db_detachnode(gdb, &nextnode);
				result = dns_dbiterator_next(dbiter);
				continue;
			}
			dns_db_detachnode(gdb, &nextnode);
			break;
		}
		if (result == ISC_R_NOMORE) {
			done = ISC_TRUE;
		} else if (result != ISC_R_SUCCESS)
			fatal("iterating through the database failed: %s",
			      isc_result_totext(result));
		dns_db_detachnode(gdb, &node);
	}

	dns_dbiterator_destroy(&dbiter);

	result = dns_db_createiterator(gdb, DNS_DB_NSEC3ONLY, &dbiter);
	check_result(result, "dns_db_createiterator()");

	for (result = dns_dbiterator_first(dbiter);
	     result == ISC_R_SUCCESS;
	     result = dns_dbiterator_next(dbiter) ) {
		dns_dbiterator_current(dbiter, &node, name);
		verifynode(name, node, ISC_FALSE, &rdataset,
			   ksk_algorithms, bad_algorithms);
		dns_db_detachnode(gdb, &node);
	}
	dns_dbiterator_destroy(&dbiter);

	dns_rdataset_disassociate(&rdataset);

	/*
	 * If we made it this far, we have what we consider a properly signed
	 * zone.  Set the good flag.
	 */
	for (i = 0; i < 256; i++) {
		if (bad_algorithms[i]) {
			if (first)
				fprintf(stderr, "The zone is not fully signed "
					"for the following algorithms:");
			alg_format(i, algbuf, sizeof(algbuf));
			fprintf(stderr, " %s", algbuf);
			first = ISC_FALSE;
		}
	}
	if (!first) {
		fprintf(stderr, ".\n");
		fatal("DNSSEC completeness test failed.");
	}

	if (goodksk) {
		/*
		 * Print the success summary.
		 */
		fprintf(stderr, "Zone signing complete:\n");
		for (i = 0; i < 256; i++) {
			if (zsk_algorithms[i] || ksk_algorithms[i] ||
			    revoked[i] || standby[i]) {
				alg_format(i, algbuf, sizeof(algbuf));
				fprintf(stderr, "Algorithm: %s: ZSKs: %u, "
					"KSKs: %u active, %u revoked, %u "
					"stand-by\n", algbuf,
					zsk_algorithms[i], ksk_algorithms[i],
					revoked[i], standby[i]);
			}
		}
	}
}

/*%
 * Sign the apex of the zone.
 * Note the origin may not be the first node if there are out of zone
 * records.
 */
static void
signapex(void) {
	dns_dbnode_t *node = NULL;
	dns_fixedname_t fixed;
	dns_name_t *name;
	isc_result_t result;
<commit_msg>fix bad test to determine if both ksk's and zsk's are present. [RT #19802]<commit_after>/*
 * Portions Copyright (C) 2004-2009  Internet Systems Consortium, Inc. ("ISC")
 * Portions Copyright (C) 1999-2003  Internet Software Consortium.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE
 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Portions Copyright (C) 1995-2000 by Network Associates, Inc.
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND ISC AND NETWORK ASSOCIATES DISCLAIMS
 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE
 * FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
 * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

/* $Id: dnssec-signzone.c,v 1.219 2009/06/09 22:54:21 marka Exp $ */

/*! \file */

#include <config.h>

#include <stdlib.h>
#include <time.h>

#include <isc/app.h>
#include <isc/base32.h>
#include <isc/commandline.h>
#include <isc/entropy.h>
#include <isc/event.h>
#include <isc/file.h>
#include <isc/hash.h>
#include <isc/hex.h>
#include <isc/mem.h>
#include <isc/mutex.h>
#include <isc/os.h>
#include <isc/print.h>
#include <isc/random.h>
#include <isc/rwlock.h>
#include <isc/serial.h>
#include <isc/stdio.h>
#include <isc/stdlib.h>
#include <isc/string.h>
#include <isc/task.h>
#include <isc/time.h>
#include <isc/util.h>

#include <dns/db.h>
#include <dns/dbiterator.h>
#include <dns/diff.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/masterdump.h>
#include <dns/nsec.h>
#include <dns/nsec3.h>
#include <dns/rdata.h>
#include <dns/rdatalist.h>
#include <dns/rdataset.h>
#include <dns/rdataclass.h>
#include <dns/rdatasetiter.h>
#include <dns/rdatastruct.h>
#include <dns/rdatatype.h>
#include <dns/result.h>
#include <dns/soa.h>
#include <dns/time.h>

#include <dst/dst.h>

#include "dnssectool.h"

const char *program = "dnssec-signzone";
int verbose;

typedef struct hashlist hashlist_t;

static int nsec_datatype = dns_rdatatype_nsec;

#define IS_NSEC3	(nsec_datatype == dns_rdatatype_nsec3)
#define OPTOUT(x)	(((x) & DNS_NSEC3FLAG_OPTOUT) != 0)

#define BUFSIZE 2048
#define MAXDSKEYS 8

typedef struct signer_key_struct signer_key_t;

struct signer_key_struct {
	dst_key_t *key;
	isc_boolean_t issigningkey;
	isc_boolean_t isdsk;
	isc_boolean_t isksk;
	isc_boolean_t wasused;
	isc_boolean_t commandline;
	unsigned int position;
	ISC_LINK(signer_key_t) link;
};

#define SIGNER_EVENTCLASS	ISC_EVENTCLASS(0x4453)
#define SIGNER_EVENT_WRITE	(SIGNER_EVENTCLASS + 0)
#define SIGNER_EVENT_WORK	(SIGNER_EVENTCLASS + 1)

#define SOA_SERIAL_KEEP		0
#define SOA_SERIAL_INCREMENT	1
#define SOA_SERIAL_UNIXTIME	2

typedef struct signer_event sevent_t;
struct signer_event {
	ISC_EVENT_COMMON(sevent_t);
	dns_fixedname_t *fname;
	dns_dbnode_t *node;
};

static ISC_LIST(signer_key_t) keylist;
static unsigned int keycount = 0;
isc_rwlock_t keylist_lock;
static isc_stdtime_t starttime = 0, endtime = 0, now;
static int cycle = -1;
static int jitter = 0;
static isc_boolean_t tryverify = ISC_FALSE;
static isc_boolean_t printstats = ISC_FALSE;
static isc_mem_t *mctx = NULL;
static isc_entropy_t *ectx = NULL;
static dns_ttl_t zonettl;
static FILE *fp;
static char *tempfile = NULL;
static const dns_master_style_t *masterstyle;
static dns_masterformat_t inputformat = dns_masterformat_text;
static dns_masterformat_t outputformat = dns_masterformat_text;
static unsigned int nsigned = 0, nretained = 0, ndropped = 0;
static unsigned int nverified = 0, nverifyfailed = 0;
static const char *directory;
static isc_mutex_t namelock, statslock;
static isc_taskmgr_t *taskmgr = NULL;
static dns_db_t *gdb;			/* The database */
static dns_dbversion_t *gversion;	/* The database version */
static dns_dbiterator_t *gdbiter;	/* The database iterator */
static dns_rdataclass_t gclass;		/* The class */
static dns_name_t *gorigin;		/* The database origin */
static int nsec3flags = 0;
static isc_task_t *master = NULL;
static unsigned int ntasks = 0;
static isc_boolean_t shuttingdown = ISC_FALSE, finished = ISC_FALSE;
static isc_boolean_t nokeys = ISC_FALSE;
static isc_boolean_t removefile = ISC_FALSE;
static isc_boolean_t generateds = ISC_FALSE;
static isc_boolean_t ignoreksk = ISC_FALSE;
static dns_name_t *dlv = NULL;
static dns_fixedname_t dlv_fixed;
static dns_master_style_t *dsstyle = NULL;
static unsigned int serialformat = SOA_SERIAL_KEEP;
static unsigned int hash_length = 0;
static isc_boolean_t unknownalg = ISC_FALSE;
static isc_boolean_t disable_zone_check = ISC_FALSE;

#define INCSTAT(counter)		\
	if (printstats) {		\
		LOCK(&statslock);	\
		counter++;		\
		UNLOCK(&statslock);	\
	}

static void
sign(isc_task_t *task, isc_event_t *event);

static isc_boolean_t
nsec3only(dns_dbnode_t *node);

static void
dumpnode(dns_name_t *name, dns_dbnode_t *node) {
	isc_result_t result;

	if (outputformat != dns_masterformat_text)
		return;
	result = dns_master_dumpnodetostream(mctx, gdb, gversion, node, name,
					     masterstyle, fp);
	check_result(result, "dns_master_dumpnodetostream");
}

static signer_key_t *
newkeystruct(dst_key_t *dstkey, isc_boolean_t signwithkey) {
	signer_key_t *key;

	key = isc_mem_get(mctx, sizeof(signer_key_t));
	if (key == NULL)
		fatal("out of memory");
	key->key = dstkey;
	if ((dst_key_flags(dstkey) & DNS_KEYFLAG_KSK) != 0) {
		key->issigningkey = signwithkey;
		key->isksk = ISC_TRUE;
		key->isdsk = ISC_FALSE;
	} else {
		key->issigningkey = signwithkey;
		key->isksk = ISC_FALSE;
		key->isdsk = ISC_TRUE;
	}
	key->wasused = ISC_FALSE;
	key->commandline = ISC_FALSE;
	key->position = keycount++;
	ISC_LINK_INIT(key, link);
	return (key);
}

static void
signwithkey(dns_name_t *name, dns_rdataset_t *rdataset, dns_rdata_t *rdata,
	    dst_key_t *key, isc_buffer_t *b)
{
	isc_result_t result;
	isc_stdtime_t jendtime;

	jendtime = (jitter != 0) ? isc_random_jitter(endtime, jitter) : endtime;
	result = dns_dnssec_sign(name, rdataset, key, &starttime, &jendtime,
				 mctx, b, rdata);
	isc_entropy_stopcallbacksources(ectx);
	if (result != ISC_R_SUCCESS) {
		char keystr[KEY_FORMATSIZE];
		key_format(key, keystr, sizeof(keystr));
		fatal("dnskey '%s' failed to sign data: %s",
		      keystr, isc_result_totext(result));
	}
	INCSTAT(nsigned);

	if (tryverify) {
		result = dns_dnssec_verify(name, rdataset, key,
					   ISC_TRUE, mctx, rdata);
		if (result == ISC_R_SUCCESS) {
			vbprintf(3, "\tsignature verified\n");
			INCSTAT(nverified);
		} else {
			vbprintf(3, "\tsignature failed to verify\n");
			INCSTAT(nverifyfailed);
		}
	}
}

static inline isc_boolean_t
issigningkey(signer_key_t *key) {
	return (key->issigningkey);
}

static inline isc_boolean_t
iszonekey(signer_key_t *key) {
	return (ISC_TF(dns_name_equal(dst_key_name(key->key), gorigin) &&
		       dst_key_iszonekey(key->key)));
}

/*%
 * Find the key if it is in our list.  If it is, return it, otherwise null.
 * No locking is performed here, this must be done by the caller.
 */
static signer_key_t *
keythatsigned_unlocked(dns_rdata_rrsig_t *rrsig) {
	signer_key_t *key;

	key = ISC_LIST_HEAD(keylist);
	while (key != NULL) {
		if (rrsig->keyid == dst_key_id(key->key) &&
		    rrsig->algorithm == dst_key_alg(key->key) &&
		    dns_name_equal(&rrsig->signer, dst_key_name(key->key)))
			return (key);
		key = ISC_LIST_NEXT(key, link);
	}
	return (NULL);
}

/*%
 * Finds the key that generated a RRSIG, if possible.  First look at the keys
 * that we've loaded already, and then see if there's a key on disk.
 */
static signer_key_t *
keythatsigned(dns_rdata_rrsig_t *rrsig) {
	isc_result_t result;
	dst_key_t *pubkey = NULL, *privkey = NULL;
	signer_key_t *key;

	isc_rwlock_lock(&keylist_lock, isc_rwlocktype_read);
	key = keythatsigned_unlocked(rrsig);
	isc_rwlock_unlock(&keylist_lock, isc_rwlocktype_read);
	if (key != NULL)
		return (key);

	/*
	 * We did not find the key in our list.  Get a write lock now, since
	 * we may be modifying the bits.  We could do the tryupgrade() dance,
	 * but instead just get a write lock and check once again to see if
	 * it is on our list.  It's possible someone else may have added it
	 * after all.
	 */
	isc_rwlock_lock(&keylist_lock, isc_rwlocktype_write);

	key = keythatsigned_unlocked(rrsig);
	if (key != NULL) {
		isc_rwlock_unlock(&keylist_lock, isc_rwlocktype_write);
		return (key);
	}

	result = dst_key_fromfile(&rrsig->signer, rrsig->keyid,
				  rrsig->algorithm, DST_TYPE_PUBLIC,
				  NULL, mctx, &pubkey);
	if (result != ISC_R_SUCCESS) {
		isc_rwlock_unlock(&keylist_lock, isc_rwlocktype_write);
		return (NULL);
	}

	result = dst_key_fromfile(&rrsig->signer, rrsig->keyid,
				  rrsig->algorithm,
				  DST_TYPE_PUBLIC | DST_TYPE_PRIVATE,
				  NULL, mctx, &privkey);
	if (result == ISC_R_SUCCESS) {
		dst_key_free(&pubkey);
		key = newkeystruct(privkey, ISC_FALSE);
	} else
		key = newkeystruct(pubkey, ISC_FALSE);
	ISC_LIST_APPEND(keylist, key, link);

	isc_rwlock_unlock(&keylist_lock, isc_rwlocktype_write);
	return (key);
}

/*%
 * Check to see if we expect to find a key at this name.  If we see a RRSIG
 * and can't find the signing key that we expect to find, we drop the rrsig.
 * I'm not sure if this is completely correct, but it seems to work.
 */
static isc_boolean_t
expecttofindkey(dns_name_t *name) {
	unsigned int options = DNS_DBFIND_NOWILD;
	dns_fixedname_t fname;
	isc_result_t result;
	char namestr[DNS_NAME_FORMATSIZE];

	dns_fixedname_init(&fname);
	result = dns_db_find(gdb, name, gversion, dns_rdatatype_dnskey, options,
			     0, NULL, dns_fixedname_name(&fname), NULL, NULL);
	switch (result) {
	case ISC_R_SUCCESS:
	case DNS_R_NXDOMAIN:
	case DNS_R_NXRRSET:
		return (ISC_TRUE);
	case DNS_R_DELEGATION:
	case DNS_R_CNAME:
	case DNS_R_DNAME:
		return (ISC_FALSE);
	}
	dns_name_format(name, namestr, sizeof(namestr));
	fatal("failure looking for '%s DNSKEY' in database: %s",
	      namestr, isc_result_totext(result));
	return (ISC_FALSE); /* removes a warning */
}

static inline isc_boolean_t
setverifies(dns_name_t *name, dns_rdataset_t *set, signer_key_t *key,
	    dns_rdata_t *rrsig)
{
	isc_result_t result;
	result = dns_dnssec_verify(name, set, key->key, ISC_FALSE, mctx, rrsig);
	if (result == ISC_R_SUCCESS) {
		INCSTAT(nverified);
		return (ISC_TRUE);
	} else {
		INCSTAT(nverifyfailed);
		return (ISC_FALSE);
	}
}

/*%
 * Signs a set.  Goes through contortions to decide if each RRSIG should
 * be dropped or retained, and then determines if any new SIGs need to
 * be generated.
 */
static void
signset(dns_diff_t *del, dns_diff_t *add, dns_dbnode_t *node, dns_name_t *name,
	dns_rdataset_t *set)
{
	dns_rdataset_t sigset;
	dns_rdata_t sigrdata = DNS_RDATA_INIT;
	dns_rdata_rrsig_t rrsig;
	signer_key_t *key;
	isc_result_t result;
	isc_boolean_t nosigs = ISC_FALSE;
	isc_boolean_t *wassignedby, *nowsignedby;
	int arraysize;
	dns_difftuple_t *tuple;
	dns_ttl_t ttl;
	int i;
	char namestr[DNS_NAME_FORMATSIZE];
	char typestr[TYPE_FORMATSIZE];
	char sigstr[SIG_FORMATSIZE];

	dns_name_format(name, namestr, sizeof(namestr));
	type_format(set->type, typestr, sizeof(typestr));

	ttl = ISC_MIN(set->ttl, endtime - starttime);

	dns_rdataset_init(&sigset);
	result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_rrsig,
				     set->type, 0, &sigset, NULL);
	if (result == ISC_R_NOTFOUND) {
		result = ISC_R_SUCCESS;
		nosigs = ISC_TRUE;
	}
	if (result != ISC_R_SUCCESS)
		fatal("failed while looking for '%s RRSIG %s': %s",
		      namestr, typestr, isc_result_totext(result));

	vbprintf(1, "%s/%s:\n", namestr, typestr);

	arraysize = keycount;
	if (!nosigs)
		arraysize += dns_rdataset_count(&sigset);
	wassignedby = isc_mem_get(mctx, arraysize * sizeof(isc_boolean_t));
	nowsignedby = isc_mem_get(mctx, arraysize * sizeof(isc_boolean_t));
	if (wassignedby == NULL || nowsignedby == NULL)
		fatal("out of memory");

	for (i = 0; i < arraysize; i++)
		wassignedby[i] = nowsignedby[i] = ISC_FALSE;

	if (nosigs)
		result = ISC_R_NOMORE;
	else
		result = dns_rdataset_first(&sigset);

	while (result == ISC_R_SUCCESS) {
		isc_boolean_t expired, future;
		isc_boolean_t keep = ISC_FALSE, resign = ISC_FALSE;

		dns_rdataset_current(&sigset, &sigrdata);

		result = dns_rdata_tostruct(&sigrdata, &rrsig, NULL);
		check_result(result, "dns_rdata_tostruct");

		future = isc_serial_lt(now, rrsig.timesigned);

		key = keythatsigned(&rrsig);
		sig_format(&rrsig, sigstr, sizeof(sigstr));
		if (key != NULL && issigningkey(key))
			expired = isc_serial_gt(now + cycle, rrsig.timeexpire);
		else
			expired = isc_serial_gt(now, rrsig.timeexpire);

		if (isc_serial_gt(rrsig.timesigned, rrsig.timeexpire)) {
			/* rrsig is dropped and not replaced */
			vbprintf(2, "\trrsig by %s dropped - "
				 "invalid validity period\n",
				 sigstr);
		} else if (key == NULL && !future &&
			 expecttofindkey(&rrsig.signer))
		{
			/* rrsig is dropped and not replaced */
			vbprintf(2, "\trrsig by %s dropped - "
				 "private dnskey not found\n",
				 sigstr);
		} else if (key == NULL || future) {
			vbprintf(2, "\trrsig by %s %s - dnskey not found\n",
				 expired ? "retained" : "dropped", sigstr);
			if (!expired)
				keep = ISC_TRUE;
		} else if (issigningkey(key)) {
			if (!expired && setverifies(name, set, key, &sigrdata))
			{
				vbprintf(2, "\trrsig by %s retained\n", sigstr);
				keep = ISC_TRUE;
				wassignedby[key->position] = ISC_TRUE;
				nowsignedby[key->position] = ISC_TRUE;
				key->wasused = ISC_TRUE;
			} else {
				vbprintf(2, "\trrsig by %s dropped - %s\n",
					 sigstr,
					 expired ? "expired" :
						   "failed to verify");
				wassignedby[key->position] = ISC_TRUE;
				resign = ISC_TRUE;
			}
		} else if (iszonekey(key)) {
			if (!expired && setverifies(name, set, key, &sigrdata))
			{
				vbprintf(2, "\trrsig by %s retained\n", sigstr);
				keep = ISC_TRUE;
				wassignedby[key->position] = ISC_TRUE;
				nowsignedby[key->position] = ISC_TRUE;
				key->wasused = ISC_TRUE;
			} else {
				vbprintf(2, "\trrsig by %s dropped - %s\n",
					 sigstr,
					 expired ? "expired" :
						   "failed to verify");
				wassignedby[key->position] = ISC_TRUE;
			}
		} else if (!expired) {
			vbprintf(2, "\trrsig by %s retained\n", sigstr);
			keep = ISC_TRUE;
		} else {
			vbprintf(2, "\trrsig by %s expired\n", sigstr);
		}

		if (keep) {
			nowsignedby[key->position] = ISC_TRUE;
			INCSTAT(nretained);
			if (sigset.ttl != ttl) {
				vbprintf(2, "\tfixing ttl %s\n", sigstr);
				tuple = NULL;
				result = dns_difftuple_create(mctx,
							      DNS_DIFFOP_DEL,
							      name, sigset.ttl,
							      &sigrdata,
							      &tuple);
				check_result(result, "dns_difftuple_create");
				dns_diff_append(del, &tuple);
				result = dns_difftuple_create(mctx,
							      DNS_DIFFOP_ADD,
							      name, ttl,
							      &sigrdata,
							      &tuple);
				check_result(result, "dns_difftuple_create");
				dns_diff_append(add, &tuple);
			}
		} else {
			tuple = NULL;
			result = dns_difftuple_create(mctx, DNS_DIFFOP_DEL,
						      name, sigset.ttl,
						      &sigrdata, &tuple);
			check_result(result, "dns_difftuple_create");
			dns_diff_append(del, &tuple);
			INCSTAT(ndropped);
		}

		if (resign) {
			isc_buffer_t b;
			dns_rdata_t trdata = DNS_RDATA_INIT;
			unsigned char array[BUFSIZE];
			char keystr[KEY_FORMATSIZE];

			INSIST(!keep);

			key_format(key->key, keystr, sizeof(keystr));
			vbprintf(1, "\tresigning with dnskey %s\n", keystr);
			isc_buffer_init(&b, array, sizeof(array));
			signwithkey(name, set, &trdata, key->key, &b);
			nowsignedby[key->position] = ISC_TRUE;
			key->wasused = ISC_TRUE;
			tuple = NULL;
			result = dns_difftuple_create(mctx, DNS_DIFFOP_ADD,
						      name, ttl, &trdata,
						      &tuple);
			check_result(result, "dns_difftuple_create");
			dns_diff_append(add, &tuple);
		}

		dns_rdata_reset(&sigrdata);
		dns_rdata_freestruct(&rrsig);
		result = dns_rdataset_next(&sigset);
	}
	if (result == ISC_R_NOMORE)
		result = ISC_R_SUCCESS;

	check_result(result, "dns_rdataset_first/next");
	if (dns_rdataset_isassociated(&sigset))
		dns_rdataset_disassociate(&sigset);

	for (key = ISC_LIST_HEAD(keylist);
	     key != NULL;
	     key = ISC_LIST_NEXT(key, link))
	{
		isc_buffer_t b;
		dns_rdata_t trdata;
		unsigned char array[BUFSIZE];
		char keystr[KEY_FORMATSIZE];

		if (nowsignedby[key->position])
			continue;

		if (!key->issigningkey)
			continue;
		if (!(ignoreksk || key->isdsk ||
		      (key->isksk &&
		       set->type == dns_rdatatype_dnskey &&
		       dns_name_equal(name, gorigin))))
			continue;

		key_format(key->key, keystr, sizeof(keystr));
		vbprintf(1, "\tsigning with dnskey %s\n", keystr);
		dns_rdata_init(&trdata);
		isc_buffer_init(&b, array, sizeof(array));
		signwithkey(name, set, &trdata, key->key, &b);
		key->wasused = ISC_TRUE;
		tuple = NULL;
		result = dns_difftuple_create(mctx, DNS_DIFFOP_ADD, name,
					      ttl, &trdata, &tuple);
		check_result(result, "dns_difftuple_create");
		dns_diff_append(add, &tuple);
	}

	isc_mem_put(mctx, wassignedby, arraysize * sizeof(isc_boolean_t));
	isc_mem_put(mctx, nowsignedby, arraysize * sizeof(isc_boolean_t));
}

struct hashlist {
	unsigned char *hashbuf;
	size_t entries;
	size_t size;
	size_t length;
};

static void
hashlist_init(hashlist_t *l, unsigned int nodes, unsigned int length) {

	l->entries = 0;
	l->length = length + 1;

	if (nodes != 0) {
		l->size = nodes;
		l->hashbuf = malloc(l->size * l->length);
		if (l->hashbuf == NULL)
			l->size = 0;
	} else {
		l->size = 0;
		l->hashbuf = NULL;
	}
}

static void
hashlist_add(hashlist_t *l, const unsigned char *hash, size_t len)
{

	REQUIRE(len <= l->length);

	if (l->entries == l->size) {
		l->size = l->size * 2 + 100;
		l->hashbuf = realloc(l->hashbuf, l->size * l->length);
	}
	memset(l->hashbuf + l->entries * l->length, 0, l->length);
	memcpy(l->hashbuf + l->entries * l->length, hash, len);
	l->entries++;
}

static void
hashlist_add_dns_name(hashlist_t *l, /*const*/ dns_name_t *name,
		      unsigned int hashalg, unsigned int iterations,
		      const unsigned char *salt, size_t salt_length,
		      isc_boolean_t speculative)
{
	char nametext[DNS_NAME_FORMATSIZE];
	unsigned char hash[NSEC3_MAX_HASH_LENGTH + 1];
	unsigned int len;
	size_t i;

	len = isc_iterated_hash(hash, hashalg, iterations, salt, salt_length,
				name->ndata, name->length);
	if (verbose) {
		dns_name_format(name, nametext, sizeof nametext);
		for (i = 0 ; i < len; i++)
			fprintf(stderr, "%02x", hash[i]);
		fprintf(stderr, " %s\n", nametext);
	}
	hash[len++] = speculative ? 1 : 0;
	hashlist_add(l, hash, len);
}

static int
hashlist_comp(const void *a, const void *b) {
	return (memcmp(a, b, hash_length + 1));
}

static void
hashlist_sort(hashlist_t *l) {
	qsort(l->hashbuf, l->entries, l->length, hashlist_comp);
}

static isc_boolean_t
hashlist_hasdup(hashlist_t *l) {
	unsigned char *current;
	unsigned char *next = l->hashbuf;
	size_t entries = l->entries;

	/*
	 * Skip initial speculative wild card hashs.
	 */
	while (entries > 0U && next[l->length-1] != 0U) {
		next += l->length;
		entries--;
	}

	current = next;
	while (entries-- > 1U) {
		next += l->length;
		if (next[l->length-1] != 0)
			continue;
		if (memcmp(current, next, l->length - 1) == 0)
			return (ISC_TRUE);
		current = next;
	}
	return (ISC_FALSE);
}

static const unsigned char *
hashlist_findnext(const hashlist_t *l,
		  const unsigned char hash[NSEC3_MAX_HASH_LENGTH])
{
	unsigned int entries = l->entries;
	const unsigned char *next = bsearch(hash, l->hashbuf, l->entries,
					    l->length, hashlist_comp);
	INSIST(next != NULL);

	do {
		if (next < l->hashbuf + (l->entries - 1) * l->length)
			next += l->length;
		else
			next = l->hashbuf;
		if (next[l->length - 1] == 0)
			break;
	} while (entries-- > 1);
	INSIST(entries != 0);
	return (next);
}

static isc_boolean_t
hashlist_exists(const hashlist_t *l,
		const unsigned char hash[NSEC3_MAX_HASH_LENGTH])
{
	if (bsearch(hash, l->hashbuf, l->entries, l->length, hashlist_comp))
		return (ISC_TRUE);
	else
		return (ISC_FALSE);
}

static void
addnowildcardhash(hashlist_t *l, /*const*/ dns_name_t *name,
		  unsigned int hashalg, unsigned int iterations,
		  const unsigned char *salt, size_t salt_length)
{
	dns_fixedname_t fixed;
	dns_name_t *wild;
	dns_dbnode_t *node = NULL;
	isc_result_t result;
	char namestr[DNS_NAME_FORMATSIZE];

	dns_fixedname_init(&fixed);
	wild = dns_fixedname_name(&fixed);

	result = dns_name_concatenate(dns_wildcardname, name, wild, NULL);
	if (result == ISC_R_NOSPACE)
		return;
	check_result(result,"addnowildcardhash: dns_name_concatenate()");

	result = dns_db_findnode(gdb, wild, ISC_FALSE, &node);
	if (result == ISC_R_SUCCESS) {
		dns_db_detachnode(gdb, &node);
		return;
	}

	if (verbose) {
		dns_name_format(wild, namestr, sizeof(namestr));
		fprintf(stderr, "adding no-wildcardhash for %s\n", namestr);
	}

	hashlist_add_dns_name(l, wild, hashalg, iterations, salt, salt_length,
			      ISC_TRUE);
}

static void
opendb(const char *prefix, dns_name_t *name, dns_rdataclass_t rdclass,
       dns_db_t **dbp)
{
	char filename[256];
	isc_buffer_t b;
	isc_result_t result;

	isc_buffer_init(&b, filename, sizeof(filename));
	if (directory != NULL) {
		isc_buffer_putstr(&b, directory);
		if (directory[strlen(directory) - 1] != '/')
			isc_buffer_putstr(&b, "/");
	}
	isc_buffer_putstr(&b, prefix);
	result = dns_name_tofilenametext(name, ISC_FALSE, &b);
	check_result(result, "dns_name_tofilenametext()");
	if (isc_buffer_availablelength(&b) == 0) {
		char namestr[DNS_NAME_FORMATSIZE];
		dns_name_format(name, namestr, sizeof(namestr));
		fatal("name '%s' is too long", namestr);
	}
	isc_buffer_putuint8(&b, 0);

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

	result = dns_db_load(*dbp, filename);
	if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE)
		dns_db_detach(dbp);
}

/*%
 * Loads the key set for a child zone, if there is one, and builds DS records.
 */
static isc_result_t
loadds(dns_name_t *name, isc_uint32_t ttl, dns_rdataset_t *dsset) {
	dns_db_t *db = NULL;
	dns_dbversion_t *ver = NULL;
	dns_dbnode_t *node = NULL;
	isc_result_t result;
	dns_rdataset_t keyset;
	dns_rdata_t key, ds;
	unsigned char dsbuf[DNS_DS_BUFFERSIZE];
	dns_diff_t diff;
	dns_difftuple_t *tuple = NULL;

	opendb("keyset-", name, gclass, &db);
	if (db == NULL)
		return (ISC_R_NOTFOUND);

	result = dns_db_findnode(db, name, ISC_FALSE, &node);
	if (result != ISC_R_SUCCESS) {
		dns_db_detach(&db);
		return (DNS_R_BADDB);
	}
	dns_rdataset_init(&keyset);
	result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_dnskey, 0, 0,
				     &keyset, NULL);
	if (result != ISC_R_SUCCESS) {
		dns_db_detachnode(db, &node);
		dns_db_detach(&db);
		return (result);
	}

	vbprintf(2, "found DNSKEY records\n");

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

	dns_diff_init(mctx, &diff);

	for (result = dns_rdataset_first(&keyset);
	     result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(&keyset))
	{
		dns_rdata_init(&key);
		dns_rdata_init(&ds);
		dns_rdataset_current(&keyset, &key);
		result = dns_ds_buildrdata(name, &key, DNS_DSDIGEST_SHA1,
					   dsbuf, &ds);
		check_result(result, "dns_ds_buildrdata");

		result = dns_difftuple_create(mctx, DNS_DIFFOP_ADD, name,
					      ttl, &ds, &tuple);
		check_result(result, "dns_difftuple_create");
		dns_diff_append(&diff, &tuple);

		dns_rdata_reset(&ds);
		result = dns_ds_buildrdata(name, &key, DNS_DSDIGEST_SHA256,
					   dsbuf, &ds);
		check_result(result, "dns_ds_buildrdata");

		result = dns_difftuple_create(mctx, DNS_DIFFOP_ADD, name,
					      ttl, &ds, &tuple);
		check_result(result, "dns_difftuple_create");
		dns_diff_append(&diff, &tuple);
	}
	result = dns_diff_apply(&diff, db, ver);
	check_result(result, "dns_diff_apply");
	dns_diff_clear(&diff);

	dns_db_closeversion(db, &ver, ISC_TRUE);

	result = dns_db_findrdataset(db, node, NULL, dns_rdatatype_ds, 0, 0,
				     dsset, NULL);
	check_result(result, "dns_db_findrdataset");

	dns_rdataset_disassociate(&keyset);
	dns_db_detachnode(db, &node);
	dns_db_detach(&db);
	return (result);
}

static isc_boolean_t
delegation(dns_name_t *name, dns_dbnode_t *node, isc_uint32_t *ttlp) {
	dns_rdataset_t nsset;
	isc_result_t result;

	if (dns_name_equal(name, gorigin))
		return (ISC_FALSE);

	dns_rdataset_init(&nsset);
	result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_ns,
				     0, 0, &nsset, NULL);
	if (dns_rdataset_isassociated(&nsset)) {
		if (ttlp != NULL)
			*ttlp = nsset.ttl;
		dns_rdataset_disassociate(&nsset);
	}

	return (ISC_TF(result == ISC_R_SUCCESS));
}

static isc_boolean_t
secure(dns_name_t *name, dns_dbnode_t *node) {
	dns_rdataset_t dsset;
	isc_result_t result;

	if (dns_name_equal(name, gorigin))
		return (ISC_FALSE);

	dns_rdataset_init(&dsset);
	result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_ds,
				     0, 0, &dsset, NULL);
	if (dns_rdataset_isassociated(&dsset))
		dns_rdataset_disassociate(&dsset);

	return (ISC_TF(result == ISC_R_SUCCESS));
}

/*%
 * Signs all records at a name.
 */
static void
signname(dns_dbnode_t *node, dns_name_t *name) {
	isc_result_t result;
	dns_rdataset_t rdataset;
	dns_rdatasetiter_t *rdsiter;
	isc_boolean_t isdelegation = ISC_FALSE;
	dns_diff_t del, add;
	char namestr[DNS_NAME_FORMATSIZE];

	dns_rdataset_init(&rdataset);
	dns_name_format(name, namestr, sizeof(namestr));

	/*
	 * Determine if this is a delegation point.
	 */
	if (delegation(name, node, NULL))
		isdelegation = ISC_TRUE;

	/*
	 * Now iterate through the rdatasets.
	 */
	dns_diff_init(mctx, &del);
	dns_diff_init(mctx, &add);
	rdsiter = NULL;
	result = dns_db_allrdatasets(gdb, node, gversion, 0, &rdsiter);
	check_result(result, "dns_db_allrdatasets()");
	result = dns_rdatasetiter_first(rdsiter);
	while (result == ISC_R_SUCCESS) {
		dns_rdatasetiter_current(rdsiter, &rdataset);

		/* If this is a RRSIG set, skip it. */
		if (rdataset.type == dns_rdatatype_rrsig)
			goto skip;

		/*
		 * If this name is a delegation point, skip all records
		 * except NSEC and DS sets.  Otherwise check that there
		 * isn't a DS record.
		 */
		if (isdelegation) {
			if (rdataset.type != nsec_datatype &&
			    rdataset.type != dns_rdatatype_ds)
				goto skip;
		} else if (rdataset.type == dns_rdatatype_ds) {
			char namebuf[DNS_NAME_FORMATSIZE];
			dns_name_format(name, namebuf, sizeof(namebuf));
			fatal("'%s': found DS RRset without NS RRset\n",
			      namebuf);
		}

		signset(&del, &add, node, name, &rdataset);

 skip:
		dns_rdataset_disassociate(&rdataset);
		result = dns_rdatasetiter_next(rdsiter);
	}
	if (result != ISC_R_NOMORE)
		fatal("rdataset iteration for name '%s' failed: %s",
		      namestr, isc_result_totext(result));

	dns_rdatasetiter_destroy(&rdsiter);

	result = dns_diff_applysilently(&del, gdb, gversion);
	if (result != ISC_R_SUCCESS)
		fatal("failed to delete SIGs at node '%s': %s",
		      namestr, isc_result_totext(result));

	result = dns_diff_applysilently(&add, gdb, gversion);
	if (result != ISC_R_SUCCESS)
		fatal("failed to add SIGs at node '%s': %s",
		      namestr, isc_result_totext(result));

	dns_diff_clear(&del);
	dns_diff_clear(&add);
}

static inline isc_boolean_t
active_node(dns_dbnode_t *node) {
	dns_rdatasetiter_t *rdsiter = NULL;
	dns_rdatasetiter_t *rdsiter2 = NULL;
	isc_boolean_t active = ISC_FALSE;
	isc_result_t result;
	dns_rdataset_t rdataset;
	dns_rdatatype_t type;
	dns_rdatatype_t covers;
	isc_boolean_t found;

	dns_rdataset_init(&rdataset);
	result = dns_db_allrdatasets(gdb, node, gversion, 0, &rdsiter);
	check_result(result, "dns_db_allrdatasets()");
	result = dns_rdatasetiter_first(rdsiter);
	while (result == ISC_R_SUCCESS) {
		dns_rdatasetiter_current(rdsiter, &rdataset);
		if (rdataset.type != dns_rdatatype_nsec &&
		    rdataset.type != dns_rdatatype_nsec3 &&
		    rdataset.type != dns_rdatatype_rrsig)
			active = ISC_TRUE;
		dns_rdataset_disassociate(&rdataset);
		if (!active)
			result = dns_rdatasetiter_next(rdsiter);
		else
			result = ISC_R_NOMORE;
	}
	if (result != ISC_R_NOMORE)
		fatal("rdataset iteration failed: %s",
		      isc_result_totext(result));

	if (!active && nsec_datatype == dns_rdatatype_nsec) {
		/*%
		 * The node is empty of everything but NSEC / RRSIG records.
		 */
		for (result = dns_rdatasetiter_first(rdsiter);
		     result == ISC_R_SUCCESS;
		     result = dns_rdatasetiter_next(rdsiter)) {
			dns_rdatasetiter_current(rdsiter, &rdataset);
			result = dns_db_deleterdataset(gdb, node, gversion,
						       rdataset.type,
						       rdataset.covers);
			check_result(result, "dns_db_deleterdataset()");
			dns_rdataset_disassociate(&rdataset);
		}
		if (result != ISC_R_NOMORE)
			fatal("rdataset iteration failed: %s",
			      isc_result_totext(result));
	} else {
		/*
		 * Delete RRSIGs for types that no longer exist.
		 */
		result = dns_db_allrdatasets(gdb, node, gversion, 0, &rdsiter2);
		check_result(result, "dns_db_allrdatasets()");
		for (result = dns_rdatasetiter_first(rdsiter);
		     result == ISC_R_SUCCESS;
		     result = dns_rdatasetiter_next(rdsiter)) {
			dns_rdatasetiter_current(rdsiter, &rdataset);
			type = rdataset.type;
			covers = rdataset.covers;
			dns_rdataset_disassociate(&rdataset);
			if (type != dns_rdatatype_rrsig)
				continue;
			found = ISC_FALSE;
			for (result = dns_rdatasetiter_first(rdsiter2);
			     !found && result == ISC_R_SUCCESS;
			     result = dns_rdatasetiter_next(rdsiter2)) {
				dns_rdatasetiter_current(rdsiter2, &rdataset);
				if (rdataset.type == covers)
					found = ISC_TRUE;
				dns_rdataset_disassociate(&rdataset);
			}
			if (!found) {
				if (result != ISC_R_NOMORE)
					fatal("rdataset iteration failed: %s",
					      isc_result_totext(result));
				result = dns_db_deleterdataset(gdb, node,
							       gversion, type,
							       covers);
				check_result(result,
					     "dns_db_deleterdataset(rrsig)");
			} else if (result != ISC_R_NOMORE &&
				   result != ISC_R_SUCCESS)
				fatal("rdataset iteration failed: %s",
				      isc_result_totext(result));
		}
		if (result != ISC_R_NOMORE)
			fatal("rdataset iteration failed: %s",
			      isc_result_totext(result));
		dns_rdatasetiter_destroy(&rdsiter2);

#if 0
		/*
		 * Delete all NSEC records and RRSIG(NSEC) if we are in
		 * NSEC3 mode and vica versa.
		 */
		for (result = dns_rdatasetiter_first(rdsiter2);
		     result == ISC_R_SUCCESS;
		     result = dns_rdatasetiter_next(rdsiter2)) {
			dns_rdatasetiter_current(rdsiter, &rdataset);
			type = rdataset.type;
			covers = rdataset.covers;
			if (type == dns_rdatatype_rrsig)
				type = covers;
			dns_rdataset_disassociate(&rdataset);
			if (type == nsec_datatype ||
			    (type != dns_rdatatype_nsec &&
			     type != dns_rdatatype_nsec3))
				continue;
			if (covers != 0)
				type = dns_rdatatype_rrsig;
			result = dns_db_deleterdataset(gdb, node, gversion,
						       type, covers);
			check_result(result, "dns_db_deleterdataset()");
		}
#endif
	}
	dns_rdatasetiter_destroy(&rdsiter);

	return (active);
}

/*%
 * Extracts the TTL from the SOA.
 */
static dns_ttl_t
soattl(void) {
	dns_rdataset_t soaset;
	dns_fixedname_t fname;
	dns_name_t *name;
	isc_result_t result;
	dns_ttl_t ttl;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	dns_rdata_soa_t soa;

	dns_fixedname_init(&fname);
	name = dns_fixedname_name(&fname);
	dns_rdataset_init(&soaset);
	result = dns_db_find(gdb, gorigin, gversion, dns_rdatatype_soa,
			     0, 0, NULL, name, &soaset, NULL);
	if (result != ISC_R_SUCCESS)
		fatal("failed to find an SOA at the zone apex: %s",
		      isc_result_totext(result));

	result = dns_rdataset_first(&soaset);
	check_result(result, "dns_rdataset_first");
	dns_rdataset_current(&soaset, &rdata);
	result = dns_rdata_tostruct(&rdata, &soa, NULL);
	check_result(result, "dns_rdata_tostruct");
	ttl = soa.minimum;
	dns_rdataset_disassociate(&soaset);
	return (ttl);
}

/*%
 * Increment (or set if nonzero) the SOA serial
 */
static isc_result_t
setsoaserial(isc_uint32_t serial) {
	isc_result_t result;
	dns_dbnode_t *node = NULL;
	dns_rdataset_t rdataset;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	isc_uint32_t old_serial, new_serial;

	result = dns_db_getoriginnode(gdb, &node);
	if (result != ISC_R_SUCCESS)
		return result;

	dns_rdataset_init(&rdataset);

	result = dns_db_findrdataset(gdb, node, gversion,
				     dns_rdatatype_soa, 0,
				     0, &rdataset, NULL);
	if (result != ISC_R_SUCCESS)
		goto cleanup;

	result = dns_rdataset_first(&rdataset);
	RUNTIME_CHECK(result == ISC_R_SUCCESS);

	dns_rdataset_current(&rdataset, &rdata);

	old_serial = dns_soa_getserial(&rdata);

	if (serial) {
		/* Set SOA serial to the value provided. */
		new_serial = serial;
	} else {
		/* Increment SOA serial using RFC 1982 arithmetics */
		new_serial = (old_serial + 1) & 0xFFFFFFFF;
		if (new_serial == 0)
			new_serial = 1;
	}

	/* If the new serial is not likely to cause a zone transfer
	 * (a/ixfr) from servers having the old serial, warn the user.
	 *
	 * RFC1982 section 7 defines the maximum increment to be
	 * (2^(32-1))-1.  Using u_int32_t arithmetic, we can do a single
	 * comparison.  (5 - 6 == (2^32)-1, not negative-one)
	 */
	if (new_serial == old_serial ||
	    (new_serial - old_serial) > 0x7fffffffU)
		fprintf(stderr, "%s: warning: Serial number not advanced, "
			"zone may not transfer\n", program);

	dns_soa_setserial(new_serial, &rdata);

	result = dns_db_deleterdataset(gdb, node, gversion,
				       dns_rdatatype_soa, 0);
	check_result(result, "dns_db_deleterdataset");
	if (result != ISC_R_SUCCESS)
		goto cleanup;

	result = dns_db_addrdataset(gdb, node, gversion,
				    0, &rdataset, 0, NULL);
	check_result(result, "dns_db_addrdataset");
	if (result != ISC_R_SUCCESS)
		goto cleanup;

cleanup:
	dns_rdataset_disassociate(&rdataset);
	if (node != NULL)
		dns_db_detachnode(gdb, &node);
	dns_rdata_reset(&rdata);

	return (result);
}

/*%
 * Delete any RRSIG records at a node.
 */
static void
cleannode(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node) {
	dns_rdatasetiter_t *rdsiter = NULL;
	dns_rdataset_t set;
	isc_result_t result, dresult;

	if (outputformat != dns_masterformat_text || !disable_zone_check)
		return;

	dns_rdataset_init(&set);
	result = dns_db_allrdatasets(db, node, version, 0, &rdsiter);
	check_result(result, "dns_db_allrdatasets");
	result = dns_rdatasetiter_first(rdsiter);
	while (result == ISC_R_SUCCESS) {
		isc_boolean_t destroy = ISC_FALSE;
		dns_rdatatype_t covers = 0;
		dns_rdatasetiter_current(rdsiter, &set);
		if (set.type == dns_rdatatype_rrsig) {
			covers = set.covers;
			destroy = ISC_TRUE;
		}
		dns_rdataset_disassociate(&set);
		result = dns_rdatasetiter_next(rdsiter);
		if (destroy) {
			dresult = dns_db_deleterdataset(db, node, version,
							dns_rdatatype_rrsig,
							covers);
			check_result(dresult, "dns_db_deleterdataset");
		}
	}
	if (result != ISC_R_NOMORE)
		fatal("rdataset iteration failed: %s",
		      isc_result_totext(result));
	dns_rdatasetiter_destroy(&rdsiter);
}

/*%
 * Set up the iterator and global state before starting the tasks.
 */
static void
presign(void) {
	isc_result_t result;

	gdbiter = NULL;
	result = dns_db_createiterator(gdb, 0, &gdbiter);
	check_result(result, "dns_db_createiterator()");
}

/*%
 * Clean up the iterator and global state after the tasks complete.
 */
static void
postsign(void) {
	dns_dbiterator_destroy(&gdbiter);
}

static isc_boolean_t
goodsig(dns_rdata_t *sigrdata, dns_name_t *name, dns_rdataset_t *keyrdataset,
	dns_rdataset_t *rdataset)
{
	dns_rdata_dnskey_t key;
	dns_rdata_rrsig_t sig;
	dst_key_t *dstkey = NULL;
	isc_result_t result;

	dns_rdata_tostruct(sigrdata, &sig, NULL);

	for (result = dns_rdataset_first(keyrdataset);
	     result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(keyrdataset)) {
		dns_rdata_t rdata = DNS_RDATA_INIT;
		dns_rdataset_current(keyrdataset, &rdata);
		dns_rdata_tostruct(&rdata, &key, NULL);
		result = dns_dnssec_keyfromrdata(gorigin, &rdata, mctx,
						 &dstkey);
		if (result != ISC_R_SUCCESS)
			return (ISC_FALSE);
		if (sig.algorithm != key.algorithm ||
		    sig.keyid != dst_key_id(dstkey) ||
		    !dns_name_equal(&sig.signer, gorigin)) {
			dst_key_free(&dstkey);
			continue;
		}
		result = dns_dnssec_verify(name, rdataset, dstkey, ISC_FALSE,
					   mctx, sigrdata);
		dst_key_free(&dstkey);
		if (result == ISC_R_SUCCESS)
			return(ISC_TRUE);
	}
	return (ISC_FALSE);
}

static void
verifyset(dns_rdataset_t *rdataset, dns_name_t *name, dns_dbnode_t *node,
	  dns_rdataset_t *keyrdataset, unsigned char *ksk_algorithms,
	   char *bad_algorithms)
{
	unsigned char set_algorithms[256];
	char namebuf[DNS_NAME_FORMATSIZE];
	char algbuf[80];
	char typebuf[80];
	dns_rdataset_t sigrdataset;
	dns_rdatasetiter_t *rdsiter = NULL;
	isc_result_t result;
	int i;

	dns_rdataset_init(&sigrdataset);
	result = dns_db_allrdatasets(gdb, node, gversion, 0, &rdsiter);
	check_result(result, "dns_db_allrdatasets()");
	for (result = dns_rdatasetiter_first(rdsiter);
	     result == ISC_R_SUCCESS;
	     result = dns_rdatasetiter_next(rdsiter)) {
		dns_rdatasetiter_current(rdsiter, &sigrdataset);
		if (sigrdataset.type == dns_rdatatype_rrsig &&
		    sigrdataset.covers == rdataset->type)
			break;
		dns_rdataset_disassociate(&sigrdataset);
	}
	if (result != ISC_R_SUCCESS) {
		dns_name_format(name, namebuf, sizeof(namebuf));
		type_format(rdataset->type, typebuf, sizeof(typebuf));
		fprintf(stderr, "no signatures for %s/%s\n", namebuf, typebuf);
		for (i = 0; i < 256; i++)
			if (ksk_algorithms[i] != 0)
				bad_algorithms[i] = 1;
		return;
	}

	memset(set_algorithms, 0, sizeof(set_algorithms));
	for (result = dns_rdataset_first(&sigrdataset);
	     result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(&sigrdataset)) {
		dns_rdata_t rdata = DNS_RDATA_INIT;
		dns_rdata_rrsig_t sig;

		dns_rdataset_current(&sigrdataset, &rdata);
		dns_rdata_tostruct(&rdata, &sig, NULL);
		if ((set_algorithms[sig.algorithm] != 0) ||
		    (ksk_algorithms[sig.algorithm] == 0))
			continue;
		if (goodsig(&rdata, name, keyrdataset, rdataset))
			set_algorithms[sig.algorithm] = 1;
	}
	dns_rdatasetiter_destroy(&rdsiter);
	if (memcmp(set_algorithms, ksk_algorithms, sizeof(set_algorithms))) {
		dns_name_format(name, namebuf, sizeof(namebuf));
		type_format(rdataset->type, typebuf, sizeof(typebuf));
		for (i = 0; i < 256; i++)
			if ((ksk_algorithms[i] != 0) &&
			    (set_algorithms[i] == 0)) {
				alg_format(i, algbuf, sizeof(algbuf));
				fprintf(stderr, "Missing %s signature for "
					"%s %s\n", algbuf, namebuf, typebuf);
				bad_algorithms[i] = 1;
			}
	}
	dns_rdataset_disassociate(&sigrdataset);
}

static void
verifynode(dns_name_t *name, dns_dbnode_t *node, isc_boolean_t delegation,
	   dns_rdataset_t *keyrdataset, unsigned char *ksk_algorithms,
	   unsigned char *bad_algorithms)
{
	dns_rdataset_t rdataset;
	dns_rdatasetiter_t *rdsiter = NULL;
	isc_result_t result;

	result = dns_db_allrdatasets(gdb, node, gversion, 0, &rdsiter);
	check_result(result, "dns_db_allrdatasets()");
	result = dns_rdatasetiter_first(rdsiter);
	dns_rdataset_init(&rdataset);
	while (result == ISC_R_SUCCESS) {
		dns_rdatasetiter_current(rdsiter, &rdataset);
		if (rdataset.type != dns_rdatatype_rrsig &&
		    rdataset.type != dns_rdatatype_dnskey &&
		    (!delegation || rdataset.type == dns_rdatatype_ds ||
		     rdataset.type == dns_rdatatype_nsec)) {
			verifyset(&rdataset, name, node, keyrdataset,
				  ksk_algorithms, bad_algorithms);
		}
		dns_rdataset_disassociate(&rdataset);
		result = dns_rdatasetiter_next(rdsiter);
	}
	if (result != ISC_R_NOMORE)
		fatal("rdataset iteration failed: %s",
		      isc_result_totext(result));
	dns_rdatasetiter_destroy(&rdsiter);
}

/*%
 * Verify that certain things are sane:
 *
 *   The apex has a DNSKEY record with at least one KSK and at least
 *   one ZSK.
 *
 *   The DNSKEY record was signed with at least one of the KSKs in this
 *   set.
 *
 *   The rest of the zone was signed with at least one of the ZSKs
 *   present in the DNSKEY RRSET.
 */
static void
verifyzone(void) {
	char algbuf[80];
	dns_dbiterator_t *dbiter = NULL;
	dns_dbnode_t *node = NULL, *nextnode = NULL;
	dns_fixedname_t fname, fnextname, fzonecut;
	dns_name_t *name, *nextname, *zonecut;
	dns_rdata_dnskey_t dnskey;
	dns_rdata_t rdata = DNS_RDATA_INIT;
	dns_rdataset_t rdataset;
	dns_rdataset_t sigrdataset;
	int i;
	isc_boolean_t done = ISC_FALSE;
	isc_boolean_t first = ISC_TRUE;
	isc_boolean_t goodksk = ISC_FALSE;
	isc_boolean_t goodzsk = ISC_FALSE;
	isc_result_t result;
	unsigned char revoked[256];
	unsigned char standby[256];
	unsigned char ksk_algorithms[256];
	unsigned char zsk_algorithms[256];
	unsigned char bad_algorithms[256];
#ifdef ALLOW_KSKLESS_ZONES
	isc_boolean_t allzsksigned = ISC_TRUE;
	unsigned char self_algorithms[256];
#endif

	if (disable_zone_check)
		return;

	result = dns_db_findnode(gdb, gorigin, ISC_FALSE, &node);
	if (result != ISC_R_SUCCESS)
		fatal("failed to find the zone's origin: %s",
		      isc_result_totext(result));

	dns_rdataset_init(&rdataset);
	dns_rdataset_init(&sigrdataset);
	result = dns_db_findrdataset(gdb, node, gversion,
				     dns_rdatatype_dnskey,
				     0, 0, &rdataset, &sigrdataset);
	dns_db_detachnode(gdb, &node);
	if (result != ISC_R_SUCCESS)
		fatal("cannot find DNSKEY rrset\n");

	if (!dns_rdataset_isassociated(&sigrdataset))
		fatal("cannot find DNSKEY RRSIGs\n");

	memset(revoked, 0, sizeof(revoked));
	memset(standby, 0, sizeof(revoked));
	memset(ksk_algorithms, 0, sizeof(ksk_algorithms));
	memset(zsk_algorithms, 0, sizeof(zsk_algorithms));
	memset(bad_algorithms, 0, sizeof(bad_algorithms));
#ifdef ALLOW_KSKLESS_ZONES
	memset(self_algorithms, 0, sizeof(self_algorithms));
#endif

	/*
	 * Check that the DNSKEY RR has at least one self signing KSK and
	 * one ZSK per algorithm in it.
	 */
	for (result = dns_rdataset_first(&rdataset);
	     result == ISC_R_SUCCESS;
	     result = dns_rdataset_next(&rdataset)) {
		dns_rdataset_current(&rdataset, &rdata);
		result = dns_rdata_tostruct(&rdata, &dnskey, NULL);
		check_result(result, "dns_rdata_tostruct");

		if ((dnskey.flags & DNS_KEYOWNER_ZONE) == 0)
			;
		else if ((dnskey.flags & DNS_KEYFLAG_REVOKE) != 0) {
			if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 &&
			    !dns_dnssec_selfsigns(&rdata, gorigin, &rdataset,
						  &sigrdataset, ISC_FALSE,
						  mctx)) {
				char namebuf[DNS_NAME_FORMATSIZE];
				char buffer[1024];
				isc_buffer_t buf;

				dns_name_format(gorigin, namebuf,
						sizeof(namebuf));
				isc_buffer_init(&buf, buffer, sizeof(buffer));
				result = dns_rdata_totext(&rdata, NULL, &buf);
				check_result(result, "dns_rdata_totext");
				fatal("revoked KSK is not self signed:\n"
				      "%s DNSKEY %.*s", namebuf,
				      (int)isc_buffer_usedlength(&buf), buffer);
			}
			if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0 &&
			     revoked[dnskey.algorithm] != 255)
				revoked[dnskey.algorithm]++;
		} else if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0) {
			if (dns_dnssec_selfsigns(&rdata, gorigin, &rdataset,
					      &sigrdataset, ISC_FALSE, mctx)) {
				if (ksk_algorithms[dnskey.algorithm] != 255)
					ksk_algorithms[dnskey.algorithm]++;
				goodksk = ISC_TRUE;
			} else {
				if (standby[dnskey.algorithm] != 255)
					standby[dnskey.algorithm]++;
			}
		} else if (dns_dnssec_selfsigns(&rdata, gorigin, &rdataset,
						&sigrdataset, ISC_FALSE,
						mctx)) {
#ifdef ALLOW_KSKLESS_ZONES
			if (self_algorithms[dnskey.algorithm] != 255)
				self_algorithms[dnskey.algorithm]++;
#endif
			if (zsk_algorithms[dnskey.algorithm] != 255)
				zsk_algorithms[dnskey.algorithm]++;
			goodzsk = ISC_TRUE;
		} else {
			if (zsk_algorithms[dnskey.algorithm] != 255)
				zsk_algorithms[dnskey.algorithm]++;
#ifdef ALLOW_KSKLESS_ZONES
			allzsksigned = ISC_FALSE;
#endif
		}
		dns_rdata_freestruct(&dnskey);
		dns_rdata_reset(&rdata);
	}
	dns_rdataset_disassociate(&sigrdataset);

	if (!goodksk) {
#ifdef ALLOW_KSKLESS_ZONES
		if (!goodzsk)
			fatal("no self signing keys found");
		fprintf(stderr, "No self signing KSK found. Using self signed "
			"ZSK's for active algorithm list.\n");
		memcpy(ksk_algorithms, self_algorithms, sizeof(ksk_algorithms));
		if (!allzsksigned)
			fprintf(stderr, "warning: not all ZSK's are self "
				"signed.\n");
#else
		fatal("no self signed KSK's found");
#endif
	}

	fprintf(stderr, "Verifying the zone using the following algorithms:");
	for (i = 0; i < 256; i++) {
		if (ksk_algorithms[i] != 0) {
			alg_format(i, algbuf, sizeof(algbuf));
			fprintf(stderr, " %s", algbuf);
		}
	}
	fprintf(stderr, ".\n");

	for (i = 0; i < 256; i++) {
		/*
		 * The counts should both be zero or both be non-zero.
		 * Mark the algorithm as bad if this is not met.
		 */
		if ((ksk_algorithms[i] != 0) == (zsk_algorithms[i] != 0))
			continue;
		alg_format(i, algbuf, sizeof(algbuf));
		fprintf(stderr, "Missing %s for algorithm %s\n",
			(ksk_algorithms[i] != 0) ? "ZSK" : "self signing KSK",
			algbuf);
		bad_algorithms[i] = 1;
	}

	/*
	 * Check that all the other records were signed by keys that are
	 * present in the DNSKEY RRSET.
	 */

	dns_fixedname_init(&fname);
	name = dns_fixedname_name(&fname);
	dns_fixedname_init(&fnextname);
	nextname = dns_fixedname_name(&fnextname);
	dns_fixedname_init(&fzonecut);
	zonecut = NULL;

	result = dns_db_createiterator(gdb, DNS_DB_NONSEC3, &dbiter);
	check_result(result, "dns_db_createiterator()");

	result = dns_dbiterator_first(dbiter);
	check_result(result, "dns_dbiterator_first()");

	while (!done) {
		isc_boolean_t isdelegation = ISC_FALSE;

		dns_dbiterator_current(dbiter, &node, name);
		if (delegation(name, node, NULL)) {
			zonecut = dns_fixedname_name(&fzonecut);
			dns_name_copy(name, zonecut, NULL);
			isdelegation = ISC_TRUE;
		}
		verifynode(name, node, isdelegation, &rdataset,
			   ksk_algorithms, bad_algorithms);
		result = dns_dbiterator_next(dbiter);
		nextnode = NULL;
		while (result == ISC_R_SUCCESS) {
			result = dns_dbiterator_current(dbiter, &nextnode,
							nextname);
			if (result != ISC_R_SUCCESS)
				break;
			if (!dns_name_issubdomain(nextname, gorigin) ||
			    (zonecut != NULL &&
			     dns_name_issubdomain(nextname, zonecut)))
			{
				dns_db_detachnode(gdb, &nextnode);
				result = dns_dbiterator_next(dbiter);
				continue;
			}
			dns_db_detachnode(gdb, &nextnode);
			break;
		}
		if (result == ISC_R_NOMORE) {
			done = ISC_TRUE;
		} else if (result != ISC_R_SUCCESS)
			fatal("iterating through the database failed: %s",
			      isc_result_totext(result));
		dns_db_detachnode(gdb, &node);
	}

	dns_dbiterator_destroy(&dbiter);

	result = dns_db_createiterator(gdb, DNS_DB_NSEC3ONLY, &dbiter);
	check_result(result, "dns_db_createiterator()");

	for (result = dns_dbiterator_first(dbiter);
	     result == ISC_R_SUCCESS;
	     result = dns_dbiterator_next(dbiter) ) {
		dns_dbiterator_current(dbiter, &node, name);
		verifynode(name, node, ISC_FALSE, &rdataset,
			   ksk_algorithms, bad_algorithms);
		dns_db_detachnode(gdb, &node);
	}
	dns_dbiterator_destroy(&dbiter);

	dns_rdataset_disassociate(&rdataset);

	/*
	 * If we made it this far, we have what we consider a properly signed
	 * zone.  Set the good flag.
	 */
	for (i = 0; i < 256; i++) {
		if (bad_algorithms[i] != 0) {
			if (first)
				fprintf(stderr, "The zone is not fully signed "
					"for the following algorithms:");
			alg_format(i, algbuf, sizeof(algbuf));
			fprintf(stderr, " %s", algbuf);
			first = ISC_FALSE;
		}
	}
	if (!first) {
		fprintf(stderr, ".\n");
		fatal("DNSSEC completeness test failed.");
	}

	if (goodksk) {
		/*
		 * Print the success summary.
		 */
		fprintf(stderr, "Zone signing complete:\n");
		for (i = 0; i < 256; i++) {
			if ((zsk_algorithms[i] != 0) ||
			    (ksk_algorithms[i] != 0) ||
			    (revoked[i] != 0) || (standby[i] != 0)) {
				alg_format(i, algbuf, sizeof(algbuf));
				fprintf(stderr, "Algorithm: %s: ZSKs: %u, "
					"KSKs: %u active, %u revoked, %u "
					"stand-by\n", algbuf,
					zsk_algorithms[i], ksk_algorithms[i],
					revoked[i], standby[i]);
			}
		}
	}
}

/*%
 * Sign the apex of the zone.
 * Note the origin may not be the first node if there are out of zone
 * records.
 */
static void
signapex(void) {
	dns_dbnode_t *node = NULL;
	dns_fixedname_t fixed;
	dns_name_t *name;
	isc_result_t result;

{'fraction_non_alphanumeric': 0.12316159701598435, 'fraction_numerical': 0.006476854620460374, 'mean_word_length': 4.417307269931913, 'pattern_counts': {'":': 0, '<': 117, '<?xml version=': 0, '>': 315, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 4, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6511650', 'n_tokens_mistral': 42624, 'n_tokens_neox': 39254, 'n_words': 8383}