summaryrefslogtreecommitdiffstats
path: root/bin/dnssec/dnssec-signzone.c
diff options
context:
space:
mode:
Diffstat (limited to 'bin/dnssec/dnssec-signzone.c')
-rw-r--r--bin/dnssec/dnssec-signzone.c3031
1 files changed, 3031 insertions, 0 deletions
diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c
new file mode 100644
index 0000000..f446ef3
--- /dev/null
+++ b/bin/dnssec/dnssec-signzone.c
@@ -0,0 +1,3031 @@
+/*
+ * Portions Copyright (C) 2004-2008 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.209 2008/11/14 22:53:46 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/serial.h>
+#include <isc/stdio.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;
+ 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;
+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;
+
+#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->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)));
+}
+
+/*%
+ * 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;
+
+ 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);
+ }
+
+ result = dst_key_fromfile(&rrsig->signer, rrsig->keyid,
+ rrsig->algorithm, DST_TYPE_PUBLIC,
+ NULL, mctx, &pubkey);
+ if (result != ISC_R_SUCCESS)
+ 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);
+ 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;
+ } 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;
+ } 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;
+ 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);
+ 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)
+ 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);
+}
+
+/*%
+ * 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;
+
+ dns_fixedname_init(&fixed);
+ name = dns_fixedname_name(&fixed);
+ result = dns_dbiterator_seek(gdbiter, gorigin);
+ check_result(result, "dns_dbiterator_seek()");
+ result = dns_dbiterator_current(gdbiter, &node, name);
+ check_result(result, "dns_dbiterator_current()");
+ signname(node, name);
+ dumpnode(name, node);
+ cleannode(gdb, gversion, node);
+ dns_db_detachnode(gdb, &node);
+ result = dns_dbiterator_first(gdbiter);
+ if (result == ISC_R_NOMORE)
+ finished = ISC_TRUE;
+ else if (result != ISC_R_SUCCESS)
+ fatal("failure iterating database: %s",
+ isc_result_totext(result));
+}
+
+/*%
+ * Assigns a node to a worker thread. This is protected by the master task's
+ * lock.
+ */
+static void
+assignwork(isc_task_t *task, isc_task_t *worker) {
+ dns_fixedname_t *fname;
+ dns_name_t *name;
+ dns_dbnode_t *node;
+ sevent_t *sevent;
+ dns_rdataset_t nsec;
+ isc_boolean_t found;
+ isc_result_t result;
+ static dns_name_t *zonecut = NULL; /* Protected by namelock. */
+ static dns_fixedname_t fzonecut; /* Protected by namelock. */
+ static unsigned int ended = 0; /* Protected by namelock. */
+
+ if (shuttingdown)
+ return;
+
+ LOCK(&namelock);
+ if (finished) {
+ ended++;
+ if (ended == ntasks) {
+ isc_task_detach(&task);
+ isc_app_shutdown();
+ }
+ goto unlock;
+ }
+
+ fname = isc_mem_get(mctx, sizeof(dns_fixedname_t));
+ if (fname == NULL)
+ fatal("out of memory");
+ dns_fixedname_init(fname);
+ name = dns_fixedname_name(fname);
+ node = NULL;
+ found = ISC_FALSE;
+ while (!found) {
+ result = dns_dbiterator_current(gdbiter, &node, name);
+ if (result != ISC_R_SUCCESS)
+ fatal("failure iterating database: %s",
+ isc_result_totext(result));
+ /*
+ * The origin was handled by signapex().
+ */
+ if (dns_name_equal(name, gorigin)) {
+ dns_db_detachnode(gdb, &node);
+ goto next;
+ }
+ /*
+ * Sort the zone data from the glue and out-of-zone data.
+ * For NSEC zones nodes with zone data have NSEC records.
+ * For NSEC3 zones the NSEC3 nodes are zone data but
+ * outside of the zone name space. For the rest we need
+ * to track the bottom of zone cuts.
+ * Nodes which don't need to be signed are dumped here.
+ */
+ dns_rdataset_init(&nsec);
+ result = dns_db_findrdataset(gdb, node, gversion,
+ nsec_datatype, 0, 0,
+ &nsec, NULL);
+ if (dns_rdataset_isassociated(&nsec))
+ dns_rdataset_disassociate(&nsec);
+ if (result == ISC_R_SUCCESS) {
+ found = ISC_TRUE;
+ } else if (nsec_datatype == dns_rdatatype_nsec3) {
+ if (dns_name_issubdomain(name, gorigin) &&
+ (zonecut == NULL ||
+ !dns_name_issubdomain(name, zonecut))) {
+ if (delegation(name, node, NULL)) {
+ dns_fixedname_init(&fzonecut);
+ zonecut = dns_fixedname_name(&fzonecut);
+ dns_name_copy(name, zonecut, NULL);
+ if (!OPTOUT(nsec3flags) ||
+ secure(name, node))
+ found = ISC_TRUE;
+ } else
+ found = ISC_TRUE;
+ }
+ }
+
+ if (!found) {
+ dumpnode(name, node);
+ dns_db_detachnode(gdb, &node);
+ }
+
+ next:
+ result = dns_dbiterator_next(gdbiter);
+ if (result == ISC_R_NOMORE) {
+ finished = ISC_TRUE;
+ break;
+ } else if (result != ISC_R_SUCCESS)
+ fatal("failure iterating database: %s",
+ isc_result_totext(result));
+ }
+ if (!found) {
+ ended++;
+ if (ended == ntasks) {
+ isc_task_detach(&task);
+ isc_app_shutdown();
+ }
+ isc_mem_put(mctx, fname, sizeof(dns_fixedname_t));
+ goto unlock;
+ }
+ sevent = (sevent_t *)
+ isc_event_allocate(mctx, task, SIGNER_EVENT_WORK,
+ sign, NULL, sizeof(sevent_t));
+ if (sevent == NULL)
+ fatal("failed to allocate event\n");
+
+ sevent->node = node;
+ sevent->fname = fname;
+ isc_task_send(worker, ISC_EVENT_PTR(&sevent));
+ unlock:
+ UNLOCK(&namelock);
+}
+
+/*%
+ * Start a worker task
+ */
+static void
+startworker(isc_task_t *task, isc_event_t *event) {
+ isc_task_t *worker;
+
+ worker = (isc_task_t *)event->ev_arg;
+ assignwork(task, worker);
+ isc_event_free(&event);
+}
+
+/*%
+ * Write a node to the output file, and restart the worker task.
+ */
+static void
+writenode(isc_task_t *task, isc_event_t *event) {
+ isc_task_t *worker;
+ sevent_t *sevent = (sevent_t *)event;
+
+ worker = (isc_task_t *)event->ev_sender;
+ dumpnode(dns_fixedname_name(sevent->fname), sevent->node);
+ cleannode(gdb, gversion, sevent->node);
+ dns_db_detachnode(gdb, &sevent->node);
+ isc_mem_put(mctx, sevent->fname, sizeof(dns_fixedname_t));
+ assignwork(task, worker);
+ isc_event_free(&event);
+}
+
+/*%
+ * Sign a database node.
+ */
+static void
+sign(isc_task_t *task, isc_event_t *event) {
+ dns_fixedname_t *fname;
+ dns_dbnode_t *node;
+ sevent_t *sevent, *wevent;
+
+ sevent = (sevent_t *)event;
+ node = sevent->node;
+ fname = sevent->fname;
+ isc_event_free(&event);
+
+ signname(node, dns_fixedname_name(fname));
+ wevent = (sevent_t *)
+ isc_event_allocate(mctx, task, SIGNER_EVENT_WRITE,
+ writenode, NULL, sizeof(sevent_t));
+ if (wevent == NULL)
+ fatal("failed to allocate event\n");
+ wevent->node = node;
+ wevent->fname = fname;
+ isc_task_send(master, ISC_EVENT_PTR(&wevent));
+}
+
+/*%
+ * Update / remove the DS RRset. Preserve RRSIG(DS) if possible.
+ */
+static void
+add_ds(dns_name_t *name, dns_dbnode_t *node, isc_uint32_t nsttl) {
+ dns_rdataset_t dsset;
+ dns_rdataset_t sigdsset;
+ isc_result_t result;
+
+ dns_rdataset_init(&dsset);
+ dns_rdataset_init(&sigdsset);
+ result = dns_db_findrdataset(gdb, node, gversion,
+ dns_rdatatype_ds,
+ 0, 0, &dsset, &sigdsset);
+ if (result == ISC_R_SUCCESS) {
+ dns_rdataset_disassociate(&dsset);
+ result = dns_db_deleterdataset(gdb, node, gversion,
+ dns_rdatatype_ds, 0);
+ check_result(result, "dns_db_deleterdataset");
+ }
+ result = loadds(name, nsttl, &dsset);
+ if (result == ISC_R_SUCCESS) {
+ result = dns_db_addrdataset(gdb, node, gversion, 0,
+ &dsset, 0, NULL);
+ check_result(result, "dns_db_addrdataset");
+ dns_rdataset_disassociate(&dsset);
+ if (dns_rdataset_isassociated(&sigdsset))
+ dns_rdataset_disassociate(&sigdsset);
+ } else if (dns_rdataset_isassociated(&sigdsset)) {
+ result = dns_db_deleterdataset(gdb, node, gversion,
+ dns_rdatatype_rrsig,
+ dns_rdatatype_ds);
+ check_result(result, "dns_db_deleterdataset");
+ dns_rdataset_disassociate(&sigdsset);
+ }
+}
+
+/*%
+ * Generate NSEC records for the zone.
+ */
+static void
+nsecify(void) {
+ dns_dbiterator_t *dbiter = NULL;
+ dns_dbnode_t *node = NULL, *nextnode = NULL;
+ dns_fixedname_t fname, fnextname, fzonecut;
+ dns_name_t *name, *nextname, *zonecut;
+ isc_boolean_t done = ISC_FALSE;
+ isc_result_t result;
+ isc_uint32_t nsttl = 0;
+
+ 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) {
+ dns_dbiterator_current(dbiter, &node, name);
+ if (delegation(name, node, &nsttl)) {
+ zonecut = dns_fixedname_name(&fzonecut);
+ dns_name_copy(name, zonecut, NULL);
+ if (generateds)
+ add_ds(name, node, nsttl);
+ }
+ result = dns_dbiterator_next(dbiter);
+ nextnode = NULL;
+ while (result == ISC_R_SUCCESS) {
+ isc_boolean_t active = ISC_FALSE;
+ result = dns_dbiterator_current(dbiter, &nextnode,
+ nextname);
+ if (result != ISC_R_SUCCESS)
+ break;
+ active = active_node(nextnode);
+ if (!active) {
+ dns_db_detachnode(gdb, &nextnode);
+ result = dns_dbiterator_next(dbiter);
+ continue;
+ }
+ 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) {
+ dns_name_clone(gorigin, nextname);
+ done = ISC_TRUE;
+ } else if (result != ISC_R_SUCCESS)
+ fatal("iterating through the database failed: %s",
+ isc_result_totext(result));
+ result = dns_nsec_build(gdb, gversion, node, nextname,
+ zonettl);
+ check_result(result, "dns_nsec_build()");
+ dns_db_detachnode(gdb, &node);
+ }
+
+ dns_dbiterator_destroy(&dbiter);
+}
+
+/*%
+ * Does this node only contain NSEC3 records or RRSIG records or is empty.
+ */
+static isc_boolean_t
+nsec3only(dns_dbnode_t *node) {
+ dns_rdatasetiter_t *rdsiter = NULL;
+ isc_result_t result;
+ dns_rdataset_t rdataset;
+ isc_boolean_t answer = ISC_TRUE;
+
+ 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_nsec3 &&
+ rdataset.type != dns_rdatatype_rrsig) {
+ answer = ISC_FALSE;
+ result = ISC_R_NOMORE;
+ } else
+ result = dns_rdatasetiter_next(rdsiter);
+ dns_rdataset_disassociate(&rdataset);
+ }
+ if (result != ISC_R_NOMORE)
+ fatal("rdataset iteration failed: %s",
+ isc_result_totext(result));
+ dns_rdatasetiter_destroy(&rdsiter);
+ return (answer);
+}
+
+static void
+addnsec3param(const unsigned char *salt, size_t salt_length,
+ unsigned int iterations)
+{
+ dns_dbnode_t *node = NULL;
+ dns_rdata_nsec3param_t nsec3param;
+ unsigned char nsec3parambuf[5 + 255];
+ dns_rdatalist_t rdatalist;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_buffer_t b;
+ isc_result_t result;
+
+ dns_rdataset_init(&rdataset);
+
+ nsec3param.common.rdclass = gclass;
+ nsec3param.common.rdtype = dns_rdatatype_nsec3param;
+ ISC_LINK_INIT(&nsec3param.common, link);
+ nsec3param.mctx = NULL;
+ nsec3param.flags = 0;
+ nsec3param.hash = unknownalg ? DNS_NSEC3_UNKNOWNALG : dns_hash_sha1;
+ nsec3param.iterations = iterations;
+ nsec3param.salt_length = salt_length;
+ DE_CONST(salt, nsec3param.salt);
+
+ isc_buffer_init(&b, nsec3parambuf, sizeof(nsec3parambuf));
+ result = dns_rdata_fromstruct(&rdata, gclass,
+ dns_rdatatype_nsec3param,
+ &nsec3param, &b);
+ rdatalist.rdclass = rdata.rdclass;
+ rdatalist.type = rdata.type;
+ rdatalist.covers = 0;
+ rdatalist.ttl = 0;
+ ISC_LIST_INIT(rdatalist.rdata);
+ ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
+ result = dns_rdatalist_tordataset(&rdatalist, &rdataset);
+ check_result(result, "dns_rdatalist_tordataset()");
+
+ result = dns_db_findnode(gdb, gorigin, ISC_TRUE, &node);
+ check_result(result, "dns_db_find(gorigin)");
+ result = dns_db_addrdataset(gdb, node, gversion, 0, &rdataset,
+ DNS_DBADD_MERGE, NULL);
+ if (result == DNS_R_UNCHANGED)
+ result = ISC_R_SUCCESS;
+ check_result(result, "addnsec3param: dns_db_addrdataset()");
+ dns_db_detachnode(gdb, &node);
+}
+
+static void
+addnsec3(dns_name_t *name, dns_dbnode_t *node,
+ const unsigned char *salt, size_t salt_length,
+ unsigned int iterations, hashlist_t *hashlist,
+ dns_ttl_t ttl)
+{
+ unsigned char hash[NSEC3_MAX_HASH_LENGTH];
+ const unsigned char *nexthash;
+ unsigned char nsec3buffer[DNS_NSEC3_BUFFERSIZE];
+ dns_fixedname_t hashname;
+ dns_rdatalist_t rdatalist;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+ dns_dbnode_t *nsec3node = NULL;
+ char namebuf[DNS_NAME_FORMATSIZE];
+ size_t hash_length;
+
+ dns_name_format(name, namebuf, sizeof(namebuf));
+
+ dns_fixedname_init(&hashname);
+ dns_rdataset_init(&rdataset);
+
+ dns_name_downcase(name, name, NULL);
+ result = dns_nsec3_hashname(&hashname, hash, &hash_length,
+ name, gorigin, dns_hash_sha1, iterations,
+ salt, salt_length);
+ check_result(result, "addnsec3: dns_nsec3_hashname()");
+ nexthash = hashlist_findnext(hashlist, hash);
+ result = dns_nsec3_buildrdata(gdb, gversion, node,
+ unknownalg ?
+ DNS_NSEC3_UNKNOWNALG : dns_hash_sha1,
+ nsec3flags, iterations,
+ salt, salt_length,
+ nexthash, ISC_SHA1_DIGESTLENGTH,
+ nsec3buffer, &rdata);
+ check_result(result, "addnsec3: dns_nsec3_buildrdata()");
+ rdatalist.rdclass = rdata.rdclass;
+ rdatalist.type = rdata.type;
+ rdatalist.covers = 0;
+ rdatalist.ttl = ttl;
+ ISC_LIST_INIT(rdatalist.rdata);
+ ISC_LIST_APPEND(rdatalist.rdata, &rdata, link);
+ result = dns_rdatalist_tordataset(&rdatalist, &rdataset);
+ check_result(result, "dns_rdatalist_tordataset()");
+ result = dns_db_findnsec3node(gdb, dns_fixedname_name(&hashname),
+ ISC_TRUE, &nsec3node);
+ check_result(result, "addnsec3: dns_db_findnode()");
+ result = dns_db_addrdataset(gdb, nsec3node, gversion, 0, &rdataset,
+ 0, NULL);
+ if (result == DNS_R_UNCHANGED)
+ result = ISC_R_SUCCESS;
+ check_result(result, "addnsec3: dns_db_addrdataset()");
+ dns_db_detachnode(gdb, &nsec3node);
+}
+
+/*%
+ * Clean out NSEC3 record and RRSIG(NSEC3) that are not in the hash list.
+ *
+ * Extract the hash from the first label of 'name' then see if it
+ * is in hashlist. If 'name' is not in the hashlist then delete the
+ * any NSEC3 records which have the same parameters as the chain we
+ * are building.
+ *
+ * XXXMPA Should we also check that it of the form <hash>.<origin>?
+ */
+static void
+nsec3clean(dns_name_t *name, dns_dbnode_t *node,
+ unsigned int hashalg, unsigned int iterations,
+ const unsigned char *salt, size_t salt_length, hashlist_t *hashlist)
+{
+ dns_label_t label;
+ dns_rdata_nsec3_t nsec3;
+ dns_rdata_t rdata, delrdata;
+ dns_rdatalist_t rdatalist;
+ dns_rdataset_t rdataset, delrdataset;
+ isc_boolean_t delete_rrsigs = ISC_FALSE;
+ isc_buffer_t target;
+ isc_result_t result;
+ unsigned char hash[NSEC3_MAX_HASH_LENGTH + 1];
+
+ /*
+ * Get the first label.
+ */
+ dns_name_getlabel(name, 0, &label);
+
+ /*
+ * We want just the label contents.
+ */
+ isc_region_consume(&label, 1);
+
+ /*
+ * Decode base32hex string.
+ */
+ isc_buffer_init(&target, hash, sizeof(hash) - 1);
+ result = isc_base32hex_decoderegion(&label, &target);
+ if (result != ISC_R_SUCCESS)
+ return;
+
+ hash[isc_buffer_usedlength(&target)] = 0;
+
+ if (hashlist_exists(hashlist, hash))
+ return;
+
+ /*
+ * Verify that the NSEC3 parameters match the current ones
+ * otherwise we are dealing with a different NSEC3 chain.
+ */
+ dns_rdataset_init(&rdataset);
+ dns_rdataset_init(&delrdataset);
+
+ result = dns_db_findrdataset(gdb, node, gversion, dns_rdatatype_nsec3,
+ 0, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS)
+ return;
+
+ /*
+ * Delete any matching NSEC3 records which have parameters that
+ * match the NSEC3 chain we are building.
+ */
+ for (result = dns_rdataset_first(&rdataset);
+ result == ISC_R_SUCCESS;
+ result = dns_rdataset_next(&rdataset)) {
+ dns_rdata_init(&rdata);
+ dns_rdataset_current(&rdataset, &rdata);
+ dns_rdata_tostruct(&rdata, &nsec3, NULL);
+ if (nsec3.hash == hashalg &&
+ nsec3.iterations == iterations &&
+ nsec3.salt_length == salt_length &&
+ !memcmp(nsec3.salt, salt, salt_length))
+ break;
+ rdatalist.rdclass = rdata.rdclass;
+ rdatalist.type = rdata.type;
+ rdatalist.covers = 0;
+ rdatalist.ttl = rdataset.ttl;
+ ISC_LIST_INIT(rdatalist.rdata);
+ dns_rdata_init(&delrdata);
+ dns_rdata_clone(&rdata, &delrdata);
+ ISC_LIST_APPEND(rdatalist.rdata, &delrdata, link);
+ result = dns_rdatalist_tordataset(&rdatalist, &delrdataset);
+ check_result(result, "dns_rdatalist_tordataset()");
+ result = dns_db_subtractrdataset(gdb, node, gversion,
+ &delrdataset, 0, NULL);
+ dns_rdataset_disassociate(&delrdataset);
+ if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED)
+ check_result(result, "dns_db_subtractrdataset(NSEC3)");
+ delete_rrsigs = ISC_TRUE;
+ }
+ dns_rdataset_disassociate(&rdataset);
+ if (result != ISC_R_NOMORE)
+ check_result(result, "dns_rdataset_first/next");
+
+ if (!delete_rrsigs)
+ return;
+ /*
+ * Delete the NSEC3 RRSIGs
+ */
+ result = dns_db_deleterdataset(gdb, node, gversion,
+ dns_rdatatype_rrsig,
+ dns_rdatatype_nsec3);
+ if (result != ISC_R_SUCCESS && result != DNS_R_UNCHANGED)
+ check_result(result, "dns_db_deleterdataset(RRSIG(NSEC3))");
+}
+
+/*
+ * Generate NSEC3 records for the zone.
+ */
+static void
+nsec3ify(unsigned int hashalg, unsigned int iterations,
+ const unsigned char *salt, size_t salt_length, hashlist_t *hashlist)
+{
+ dns_dbiterator_t *dbiter = NULL;
+ dns_dbnode_t *node = NULL, *nextnode = NULL;
+ dns_fixedname_t fname, fnextname, fzonecut;
+ dns_name_t *name, *nextname, *zonecut;
+ isc_boolean_t done = ISC_FALSE;
+ isc_result_t result;
+ isc_boolean_t active;
+ isc_uint32_t nsttl = 0;
+ unsigned int count, nlabels;
+ int order;
+
+ dns_fixedname_init(&fname);
+ name = dns_fixedname_name(&fname);
+ dns_fixedname_init(&fnextname);
+ nextname = dns_fixedname_name(&fnextname);
+ dns_fixedname_init(&fzonecut);
+ zonecut = NULL;
+
+ /*
+ * Walk the zone generating the hash names.
+ */
+ 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) {
+ dns_dbiterator_current(dbiter, &node, name);
+ 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;
+ active = active_node(nextnode);
+ if (!active) {
+ dns_db_detachnode(gdb, &nextnode);
+ result = dns_dbiterator_next(dbiter);
+ continue;
+ }
+ if (!dns_name_issubdomain(nextname, gorigin) ||
+ (zonecut != NULL &&
+ dns_name_issubdomain(nextname, zonecut))) {
+ dns_db_detachnode(gdb, &nextnode);
+ result = dns_dbiterator_next(dbiter);
+ continue;
+ }
+ if (delegation(nextname, nextnode, &nsttl)) {
+ zonecut = dns_fixedname_name(&fzonecut);
+ dns_name_copy(nextname, zonecut, NULL);
+ if (generateds)
+ add_ds(nextname, nextnode, nsttl);
+ if (OPTOUT(nsec3flags) &&
+ !secure(nextname, nextnode)) {
+ dns_db_detachnode(gdb, &nextnode);
+ result = dns_dbiterator_next(dbiter);
+ continue;
+ }
+ }
+ dns_db_detachnode(gdb, &nextnode);
+ break;
+ }
+ if (result == ISC_R_NOMORE) {
+ dns_name_copy(gorigin, nextname, NULL);
+ done = ISC_TRUE;
+ } else if (result != ISC_R_SUCCESS)
+ fatal("iterating through the database failed: %s",
+ isc_result_totext(result));
+ dns_name_downcase(name, name, NULL);
+ hashlist_add_dns_name(hashlist, name, hashalg, iterations,
+ salt, salt_length, ISC_FALSE);
+ dns_db_detachnode(gdb, &node);
+ /*
+ * Add hashs for empty nodes. Use closest encloser logic.
+ * The closest encloser either has data or is a empty
+ * node for another <name,nextname> span so we don't add
+ * it here. Empty labels on nextname are within the span.
+ */
+ dns_name_downcase(nextname, nextname, NULL);
+ dns_name_fullcompare(name, nextname, &order, &nlabels);
+ addnowildcardhash(hashlist, name, hashalg, iterations,
+ salt, salt_length);
+ count = dns_name_countlabels(nextname);
+ while (count > nlabels + 1) {
+ count--;
+ dns_name_split(nextname, count, NULL, nextname);
+ hashlist_add_dns_name(hashlist, nextname, hashalg,
+ iterations, salt, salt_length,
+ ISC_FALSE);
+ addnowildcardhash(hashlist, nextname, hashalg,
+ iterations, salt, salt_length);
+ }
+ }
+ dns_dbiterator_destroy(&dbiter);
+
+ /*
+ * We have all the hashes now so we can sort them.
+ */
+ hashlist_sort(hashlist);
+
+ /*
+ * Check for duplicate hashes. If found the salt needs to
+ * be changed.
+ */
+ if (hashlist_hasdup(hashlist))
+ fatal("Duplicate hash detected. Pick a different salt.");
+
+ /*
+ * Generate the nsec3 records.
+ */
+ zonecut = NULL;
+ done = ISC_FALSE;
+
+ addnsec3param(salt, salt_length, iterations);
+
+ 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) {
+ dns_dbiterator_current(dbiter, &node, name);
+ 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;
+ /*
+ * Cleanout NSEC3 RRsets which don't exist in the
+ * hash table.
+ */
+ nsec3clean(nextname, nextnode, hashalg, iterations,
+ salt, salt_length, hashlist);
+ /*
+ * Skip NSEC3 only nodes when looking for the next
+ * node in the zone. Also skips now empty nodes.
+ */
+ if (nsec3only(nextnode)) {
+ dns_db_detachnode(gdb, &nextnode);
+ result = dns_dbiterator_next(dbiter);
+ continue;
+ }
+ if (!dns_name_issubdomain(nextname, gorigin) ||
+ (zonecut != NULL &&
+ dns_name_issubdomain(nextname, zonecut))) {
+ dns_db_detachnode(gdb, &nextnode);
+ result = dns_dbiterator_next(dbiter);
+ continue;
+ }
+ if (delegation(nextname, nextnode, NULL)) {
+ zonecut = dns_fixedname_name(&fzonecut);
+ dns_name_copy(nextname, zonecut, NULL);
+ if (OPTOUT(nsec3flags) &&
+ !secure(nextname, nextnode)) {
+ dns_db_detachnode(gdb, &nextnode);
+ result = dns_dbiterator_next(dbiter);
+ continue;
+ }
+ }
+ dns_db_detachnode(gdb, &nextnode);
+ break;
+ }
+ if (result == ISC_R_NOMORE) {
+ dns_name_copy(gorigin, nextname, NULL);
+ done = ISC_TRUE;
+ } else if (result != ISC_R_SUCCESS)
+ fatal("iterating through the database failed: %s",
+ isc_result_totext(result));
+ /*
+ * We need to pause here to release the lock on the database.
+ */
+ dns_dbiterator_pause(dbiter);
+ addnsec3(name, node, salt, salt_length, iterations,
+ hashlist, zonettl);
+ dns_db_detachnode(gdb, &node);
+ /*
+ * Add NSEC3's for empty nodes. Use closest encloser logic.
+ */
+ dns_name_fullcompare(name, nextname, &order, &nlabels);
+ count = dns_name_countlabels(nextname);
+ while (count > nlabels + 1) {
+ count--;
+ dns_name_split(nextname, count, NULL, nextname);
+ addnsec3(nextname, NULL, salt, salt_length,
+ iterations, hashlist, zonettl);
+ }
+ }
+ dns_dbiterator_destroy(&dbiter);
+}
+
+/*%
+ * Load the zone file from disk
+ */
+static void
+loadzone(char *file, char *origin, dns_rdataclass_t rdclass, dns_db_t **db) {
+ isc_buffer_t b;
+ int len;
+ dns_fixedname_t fname;
+ dns_name_t *name;
+ isc_result_t result;
+
+ len = strlen(origin);
+ isc_buffer_init(&b, origin, len);
+ isc_buffer_add(&b, len);
+
+ dns_fixedname_init(&fname);
+ name = dns_fixedname_name(&fname);
+ result = dns_name_fromtext(name, &b, dns_rootname, ISC_FALSE, NULL);
+ if (result != ISC_R_SUCCESS)
+ fatal("failed converting name '%s' to dns format: %s",
+ origin, isc_result_totext(result));
+
+ result = dns_db_create(mctx, "rbt", name, dns_dbtype_zone,
+ rdclass, 0, NULL, db);
+ check_result(result, "dns_db_create()");
+
+ result = dns_db_load2(*db, file, inputformat);
+ if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE)
+ fatal("failed loading zone from '%s': %s",
+ file, isc_result_totext(result));
+}
+
+/*%
+ * Finds all public zone keys in the zone, and attempts to load the
+ * private keys from disk.
+ */
+static void
+loadzonekeys(dns_db_t *db) {
+ dns_dbnode_t *node;
+ dns_dbversion_t *currentversion;
+ isc_result_t result;
+ dst_key_t *keys[20];
+ unsigned int nkeys, i;
+
+ currentversion = NULL;
+ dns_db_currentversion(db, &currentversion);
+
+ node = NULL;
+ result = dns_db_findnode(db, gorigin, ISC_FALSE, &node);
+ if (result != ISC_R_SUCCESS)
+ fatal("failed to find the zone's origin: %s",
+ isc_result_totext(result));
+
+ result = dns_dnssec_findzonekeys(db, currentversion, node, gorigin,
+ mctx, 20, keys, &nkeys);
+ if (result == ISC_R_NOTFOUND)
+ result = ISC_R_SUCCESS;
+ if (result != ISC_R_SUCCESS)
+ fatal("failed to find the zone keys: %s",
+ isc_result_totext(result));
+
+ for (i = 0; i < nkeys; i++) {
+ signer_key_t *key;
+
+ key = newkeystruct(keys[i], dst_key_isprivate(keys[i]));
+ ISC_LIST_APPEND(keylist, key, link);
+ }
+ dns_db_detachnode(db, &node);
+ dns_db_closeversion(db, &currentversion, ISC_FALSE);
+}
+
+/*%
+ * Finds all public zone keys in the zone.
+ */
+static void
+loadzonepubkeys(dns_db_t *db) {
+ dns_dbversion_t *currentversion = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ dst_key_t *pubkey;
+ signer_key_t *key;
+ isc_result_t result;
+
+ dns_db_currentversion(db, &currentversion);
+
+ result = dns_db_findnode(db, 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);
+ result = dns_db_findrdataset(db, node, currentversion,
+ dns_rdatatype_dnskey, 0, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS)
+ fatal("failed to find keys at the zone apex: %s",
+ isc_result_totext(result));
+ result = dns_rdataset_first(&rdataset);
+ check_result(result, "dns_rdataset_first");
+ while (result == ISC_R_SUCCESS) {
+ pubkey = NULL;
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_dnssec_keyfromrdata(gorigin, &rdata, mctx,
+ &pubkey);
+ if (result != ISC_R_SUCCESS)
+ goto next;
+ if (!dst_key_iszonekey(pubkey)) {
+ dst_key_free(&pubkey);
+ goto next;
+ }
+
+ key = newkeystruct(pubkey, ISC_FALSE);
+ ISC_LIST_APPEND(keylist, key, link);
+ next:
+ result = dns_rdataset_next(&rdataset);
+ }
+ dns_rdataset_disassociate(&rdataset);
+ dns_db_detachnode(db, &node);
+ dns_db_closeversion(db, &currentversion, ISC_FALSE);
+}
+
+static void
+warnifallksk(dns_db_t *db) {
+ dns_dbversion_t *currentversion = NULL;
+ dns_dbnode_t *node = NULL;
+ dns_rdataset_t rdataset;
+ dns_rdata_t rdata = DNS_RDATA_INIT;
+ isc_result_t result;
+ dns_rdata_key_t key;
+ isc_boolean_t have_non_ksk = ISC_FALSE;
+
+ dns_db_currentversion(db, &currentversion);
+
+ result = dns_db_findnode(db, 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);
+ result = dns_db_findrdataset(db, node, currentversion,
+ dns_rdatatype_dnskey, 0, 0, &rdataset, NULL);
+ if (result != ISC_R_SUCCESS)
+ fatal("failed to find keys at the zone apex: %s",
+ isc_result_totext(result));
+ result = dns_rdataset_first(&rdataset);
+ check_result(result, "dns_rdataset_first");
+ while (result == ISC_R_SUCCESS) {
+ dns_rdata_reset(&rdata);
+ dns_rdataset_current(&rdataset, &rdata);
+ result = dns_rdata_tostruct(&rdata, &key, NULL);
+ check_result(result, "dns_rdata_tostruct");
+ if ((key.flags & DNS_KEYFLAG_KSK) == 0) {
+ have_non_ksk = ISC_TRUE;
+ result = ISC_R_NOMORE;
+ } else
+ result = dns_rdataset_next(&rdataset);
+ }
+ dns_rdataset_disassociate(&rdataset);
+ dns_db_detachnode(db, &node);
+ dns_db_closeversion(db, &currentversion, ISC_FALSE);
+ if (!have_non_ksk && !ignoreksk)
+ fprintf(stderr, "%s: warning: No non-KSK dnskey found. "
+ "Supply non-KSK dnskey or use '-z'.\n",
+ program);
+}
+
+static void
+writeset(const char *prefix, dns_rdatatype_t type) {
+ char *filename;
+ char namestr[DNS_NAME_FORMATSIZE];
+ dns_db_t *db = NULL;
+ dns_dbversion_t *version = NULL;
+ dns_diff_t diff;
+ dns_difftuple_t *tuple = NULL;
+ dns_fixedname_t fixed;
+ dns_name_t *name;
+ dns_rdata_t rdata, ds;
+ isc_boolean_t have_ksk = ISC_FALSE;
+ isc_boolean_t have_non_ksk = ISC_FALSE;
+ isc_buffer_t b;
+ isc_buffer_t namebuf;
+ isc_region_t r;
+ isc_result_t result;
+ signer_key_t *key;
+ unsigned char dsbuf[DNS_DS_BUFFERSIZE];
+ unsigned char keybuf[DST_KEY_MAXSIZE];
+ unsigned int filenamelen;
+ const dns_master_style_t *style =
+ (type == dns_rdatatype_dnskey) ? masterstyle : dsstyle;
+
+ isc_buffer_init(&namebuf, namestr, sizeof(namestr));
+ result = dns_name_tofilenametext(gorigin, ISC_FALSE, &namebuf);
+ check_result(result, "dns_name_tofilenametext");
+ isc_buffer_putuint8(&namebuf, 0);
+ filenamelen = strlen(prefix) + strlen(namestr);
+ if (directory != NULL)
+ filenamelen += strlen(directory) + 1;
+ filename = isc_mem_get(mctx, filenamelen + 1);
+ if (filename == NULL)
+ fatal("out of memory");
+ if (directory != NULL)
+ sprintf(filename, "%s/", directory);
+ else
+ filename[0] = 0;
+ strcat(filename, prefix);
+ strcat(filename, namestr);
+
+ dns_diff_init(mctx, &diff);
+
+ for (key = ISC_LIST_HEAD(keylist);
+ key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ if (!key->isksk) {
+ have_non_ksk = ISC_TRUE;
+ break;
+ }
+
+ for (key = ISC_LIST_HEAD(keylist);
+ key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ if (key->isksk) {
+ have_ksk = ISC_TRUE;
+ break;
+ }
+
+ if (type == dns_rdatatype_dlv) {
+ dns_name_t tname;
+ unsigned int labels;
+
+ dns_name_init(&tname, NULL);
+ dns_fixedname_init(&fixed);
+ name = dns_fixedname_name(&fixed);
+ labels = dns_name_countlabels(gorigin);
+ dns_name_getlabelsequence(gorigin, 0, labels - 1, &tname);
+ result = dns_name_concatenate(&tname, dlv, name, NULL);
+ check_result(result, "dns_name_concatenate");
+ } else
+ name = gorigin;
+
+ for (key = ISC_LIST_HEAD(keylist);
+ key != NULL;
+ key = ISC_LIST_NEXT(key, link))
+ {
+ if (have_ksk && have_non_ksk && !key->isksk)
+ continue;
+ dns_rdata_init(&rdata);
+ dns_rdata_init(&ds);
+ isc_buffer_init(&b, keybuf, sizeof(keybuf));
+ result = dst_key_todns(key->key, &b);
+ check_result(result, "dst_key_todns");
+ isc_buffer_usedregion(&b, &r);
+ dns_rdata_fromregion(&rdata, gclass, dns_rdatatype_dnskey, &r);
+ if (type != dns_rdatatype_dnskey) {
+ result = dns_ds_buildrdata(gorigin, &rdata,
+ DNS_DSDIGEST_SHA1,
+ dsbuf, &ds);
+ check_result(result, "dns_ds_buildrdata");
+ if (type == dns_rdatatype_dlv)
+ ds.type = dns_rdatatype_dlv;
+ result = dns_difftuple_create(mctx, DNS_DIFFOP_ADD,
+ name, 0, &ds, &tuple);
+ check_result(result, "dns_difftuple_create");
+ dns_diff_append(&diff, &tuple);
+
+ dns_rdata_reset(&ds);
+ result = dns_ds_buildrdata(gorigin, &rdata,
+ DNS_DSDIGEST_SHA256,
+ dsbuf, &ds);
+ check_result(result, "dns_ds_buildrdata");
+ if (type == dns_rdatatype_dlv)
+ ds.type = dns_rdatatype_dlv;
+ result = dns_difftuple_create(mctx, DNS_DIFFOP_ADD,
+ name, 0, &ds, &tuple);
+
+ } else
+ result = dns_difftuple_create(mctx, DNS_DIFFOP_ADD,
+ gorigin, zonettl,
+ &rdata, &tuple);
+ check_result(result, "dns_difftuple_create");
+ dns_diff_append(&diff, &tuple);
+ }
+
+ result = dns_db_create(mctx, "rbt", dns_rootname, dns_dbtype_zone,
+ gclass, 0, NULL, &db);
+ check_result(result, "dns_db_create");
+
+ result = dns_db_newversion(db, &version);
+ check_result(result, "dns_db_newversion");
+
+ result = dns_diff_apply(&diff, db, version);
+ check_result(result, "dns_diff_apply");
+ dns_diff_clear(&diff);
+
+ result = dns_master_dump(mctx, db, version, style, filename);
+ check_result(result, "dns_master_dump");
+
+ isc_mem_put(mctx, filename, filenamelen + 1);
+
+ dns_db_closeversion(db, &version, ISC_FALSE);
+ dns_db_detach(&db);
+}
+
+static void
+print_time(FILE *fp) {
+ time_t currenttime;
+
+ if (outputformat != dns_masterformat_text)
+ return;
+
+ currenttime = time(NULL);
+ fprintf(fp, "; File written on %s", ctime(&currenttime));
+}
+
+static void
+print_version(FILE *fp) {
+ if (outputformat != dns_masterformat_text)
+ return;
+
+ fprintf(fp, "; dnssec_signzone version " VERSION "\n");
+}
+
+static void
+usage(void) {
+ fprintf(stderr, "Usage:\n");
+ fprintf(stderr, "\t%s [options] zonefile [keys]\n", program);
+
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "Version: %s\n", VERSION);
+
+ fprintf(stderr, "Options: (default value in parenthesis) \n");
+ fprintf(stderr, "\t-c class (IN)\n");
+ fprintf(stderr, "\t-d directory\n");
+ fprintf(stderr, "\t\tdirectory to find keyset files (.)\n");
+ fprintf(stderr, "\t-g:\t");
+ fprintf(stderr, "generate DS records from keyset files\n");
+ fprintf(stderr, "\t-s [YYYYMMDDHHMMSS|+offset]:\n");
+ fprintf(stderr, "\t\tRRSIG start time - absolute|offset (now - 1 hour)\n");
+ fprintf(stderr, "\t-e [YYYYMMDDHHMMSS|+offset|\"now\"+offset]:\n");
+ fprintf(stderr, "\t\tRRSIG end time - absolute|from start|from now "
+ "(now + 30 days)\n");
+ fprintf(stderr, "\t-i interval:\n");
+ fprintf(stderr, "\t\tcycle interval - resign "
+ "if < interval from end ( (end-start)/4 )\n");
+ fprintf(stderr, "\t-j jitter:\n");
+ fprintf(stderr, "\t\trandomize signature end time up to jitter seconds\n");
+ fprintf(stderr, "\t-v debuglevel (0)\n");
+ fprintf(stderr, "\t-o origin:\n");
+ fprintf(stderr, "\t\tzone origin (name of zonefile)\n");
+ fprintf(stderr, "\t-f outfile:\n");
+ fprintf(stderr, "\t\tfile the signed zone is written in "
+ "(zonefile + .signed)\n");
+ fprintf(stderr, "\t-I format:\n");
+ fprintf(stderr, "\t\tfile format of input zonefile (text)\n");
+ fprintf(stderr, "\t-O format:\n");
+ fprintf(stderr, "\t\tfile format of signed zone file (text)\n");
+ fprintf(stderr, "\t-N format:\n");
+ fprintf(stderr, "\t\tsoa serial format of signed zone file (keep)\n");
+ fprintf(stderr, "\t-r randomdev:\n");
+ fprintf(stderr, "\t\ta file containing random data\n");
+ fprintf(stderr, "\t-a:\t");
+ fprintf(stderr, "verify generated signatures\n");
+ fprintf(stderr, "\t-p:\t");
+ fprintf(stderr, "use pseudorandom data (faster but less secure)\n");
+ fprintf(stderr, "\t-t:\t");
+ fprintf(stderr, "print statistics\n");
+ fprintf(stderr, "\t-n ncpus (number of cpus present)\n");
+ fprintf(stderr, "\t-k key_signing_key\n");
+ fprintf(stderr, "\t-l lookasidezone\n");
+ fprintf(stderr, "\t-3 salt (NSEC3 salt)\n");
+ fprintf(stderr, "\t-H interations (NSEC3 interations)\n");
+ fprintf(stderr, "\t-A (NSEC3 optout)\n");
+ fprintf(stderr, "\t-z:\t");
+ fprintf(stderr, "ignore KSK flag in DNSKEYs");
+
+ fprintf(stderr, "\n");
+
+ fprintf(stderr, "Signing Keys: ");
+ fprintf(stderr, "(default: all zone keys that have private keys)\n");
+ fprintf(stderr, "\tkeyfile (Kname+alg+tag)\n");
+ exit(0);
+}
+
+static void
+removetempfile(void) {
+ if (removefile)
+ isc_file_remove(tempfile);
+}
+
+static void
+print_stats(isc_time_t *timer_start, isc_time_t *timer_finish) {
+ isc_uint64_t runtime_us; /* Runtime in microseconds */
+ isc_uint64_t runtime_ms; /* Runtime in milliseconds */
+ isc_uint64_t sig_ms; /* Signatures per millisecond */
+
+ runtime_us = isc_time_microdiff(timer_finish, timer_start);
+
+ printf("Signatures generated: %10d\n", nsigned);
+ printf("Signatures retained: %10d\n", nretained);
+ printf("Signatures dropped: %10d\n", ndropped);
+ printf("Signatures successfully verified: %10d\n", nverified);
+ printf("Signatures unsuccessfully verified: %10d\n", nverifyfailed);
+ runtime_ms = runtime_us / 1000;
+ printf("Runtime in seconds: %7u.%03u\n",
+ (unsigned int) (runtime_ms / 1000),
+ (unsigned int) (runtime_ms % 1000));
+ if (runtime_us > 0) {
+ sig_ms = ((isc_uint64_t)nsigned * 1000000000) / runtime_us;
+ printf("Signatures per second: %7u.%03u\n",
+ (unsigned int) sig_ms / 1000,
+ (unsigned int) sig_ms % 1000);
+ }
+}
+
+int
+main(int argc, char *argv[]) {
+ int i, ch;
+ char *startstr = NULL, *endstr = NULL, *classname = NULL;
+ char *origin = NULL, *file = NULL, *output = NULL;
+ char *inputformatstr = NULL, *outputformatstr = NULL;
+ char *serialformatstr = NULL;
+ char *dskeyfile[MAXDSKEYS];
+ int ndskeys = 0;
+ char *endp;
+ isc_time_t timer_start, timer_finish;
+ signer_key_t *key;
+ isc_result_t result;
+ isc_log_t *log = NULL;
+ isc_boolean_t pseudorandom = ISC_FALSE;
+ unsigned int eflags;
+ isc_boolean_t free_output = ISC_FALSE;
+ int tempfilelen;
+ dns_rdataclass_t rdclass;
+ isc_task_t **tasks = NULL;
+ isc_buffer_t b;
+ int len;
+ unsigned int iterations = 100U;
+ const unsigned char *salt = NULL;
+ size_t salt_length = 0;
+ unsigned char saltbuf[255];
+ hashlist_t hashlist;
+
+#define CMDLINE_FLAGS "3:aAc:d:e:f:ghH:i:I:j:k:l:m:n:N:o:O:pr:s:StUv:z"
+
+ /*
+ * Process memory debugging arguement first.
+ */
+ while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) {
+ switch (ch) {
+ case 'm':
+ if (strcasecmp(isc_commandline_argument, "record") == 0)
+ isc_mem_debugging |= ISC_MEM_DEBUGRECORD;
+ if (strcasecmp(isc_commandline_argument, "trace") == 0)
+ isc_mem_debugging |= ISC_MEM_DEBUGTRACE;
+ if (strcasecmp(isc_commandline_argument, "usage") == 0)
+ isc_mem_debugging |= ISC_MEM_DEBUGUSAGE;
+ if (strcasecmp(isc_commandline_argument, "size") == 0)
+ isc_mem_debugging |= ISC_MEM_DEBUGSIZE;
+ if (strcasecmp(isc_commandline_argument, "mctx") == 0)
+ isc_mem_debugging |= ISC_MEM_DEBUGCTX;
+ break;
+ default:
+ break;
+ }
+ }
+ isc_commandline_reset = ISC_TRUE;
+
+ masterstyle = &dns_master_style_explicitttl;
+
+ check_result(isc_app_start(), "isc_app_start");
+
+ result = isc_mem_create(0, 0, &mctx);
+ if (result != ISC_R_SUCCESS)
+ fatal("out of memory");
+
+ dns_result_register();
+
+ isc_commandline_errprint = ISC_FALSE;
+
+ while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) {
+ switch (ch) {
+ case '3':
+ if (strcmp(isc_commandline_argument, "-")) {
+ isc_buffer_t target;
+ char *sarg;
+
+ sarg = isc_commandline_argument;
+ isc_buffer_init(&target, saltbuf,
+ sizeof(saltbuf));
+ result = isc_hex_decodestring(sarg, &target);
+ check_result(result,
+ "isc_hex_decodestring(salt)");
+ salt = saltbuf;
+ salt_length = isc_buffer_usedlength(&target);
+ } else {
+ salt = saltbuf;
+ salt_length = 0;
+ }
+ nsec_datatype = dns_rdatatype_nsec3;
+ break;
+
+ case 'A':
+ nsec3flags |= DNS_NSEC3FLAG_OPTOUT;
+ break;
+
+ case 'a':
+ tryverify = ISC_TRUE;
+ break;
+
+ case 'c':
+ classname = isc_commandline_argument;
+ break;
+
+ case 'd':
+ directory = isc_commandline_argument;
+ break;
+
+ case 'e':
+ endstr = isc_commandline_argument;
+ break;
+
+ case 'f':
+ output = isc_commandline_argument;
+ break;
+
+ case 'g':
+ generateds = ISC_TRUE;
+ break;
+
+ case '?':
+ if (isc_commandline_option != '?')
+ fprintf(stderr, "%s: invalid argument -%c\n",
+ program, isc_commandline_option);
+ case 'h':
+ usage();
+ break;
+
+ default:
+ fprintf(stderr, "%s: unhandled option -%c\n",
+ program, isc_commandline_option);
+ exit(1);
+
+ case 'i':
+ endp = NULL;
+ cycle = strtol(isc_commandline_argument, &endp, 0);
+ if (*endp != '\0' || cycle < 0)
+ fatal("cycle period must be numeric and "
+ "positive");
+ break;
+
+ case 'I':
+ inputformatstr = isc_commandline_argument;
+ break;
+
+ case 'j':
+ endp = NULL;
+ jitter = strtol(isc_commandline_argument, &endp, 0);
+ if (*endp != '\0' || jitter < 0)
+ fatal("jitter must be numeric and positive");
+ break;
+
+ case 'l':
+ dns_fixedname_init(&dlv_fixed);
+ len = strlen(isc_commandline_argument);
+ isc_buffer_init(&b, isc_commandline_argument, len);
+ isc_buffer_add(&b, len);
+
+ dns_fixedname_init(&dlv_fixed);
+ dlv = dns_fixedname_name(&dlv_fixed);
+ result = dns_name_fromtext(dlv, &b, dns_rootname,
+ ISC_FALSE, NULL);
+ check_result(result, "dns_name_fromtext(dlv)");
+ break;
+
+ case 'k':
+ if (ndskeys == MAXDSKEYS)
+ fatal("too many key-signing keys specified");
+ dskeyfile[ndskeys++] = isc_commandline_argument;
+ break;
+
+ case 'm':
+ break;
+
+ case 'n':
+ endp = NULL;
+ ntasks = strtol(isc_commandline_argument, &endp, 0);
+ if (*endp != '\0' || ntasks > ISC_INT32_MAX)
+ fatal("number of cpus must be numeric");
+ break;
+
+ case 'N':
+ serialformatstr = isc_commandline_argument;
+ break;
+
+ case 'H':
+ iterations = strtoul(isc_commandline_argument,
+ &endp, 0);
+ if (*endp != '\0')
+ fatal("iterations must be numeric");
+ if (iterations > 0xffffU)
+ fatal("iterations too big");
+ break;
+
+ case 'o':
+ origin = isc_commandline_argument;
+ break;
+
+ case 'O':
+ outputformatstr = isc_commandline_argument;
+ break;
+
+ case 'p':
+ pseudorandom = ISC_TRUE;
+ break;
+
+ case 'r':
+ setup_entropy(mctx, isc_commandline_argument, &ectx);
+ break;
+
+ case 's':
+ startstr = isc_commandline_argument;
+ break;
+
+ case 'S':
+ /* This is intentionally undocumented */
+ /* -S: simple output style */
+ masterstyle = &dns_master_style_simple;
+ break;
+
+ case 't':
+ printstats = ISC_TRUE;
+ break;
+
+ case 'U': /* Undocumented for testing only. */
+ unknownalg = ISC_TRUE;
+ break;
+
+ case 'v':
+ endp = NULL;
+ verbose = strtol(isc_commandline_argument, &endp, 0);
+ if (*endp != '\0')
+ fatal("verbose level must be numeric");
+ break;
+
+ case 'z':
+ ignoreksk = ISC_TRUE;
+ break;
+ }
+ }
+
+ if (ectx == NULL)
+ setup_entropy(mctx, NULL, &ectx);
+ eflags = ISC_ENTROPY_BLOCKING;
+ if (!pseudorandom)
+ eflags |= ISC_ENTROPY_GOODONLY;
+
+ result = isc_hash_create(mctx, ectx, DNS_NAME_MAXWIRE);
+ if (result != ISC_R_SUCCESS)
+ fatal("could not create hash context");
+
+ result = dst_lib_init(mctx, ectx, eflags);
+ if (result != ISC_R_SUCCESS)
+ fatal("could not initialize dst");
+
+ isc_stdtime_get(&now);
+
+ if (startstr != NULL)
+ starttime = strtotime(startstr, now, now);
+ else
+ starttime = now - 3600; /* Allow for some clock skew. */
+
+ if (endstr != NULL)
+ endtime = strtotime(endstr, now, starttime);
+ else
+ endtime = starttime + (30 * 24 * 60 * 60);
+
+ if (cycle == -1)
+ cycle = (endtime - starttime) / 4;
+
+ if (ntasks == 0)
+ ntasks = isc_os_ncpus() * 2;
+ vbprintf(4, "using %d cpus\n", ntasks);
+
+ rdclass = strtoclass(classname);
+
+ setup_logging(verbose, mctx, &log);
+
+ argc -= isc_commandline_index;
+ argv += isc_commandline_index;
+
+ if (argc < 1)
+ usage();
+
+ file = argv[0];
+
+ argc -= 1;
+ argv += 1;
+
+ if (origin == NULL)
+ origin = file;
+
+ if (output == NULL) {
+ free_output = ISC_TRUE;
+ output = isc_mem_allocate(mctx,
+ strlen(file) + strlen(".signed") + 1);
+ if (output == NULL)
+ fatal("out of memory");
+ sprintf(output, "%s.signed", file);
+ }
+
+ if (inputformatstr != NULL) {
+ if (strcasecmp(inputformatstr, "text") == 0)
+ inputformat = dns_masterformat_text;
+ else if (strcasecmp(inputformatstr, "raw") == 0)
+ inputformat = dns_masterformat_raw;
+ else
+ fatal("unknown file format: %s\n", inputformatstr);
+ }
+
+ if (outputformatstr != NULL) {
+ if (strcasecmp(outputformatstr, "text") == 0)
+ outputformat = dns_masterformat_text;
+ else if (strcasecmp(outputformatstr, "raw") == 0)
+ outputformat = dns_masterformat_raw;
+ else
+ fatal("unknown file format: %s\n", outputformatstr);
+ }
+
+ if (serialformatstr != NULL) {
+ if (strcasecmp(serialformatstr, "keep") == 0)
+ serialformat = SOA_SERIAL_KEEP;
+ else if (strcasecmp(serialformatstr, "increment") == 0 ||
+ strcasecmp(serialformatstr, "incr") == 0)
+ serialformat = SOA_SERIAL_INCREMENT;
+ else if (strcasecmp(serialformatstr, "unixtime") == 0)
+ serialformat = SOA_SERIAL_UNIXTIME;
+ else
+ fatal("unknown soa serial format: %s\n", serialformatstr);
+ }
+
+ result = dns_master_stylecreate(&dsstyle, DNS_STYLEFLAG_NO_TTL,
+ 0, 24, 0, 0, 0, 8, mctx);
+ check_result(result, "dns_master_stylecreate");
+
+ gdb = NULL;
+ TIME_NOW(&timer_start);
+ loadzone(file, origin, rdclass, &gdb);
+ gorigin = dns_db_origin(gdb);
+ gclass = dns_db_class(gdb);
+ zonettl = soattl();
+
+ if (IS_NSEC3) {
+ isc_boolean_t answer;
+ hash_length = dns_nsec3_hashlength(dns_hash_sha1);
+ hashlist_init(&hashlist, dns_db_nodecount(gdb) * 2,
+ hash_length);
+ result = dns_nsec_nseconly(gdb, gversion, &answer);
+ check_result(result, "dns_nsec_nseconly");
+ if (answer)
+ fatal("NSEC3 generation requested with "
+ "NSEC only DNSKEY");
+ }
+
+ ISC_LIST_INIT(keylist);
+
+ if (argc == 0) {
+ loadzonekeys(gdb);
+ } else {
+ for (i = 0; i < argc; i++) {
+ dst_key_t *newkey = NULL;
+
+ result = dst_key_fromnamedfile(argv[i],
+ DST_TYPE_PUBLIC |
+ DST_TYPE_PRIVATE,
+ mctx, &newkey);
+ if (result != ISC_R_SUCCESS)
+ fatal("cannot load dnskey %s: %s", argv[i],
+ isc_result_totext(result));
+
+ if (!dns_name_equal(gorigin, dst_key_name(newkey)))
+ fatal("key %s not at origin\n", argv[i]);
+
+ key = ISC_LIST_HEAD(keylist);
+ while (key != NULL) {
+ dst_key_t *dkey = key->key;
+ if (dst_key_id(dkey) == dst_key_id(newkey) &&
+ dst_key_alg(dkey) == dst_key_alg(newkey) &&
+ dns_name_equal(dst_key_name(dkey),
+ dst_key_name(newkey)))
+ {
+ if (!dst_key_isprivate(dkey))
+ fatal("cannot sign zone with "
+ "non-private dnskey %s",
+ argv[i]);
+ break;
+ }
+ key = ISC_LIST_NEXT(key, link);
+ }
+ if (key == NULL) {
+ key = newkeystruct(newkey, ISC_TRUE);
+ ISC_LIST_APPEND(keylist, key, link);
+ } else
+ dst_key_free(&newkey);
+ }
+
+ loadzonepubkeys(gdb);
+ }
+
+ for (i = 0; i < ndskeys; i++) {
+ dst_key_t *newkey = NULL;
+
+ result = dst_key_fromnamedfile(dskeyfile[i],
+ DST_TYPE_PUBLIC |
+ DST_TYPE_PRIVATE,
+ mctx, &newkey);
+ if (result != ISC_R_SUCCESS)
+ fatal("cannot load dnskey %s: %s", dskeyfile[i],
+ isc_result_totext(result));
+
+ if (!dns_name_equal(gorigin, dst_key_name(newkey)))
+ fatal("key %s not at origin\n", dskeyfile[i]);
+
+ key = ISC_LIST_HEAD(keylist);
+ while (key != NULL) {
+ dst_key_t *dkey = key->key;
+ if (dst_key_id(dkey) == dst_key_id(newkey) &&
+ dst_key_alg(dkey) == dst_key_alg(newkey) &&
+ dns_name_equal(dst_key_name(dkey),
+ dst_key_name(newkey)))
+ {
+ /* Override key flags. */
+ key->issigningkey = ISC_TRUE;
+ key->isksk = ISC_TRUE;
+ key->isdsk = ISC_FALSE;
+ dst_key_free(&dkey);
+ key->key = newkey;
+ break;
+ }
+ key = ISC_LIST_NEXT(key, link);
+ }
+ if (key == NULL) {
+ /* Override dnskey flags. */
+ key = newkeystruct(newkey, ISC_TRUE);
+ key->isksk = ISC_TRUE;
+ key->isdsk = ISC_FALSE;
+ ISC_LIST_APPEND(keylist, key, link);
+ }
+ }
+
+ if (ISC_LIST_EMPTY(keylist)) {
+ fprintf(stderr, "%s: warning: No keys specified or found\n",
+ program);
+ nokeys = ISC_TRUE;
+ }
+
+ if (IS_NSEC3) {
+ unsigned int max;
+ result = dns_nsec3_maxiterations(gdb, NULL, mctx, &max);
+ check_result(result, "dns_nsec3_maxiterations()");
+ if (iterations > max)
+ fatal("NSEC3 interations too big for weakest DNSKEY "
+ "strength. Maximum iterations allowed %u.", max);
+ }
+
+ warnifallksk(gdb);
+
+ gversion = NULL;
+ result = dns_db_newversion(gdb, &gversion);
+ check_result(result, "dns_db_newversion()");
+
+ switch (serialformat) {
+ case SOA_SERIAL_INCREMENT:
+ setsoaserial(0);
+ break;
+ case SOA_SERIAL_UNIXTIME:
+ setsoaserial(now);
+ break;
+ case SOA_SERIAL_KEEP:
+ default:
+ /* do nothing */
+ break;
+ }
+
+ if (IS_NSEC3)
+ nsec3ify(dns_hash_sha1, iterations, salt, salt_length,
+ &hashlist);
+ else
+ nsecify();
+
+ if (!nokeys) {
+ writeset("keyset-", dns_rdatatype_dnskey);
+ writeset("dsset-", dns_rdatatype_ds);
+ if (dlv != NULL) {
+ writeset("dlvset-", dns_rdatatype_dlv);
+ }
+ }
+
+ tempfilelen = strlen(output) + 20;
+ tempfile = isc_mem_get(mctx, tempfilelen);
+ if (tempfile == NULL)
+ fatal("out of memory");
+
+ result = isc_file_mktemplate(output, tempfile, tempfilelen);
+ check_result(result, "isc_file_mktemplate");
+
+ fp = NULL;
+ result = isc_file_openunique(tempfile, &fp);
+ if (result != ISC_R_SUCCESS)
+ fatal("failed to open temporary output file: %s",
+ isc_result_totext(result));
+ removefile = ISC_TRUE;
+ setfatalcallback(&removetempfile);
+
+ print_time(fp);
+ print_version(fp);
+
+ result = isc_taskmgr_create(mctx, ntasks, 0, &taskmgr);
+ if (result != ISC_R_SUCCESS)
+ fatal("failed to create task manager: %s",
+ isc_result_totext(result));
+
+ master = NULL;
+ result = isc_task_create(taskmgr, 0, &master);
+ if (result != ISC_R_SUCCESS)
+ fatal("failed to create task: %s", isc_result_totext(result));
+
+ tasks = isc_mem_get(mctx, ntasks * sizeof(isc_task_t *));
+ if (tasks == NULL)
+ fatal("out of memory");
+ for (i = 0; i < (int)ntasks; i++) {
+ tasks[i] = NULL;
+ result = isc_task_create(taskmgr, 0, &tasks[i]);
+ if (result != ISC_R_SUCCESS)
+ fatal("failed to create task: %s",
+ isc_result_totext(result));
+ }
+
+ RUNTIME_CHECK(isc_mutex_init(&namelock) == ISC_R_SUCCESS);
+ if (printstats)
+ RUNTIME_CHECK(isc_mutex_init(&statslock) == ISC_R_SUCCESS);
+
+ presign();
+ signapex();
+ if (!finished) {
+ /*
+ * There is more work to do. Spread it out over multiple
+ * processors if possible.
+ */
+ for (i = 0; i < (int)ntasks; i++) {
+ result = isc_app_onrun(mctx, master, startworker,
+ tasks[i]);
+ if (result != ISC_R_SUCCESS)
+ fatal("failed to start task: %s",
+ isc_result_totext(result));
+ }
+ (void)isc_app_run();
+ if (!finished)
+ fatal("process aborted by user");
+ } else
+ isc_task_detach(&master);
+ shuttingdown = ISC_TRUE;
+ for (i = 0; i < (int)ntasks; i++)
+ isc_task_detach(&tasks[i]);
+ isc_taskmgr_destroy(&taskmgr);
+ isc_mem_put(mctx, tasks, ntasks * sizeof(isc_task_t *));
+ postsign();
+
+ if (outputformat != dns_masterformat_text) {
+ result = dns_master_dumptostream2(mctx, gdb, gversion,
+ masterstyle, outputformat,
+ fp);
+ check_result(result, "dns_master_dumptostream2");
+ }
+
+ result = isc_stdio_close(fp);
+ check_result(result, "isc_stdio_close");
+ removefile = ISC_FALSE;
+
+ result = isc_file_rename(tempfile, output);
+ if (result != ISC_R_SUCCESS)
+ fatal("failed to rename temp file to %s: %s\n",
+ output, isc_result_totext(result));
+
+ DESTROYLOCK(&namelock);
+ if (printstats)
+ DESTROYLOCK(&statslock);
+
+ printf("%s\n", output);
+
+ dns_db_closeversion(gdb, &gversion, ISC_FALSE);
+ dns_db_detach(&gdb);
+
+ while (!ISC_LIST_EMPTY(keylist)) {
+ key = ISC_LIST_HEAD(keylist);
+ ISC_LIST_UNLINK(keylist, key, link);
+ dst_key_free(&key->key);
+ isc_mem_put(mctx, key, sizeof(signer_key_t));
+ }
+
+ isc_mem_put(mctx, tempfile, tempfilelen);
+
+ if (free_output)
+ isc_mem_free(mctx, output);
+
+ dns_master_styledestroy(&dsstyle, mctx);
+
+ cleanup_logging(&log);
+ dst_lib_destroy();
+ isc_hash_destroy();
+ cleanup_entropy(&ectx);
+ dns_name_destroy();
+ if (verbose > 10)
+ isc_mem_stats(mctx, stdout);
+ isc_mem_destroy(&mctx);
+
+ (void) isc_app_finish();
+
+ if (printstats) {
+ TIME_NOW(&timer_finish);
+ print_stats(&timer_start, &timer_finish);
+ }
+
+ return (0);
+}