diff options
Diffstat (limited to '')
-rw-r--r-- | bin/dnssec/dnssec-signzone.c | 4197 |
1 files changed, 4197 insertions, 0 deletions
diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c new file mode 100644 index 0000000..2d2c158 --- /dev/null +++ b/bin/dnssec/dnssec-signzone.c @@ -0,0 +1,4197 @@ +/* + * Portions Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + * + * Portions Copyright (C) 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. + */ + +/*! \file */ + +#include <inttypes.h> +#include <stdbool.h> +#include <stdlib.h> +#include <time.h> +#include <unistd.h> + +#include <isc/app.h> +#include <isc/atomic.h> +#include <isc/base32.h> +#include <isc/commandline.h> +#include <isc/event.h> +#include <isc/file.h> +#include <isc/hash.h> +#include <isc/hex.h> +#include <isc/managers.h> +#include <isc/md.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/safe.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/rdataclass.h> +#include <dns/rdatalist.h> +#include <dns/rdataset.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 <dns/update.h> +#include <dns/zoneverify.h> + +#include <dst/dst.h> + +#if USE_PKCS11 +#include <pk11/result.h> +#endif /* if USE_PKCS11 */ + +#include "dnssectool.h" + +const char *program = "dnssec-signzone"; + +typedef struct hashlist hashlist_t; + +static int nsec_datatype = dns_rdatatype_nsec; + +#define check_dns_dbiterator_current(result) \ + check_result((result == DNS_R_NEWORIGIN) ? ISC_R_SUCCESS : result, \ + "dns_dbiterator_current()") + +#define IS_NSEC3 (nsec_datatype == dns_rdatatype_nsec3) +#define OPTOUT(x) (((x)&DNS_NSEC3FLAG_OPTOUT) != 0) + +#define REVOKE(x) ((dst_key_flags(x) & DNS_KEYFLAG_REVOKE) != 0) + +#define BUFSIZE 2048 +#define MAXDSKEYS 8 + +#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 +#define SOA_SERIAL_DATE 3 + +typedef struct signer_event sevent_t; +struct signer_event { + ISC_EVENT_COMMON(sevent_t); + dns_fixedname_t *fname; + dns_dbnode_t *node; +}; + +static dns_dnsseckeylist_t keylist; +static unsigned int keycount = 0; +static isc_rwlock_t keylist_lock; +static isc_stdtime_t starttime = 0, endtime = 0, dnskey_endtime = 0, now; +static int cycle = -1; +static int jitter = 0; +static bool tryverify = false; +static bool printstats = false; +static isc_mem_t *mctx = NULL; +static dns_ttl_t zone_soa_min_ttl; +static dns_ttl_t soa_ttl; +static FILE *outfp = NULL; +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 uint32_t rawversion = 1, serialnum = 0; +static bool snset = false; +static unsigned int nsigned = 0, nretained = 0, ndropped = 0; +static unsigned int nverified = 0, nverifyfailed = 0; +static const char *directory = NULL, *dsdir = NULL; +static isc_mutex_t namelock, statslock; +static isc_nm_t *netmgr = NULL; +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 dns_iterations_t nsec3iter = 10U; +static unsigned char saltbuf[255]; +static unsigned char *gsalt = saltbuf; +static size_t salt_length = 0; +static isc_task_t *master = NULL; +static unsigned int ntasks = 0; +static atomic_bool shuttingdown; +static atomic_bool finished; +static bool nokeys = false; +static bool removefile = false; +static bool generateds = false; +static bool ignore_kskflag = false; +static bool keyset_kskonly = false; +static dns_master_style_t *dsstyle = NULL; +static unsigned int serialformat = SOA_SERIAL_KEEP; +static unsigned int hash_length = 0; +static bool unknownalg = false; +static bool disable_zone_check = false; +static bool update_chain = false; +static bool set_keyttl = false; +static dns_ttl_t keyttl; +static bool smartsign = false; +static bool remove_orphansigs = false; +static bool remove_inactkeysigs = false; +static bool output_dnssec_only = false; +static bool output_stdout = false; +static bool set_maxttl = false; +static dns_ttl_t maxttl = 0; +static bool no_max_check = false; + +#define INCSTAT(counter) \ + if (printstats) { \ + LOCK(&statslock); \ + counter++; \ + UNLOCK(&statslock); \ + } + +static void +sign(isc_task_t *task, isc_event_t *event); + +/*% + * Store a copy of 'name' in 'fzonecut' and return a pointer to that copy. + */ +static dns_name_t * +savezonecut(dns_fixedname_t *fzonecut, dns_name_t *name) { + dns_name_t *result; + + result = dns_fixedname_initname(fzonecut); + dns_name_copynf(name, result); + + return (result); +} + +static void +dumpnode(dns_name_t *name, dns_dbnode_t *node) { + dns_rdataset_t rds; + dns_rdatasetiter_t *iter = NULL; + isc_buffer_t *buffer = NULL; + isc_region_t r; + isc_result_t result; + unsigned bufsize = 4096; + + if (outputformat != dns_masterformat_text) { + return; + } + + if (!output_dnssec_only) { + result = dns_master_dumpnodetostream(mctx, gdb, gversion, node, + name, masterstyle, outfp); + check_result(result, "dns_master_dumpnodetostream"); + return; + } + + result = dns_db_allrdatasets(gdb, node, gversion, 0, 0, &iter); + check_result(result, "dns_db_allrdatasets"); + + dns_rdataset_init(&rds); + + isc_buffer_allocate(mctx, &buffer, bufsize); + + for (result = dns_rdatasetiter_first(iter); result == ISC_R_SUCCESS; + result = dns_rdatasetiter_next(iter)) + { + dns_rdatasetiter_current(iter, &rds); + + if (rds.type != dns_rdatatype_rrsig && + rds.type != dns_rdatatype_nsec && + rds.type != dns_rdatatype_nsec3 && + rds.type != dns_rdatatype_nsec3param && + (!smartsign || rds.type != dns_rdatatype_dnskey)) + { + dns_rdataset_disassociate(&rds); + continue; + } + + for (;;) { + result = dns_master_rdatasettotext( + name, &rds, masterstyle, NULL, buffer); + if (result != ISC_R_NOSPACE) { + break; + } + + bufsize <<= 1; + isc_buffer_free(&buffer); + isc_buffer_allocate(mctx, &buffer, bufsize); + } + check_result(result, "dns_master_rdatasettotext"); + + isc_buffer_usedregion(buffer, &r); + result = isc_stdio_write(r.base, 1, r.length, outfp, NULL); + check_result(result, "isc_stdio_write"); + isc_buffer_clear(buffer); + + dns_rdataset_disassociate(&rds); + } + + isc_buffer_free(&buffer); + dns_rdatasetiter_destroy(&iter); +} + +/*% + * Sign the given RRset with given key, and add the signature record to the + * given tuple. + */ +static void +signwithkey(dns_name_t *name, dns_rdataset_t *rdataset, dst_key_t *key, + dns_ttl_t ttl, dns_diff_t *add, const char *logmsg) { + isc_result_t result; + isc_stdtime_t jendtime, expiry; + char keystr[DST_KEY_FORMATSIZE]; + dns_rdata_t trdata = DNS_RDATA_INIT; + unsigned char array[BUFSIZE]; + isc_buffer_t b; + dns_difftuple_t *tuple; + + dst_key_format(key, keystr, sizeof(keystr)); + vbprintf(1, "\t%s %s\n", logmsg, keystr); + + if (rdataset->type == dns_rdatatype_dnskey) { + expiry = dnskey_endtime; + } else { + expiry = endtime; + } + + jendtime = (jitter != 0) ? expiry - isc_random_uniform(jitter) : expiry; + isc_buffer_init(&b, array, sizeof(array)); + result = dns_dnssec_sign(name, rdataset, key, &starttime, &jendtime, + mctx, &b, &trdata); + if (result != ISC_R_SUCCESS) { + fatal("dnskey '%s' failed to sign data: %s", keystr, + isc_result_totext(result)); + } + INCSTAT(nsigned); + + if (tryverify) { + result = dns_dnssec_verify(name, rdataset, key, true, 0, mctx, + &trdata, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) { + vbprintf(3, "\tsignature verified\n"); + INCSTAT(nverified); + } else { + vbprintf(3, "\tsignature failed to verify\n"); + INCSTAT(nverifyfailed); + } + } + + tuple = NULL; + result = dns_difftuple_create(mctx, DNS_DIFFOP_ADDRESIGN, name, ttl, + &trdata, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(add, &tuple); +} + +static bool +issigningkey(dns_dnsseckey_t *key) { + return (key->force_sign || key->hint_sign); +} + +static bool +ispublishedkey(dns_dnsseckey_t *key) { + return ((key->force_publish || key->hint_publish) && !key->hint_remove); +} + +static bool +iszonekey(dns_dnsseckey_t *key) { + return (dns_name_equal(dst_key_name(key->key), gorigin) && + dst_key_iszonekey(key->key)); +} + +static bool +isksk(dns_dnsseckey_t *key) { + return (key->ksk); +} + +static bool +iszsk(dns_dnsseckey_t *key) { + return (ignore_kskflag || !key->ksk); +} + +/*% + * Find the key that generated an RRSIG, if it is in the key list. If + * so, return a pointer to it, otherwise return NULL. + * + * No locking is performed here, this must be done by the caller. + */ +static dns_dnsseckey_t * +keythatsigned_unlocked(dns_rdata_rrsig_t *rrsig) { + dns_dnsseckey_t *key; + + for (key = ISC_LIST_HEAD(keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + 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); + } + } + 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 dns_dnsseckey_t * +keythatsigned(dns_rdata_rrsig_t *rrsig) { + isc_result_t result; + dst_key_t *pubkey = NULL, *privkey = NULL; + dns_dnsseckey_t *key = NULL; + + RWLOCK(&keylist_lock, isc_rwlocktype_read); + key = keythatsigned_unlocked(rrsig); + RWUNLOCK(&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, directory, + 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, directory, mctx, &privkey); + if (result == ISC_R_SUCCESS) { + dst_key_free(&pubkey); + result = dns_dnsseckey_create(mctx, &privkey, &key); + } else { + result = dns_dnsseckey_create(mctx, &pubkey, &key); + } + + if (result == ISC_R_SUCCESS) { + key->force_publish = false; + key->force_sign = false; + key->index = keycount++; + 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 bool +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 (true); + case DNS_R_DELEGATION: + case DNS_R_CNAME: + case DNS_R_DNAME: + return (false); + } + dns_name_format(name, namestr, sizeof(namestr)); + fatal("failure looking for '%s DNSKEY' in database: %s", namestr, + isc_result_totext(result)); + UNREACHABLE(); + return (false); /* removes a warning */ +} + +static bool +setverifies(dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, + dns_rdata_t *rrsig) { + isc_result_t result; + result = dns_dnssec_verify(name, set, key, false, 0, mctx, rrsig, NULL); + if (result == ISC_R_SUCCESS || result == DNS_R_FROMWILDCARD) { + INCSTAT(nverified); + return (true); + } else { + INCSTAT(nverifyfailed); + return (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; + dns_dnsseckey_t *key; + isc_result_t result; + bool nosigs = false; + bool *wassignedby, *nowsignedby; + int arraysize; + dns_difftuple_t *tuple; + dns_ttl_t ttl; + int i; + char namestr[DNS_NAME_FORMATSIZE]; + char typestr[DNS_RDATATYPE_FORMATSIZE]; + char sigstr[SIG_FORMATSIZE]; + + dns_name_format(name, namestr, sizeof(namestr)); + dns_rdatatype_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) { + vbprintf(2, "no existing signatures for %s/%s\n", namestr, + typestr); + result = ISC_R_SUCCESS; + nosigs = 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(bool)); + nowsignedby = isc_mem_get(mctx, arraysize * sizeof(bool)); + + for (i = 0; i < arraysize; i++) { + wassignedby[i] = nowsignedby[i] = false; + } + + if (nosigs) { + result = ISC_R_NOMORE; + } else { + result = dns_rdataset_first(&sigset); + } + + while (result == ISC_R_SUCCESS) { + bool expired, future; + bool keep = false, resign = 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)); + expired = isc_serial_gt(now + cycle, 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) { + keep = (!expired && !remove_orphansigs); + vbprintf(2, "\trrsig by %s %s - dnskey not found\n", + keep ? "retained" : "dropped", sigstr); + } else if (!dns_dnssec_keyactive(key->key, now) && + remove_inactkeysigs) + { + keep = false; + vbprintf(2, "\trrsig by %s dropped - key inactive\n", + sigstr); + } else if (issigningkey(key)) { + wassignedby[key->index] = true; + + if (!expired && rrsig.originalttl == set->ttl && + setverifies(name, set, key->key, &sigrdata)) + { + vbprintf(2, "\trrsig by %s retained\n", sigstr); + keep = true; + } else { + vbprintf(2, "\trrsig by %s dropped - %s\n", + sigstr, + expired ? "expired" + : rrsig.originalttl != set->ttl + ? "ttl change" + : "failed to " + "verify"); + resign = true; + } + } else if (!ispublishedkey(key) && remove_orphansigs) { + vbprintf(2, "\trrsig by %s dropped - dnskey removed\n", + sigstr); + } else if (iszonekey(key)) { + wassignedby[key->index] = true; + + if (!expired && rrsig.originalttl == set->ttl && + setverifies(name, set, key->key, &sigrdata)) + { + vbprintf(2, "\trrsig by %s retained\n", sigstr); + keep = true; + } else { + vbprintf(2, "\trrsig by %s dropped - %s\n", + sigstr, + expired ? "expired" + : rrsig.originalttl != set->ttl + ? "ttl change" + : "failed to " + "verify"); + } + } else if (!expired) { + vbprintf(2, "\trrsig by %s retained\n", sigstr); + keep = true; + } else { + vbprintf(2, "\trrsig by %s expired\n", sigstr); + } + + if (keep) { + if (key != NULL) { + nowsignedby[key->index] = true; + } + INCSTAT(nretained); + if (sigset.ttl != ttl) { + vbprintf(2, "\tfixing ttl %s\n", sigstr); + tuple = NULL; + result = dns_difftuple_create( + mctx, DNS_DIFFOP_DELRESIGN, name, + sigset.ttl, &sigrdata, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(del, &tuple); + result = dns_difftuple_create( + mctx, DNS_DIFFOP_ADDRESIGN, name, ttl, + &sigrdata, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(add, &tuple); + } + } else { + tuple = NULL; + vbprintf(2, "\tremoving signature by %s\n", sigstr); + result = dns_difftuple_create( + mctx, DNS_DIFFOP_DELRESIGN, name, sigset.ttl, + &sigrdata, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(del, &tuple); + INCSTAT(ndropped); + } + + if (resign) { + INSIST(!keep); + + signwithkey(name, set, key->key, ttl, add, + "resigning with dnskey"); + nowsignedby[key->index] = true; + } + + 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)) + { + if (nowsignedby[key->index]) { + continue; + } + + if (!issigningkey(key)) { + continue; + } + + if ((set->type == dns_rdatatype_cds || + set->type == dns_rdatatype_cdnskey || + set->type == dns_rdatatype_dnskey) && + dns_name_equal(name, gorigin)) + { + bool have_ksk; + dns_dnsseckey_t *curr; + + have_ksk = isksk(key); + for (curr = ISC_LIST_HEAD(keylist); curr != NULL; + curr = ISC_LIST_NEXT(curr, link)) + { + if (dst_key_alg(key->key) != + dst_key_alg(curr->key)) + { + continue; + } + if (REVOKE(curr->key)) { + continue; + } + if (isksk(curr)) { + have_ksk = true; + } + } + if (isksk(key) || !have_ksk || + (iszsk(key) && !keyset_kskonly)) + { + signwithkey(name, set, key->key, ttl, add, + "signing with dnskey"); + } + } else if (iszsk(key)) { + /* + * Sign with the ZSK unless there is a predecessor + * key that already signs this RRset. + */ + bool have_pre_sig = false; + dns_dnsseckey_t *curr; + uint32_t pre; + isc_result_t ret = dst_key_getnum( + key->key, DST_NUM_PREDECESSOR, &pre); + if (ret == ISC_R_SUCCESS) { + /* + * This key has a predecessor, look for the + * corresponding key in the keylist. The + * key we are looking for must be: + * - From the same cryptographic algorithm. + * - Have the ZSK type (iszsk). + * - Have key ID equal to the predecessor id. + * - Have a successor that matches 'key' id. + */ + for (curr = ISC_LIST_HEAD(keylist); + curr != NULL; + curr = ISC_LIST_NEXT(curr, link)) + { + uint32_t suc; + + if (dst_key_alg(key->key) != + dst_key_alg(curr->key) || + !iszsk(curr) || + dst_key_id(curr->key) != pre) + { + continue; + } + ret = dst_key_getnum(curr->key, + DST_NUM_SUCCESSOR, + &suc); + if (ret != ISC_R_SUCCESS || + dst_key_id(key->key) != suc) + { + continue; + } + + /* + * curr is the predecessor we were + * looking for. Check if this key + * signs this RRset. + */ + if (nowsignedby[curr->index]) { + have_pre_sig = true; + } + } + } + + /* + * If we have a signature of a predecessor key, + * skip signing with this key. + */ + if (!have_pre_sig) { + signwithkey(name, set, key->key, ttl, add, + "signing with dnskey"); + } + } + } + + isc_mem_put(mctx, wassignedby, arraysize * sizeof(bool)); + isc_mem_put(mctx, nowsignedby, arraysize * sizeof(bool)); +} + +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_free(hashlist_t *l) { + if (l->hashbuf) { + free(l->hashbuf); + l->hashbuf = NULL; + l->entries = 0; + l->length = 0; + l->size = 0; + } +} + +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); + if (l->hashbuf == NULL) { + fatal("unable to grow hashlist: out of memory"); + } + } + memset(l->hashbuf + l->entries * l->length, 0, l->length); + memmove(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_len, bool 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, (int)salt_len, + 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) { + INSIST(l->hashbuf != NULL || l->length == 0); + if (l->length > 0) { + qsort(l->hashbuf, l->entries, l->length, hashlist_comp); + } +} + +static bool +hashlist_hasdup(hashlist_t *l) { + unsigned char *current; + unsigned char *next = l->hashbuf; + size_t entries = l->entries; + + /* + * Skip initial speculative wild card hashes. + */ + 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 (isc_safe_memequal(current, next, l->length - 1)) { + return (true); + } + current = next; + } + return (false); +} + +static const unsigned char * +hashlist_findnext(const hashlist_t *l, + const unsigned char hash[NSEC3_MAX_HASH_LENGTH]) { + size_t 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-- > 1U); + INSIST(entries != 0U); + return (next); +} + +static bool +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 (true); + } else { + return (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_len) { + dns_fixedname_t fixed; + dns_name_t *wild; + dns_dbnode_t *node = NULL; + isc_result_t result; + char namestr[DNS_NAME_FORMATSIZE]; + + wild = dns_fixedname_initname(&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, 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_len, + true); +} + +static void +opendb(const char *prefix, dns_name_t *name, dns_rdataclass_t rdclass, + dns_db_t **dbp) { + char filename[PATH_MAX]; + isc_buffer_t b; + isc_result_t result; + + isc_buffer_init(&b, filename, sizeof(filename)); + if (dsdir != NULL) { + /* allow room for a trailing slash */ + if (strlen(dsdir) >= isc_buffer_availablelength(&b)) { + fatal("path '%s' is too long", dsdir); + } + isc_buffer_putstr(&b, dsdir); + if (dsdir[strlen(dsdir) - 1] != '/') { + isc_buffer_putstr(&b, "/"); + } + } + if (strlen(prefix) > isc_buffer_availablelength(&b)) { + fatal("path '%s' is too long", dsdir); + } + isc_buffer_putstr(&b, prefix); + result = dns_name_tofilenametext(name, 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, inputformat, DNS_MASTER_HINT); + if (result != ISC_R_SUCCESS && result != DNS_R_SEENINCLUDE) { + dns_db_detach(dbp); + } +} + +/*% + * Load the DS set for a child zone, if a dsset-* file can be found. + * If not, try to find a keyset-* file from an earlier version of + * dnssec-signzone, and build DS records from that. + */ +static isc_result_t +loadds(dns_name_t *name, 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("dsset-", name, gclass, &db); + if (db != NULL) { + result = dns_db_findnode(db, name, false, &node); + if (result == ISC_R_SUCCESS) { + dns_rdataset_init(dsset); + result = dns_db_findrdataset(db, node, NULL, + dns_rdatatype_ds, 0, 0, + dsset, NULL); + dns_db_detachnode(db, &node); + if (result == ISC_R_SUCCESS) { + vbprintf(2, "found DS records\n"); + dsset->ttl = ttl; + dns_db_detach(&db); + return (result); + } + } + dns_db_detach(&db); + } + + /* No DS records found; try again, looking for DNSKEY records */ + opendb("keyset-", name, gclass, &db); + if (db == NULL) { + return (ISC_R_NOTFOUND); + } + + result = dns_db_findnode(db, name, false, &node); + if (result != ISC_R_SUCCESS) { + dns_db_detach(&db); + return (result); + } + + 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_SHA256, + dsbuf, &ds); + check_result(result, "dns_ds_buildrdata"); + + result = dns_difftuple_create(mctx, DNS_DIFFOP_ADDRESIGN, 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, 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 bool +secure(dns_name_t *name, dns_dbnode_t *node) { + dns_rdataset_t dsset; + isc_result_t result; + + if (dns_name_equal(name, gorigin)) { + return (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 (result == ISC_R_SUCCESS); +} + +static bool +is_delegation(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin, + dns_name_t *name, dns_dbnode_t *node, uint32_t *ttlp) { + dns_rdataset_t nsset; + isc_result_t result; + + if (dns_name_equal(name, origin)) { + return (false); + } + + dns_rdataset_init(&nsset); + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_ns, 0, 0, + &nsset, NULL); + if (dns_rdataset_isassociated(&nsset)) { + if (ttlp != NULL) { + *ttlp = nsset.ttl; + } + dns_rdataset_disassociate(&nsset); + } + + return ((result == ISC_R_SUCCESS)); +} + +/*% + * Return true if version 'ver' of database 'db' contains a DNAME RRset at + * 'node'; return false otherwise. + */ +static bool +has_dname(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node) { + dns_rdataset_t dnameset; + isc_result_t result; + + dns_rdataset_init(&dnameset); + result = dns_db_findrdataset(db, node, ver, dns_rdatatype_dname, 0, 0, + &dnameset, NULL); + if (dns_rdataset_isassociated(&dnameset)) { + dns_rdataset_disassociate(&dnameset); + } + + return ((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; + bool isdelegation = 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 (is_delegation(gdb, gversion, gorigin, name, node, NULL)) { + isdelegation = 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, 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); +} + +/* + * See if the node contains any non RRSIG/NSEC records and report to + * caller. Clean out extraneous RRSIG records for node. + */ +static bool +active_node(dns_dbnode_t *node) { + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdatasetiter_t *rdsiter2 = NULL; + bool active = false; + isc_result_t result; + dns_rdataset_t rdataset; + dns_rdatatype_t type; + dns_rdatatype_t covers; + bool found; + + dns_rdataset_init(&rdataset); + result = dns_db_allrdatasets(gdb, node, gversion, 0, 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 = 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, 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); + /* + * Delete the NSEC chain if we are signing with + * NSEC3. + */ + if (nsec_datatype == dns_rdatatype_nsec3 && + (type == dns_rdatatype_nsec || + covers == dns_rdatatype_nsec)) + { + result = dns_db_deleterdataset( + gdb, node, gversion, type, covers); + check_result(result, "dns_db_deleterdataset(" + "nsec/rrsig)"); + continue; + } + if (type != dns_rdatatype_rrsig) { + continue; + } + found = 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 = 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); + } + dns_rdatasetiter_destroy(&rdsiter); + + return (active); +} + +/*% + * Extracts the minimum TTL from the SOA record, and the SOA record's TTL. + */ +static void +get_soa_ttls(void) { + dns_rdataset_t soaset; + dns_fixedname_t fname; + dns_name_t *name; + isc_result_t result; + dns_rdata_t rdata = DNS_RDATA_INIT; + + name = dns_fixedname_initname(&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); + soa_ttl = soaset.ttl; + zone_soa_min_ttl = ISC_MIN(dns_soa_getminimum(&rdata), soa_ttl); + if (set_maxttl) { + zone_soa_min_ttl = ISC_MIN(zone_soa_min_ttl, maxttl); + soa_ttl = ISC_MIN(soa_ttl, maxttl); + } + dns_rdataset_disassociate(&soaset); +} + +/*% + * Increment (or set if nonzero) the SOA serial + */ +static isc_result_t +setsoaserial(uint32_t serial, dns_updatemethod_t method) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + uint32_t old_serial, new_serial = 0; + dns_updatemethod_t used = dns_updatemethod_none; + + 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 (method == dns_updatemethod_date || + method == dns_updatemethod_unixtime) + { + new_serial = dns_update_soaserial(old_serial, method, &used); + } else if (serial != 0 || method == dns_updatemethod_none) { + /* Set SOA serial to the value provided. */ + new_serial = serial; + used = method; + } else { + new_serial = dns_update_soaserial(old_serial, method, &used); + } + + if (method != used) { + fprintf(stderr, + "%s: warning: Serial number would not advance, " + "using increment method instead\n", + program); + } + + /* 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 *dbversion, 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, dbversion, 0, 0, &rdsiter); + check_result(result, "dns_db_allrdatasets"); + result = dns_rdatasetiter_first(rdsiter); + while (result == ISC_R_SUCCESS) { + bool destroy = false; + dns_rdatatype_t covers = 0; + dns_rdatasetiter_current(rdsiter, &set); + if (set.type == dns_rdatatype_rrsig) { + covers = set.covers; + destroy = true; + } + dns_rdataset_disassociate(&set); + result = dns_rdatasetiter_next(rdsiter); + if (destroy) { + dresult = dns_db_deleterdataset(db, node, dbversion, + 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; + + name = dns_fixedname_initname(&fixed); + result = dns_dbiterator_seek(gdbiter, gorigin); + check_result(result, "dns_dbiterator_seek()"); + result = dns_dbiterator_current(gdbiter, &node, name); + check_dns_dbiterator_current(result); + 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) { + atomic_store(&finished, 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; + bool 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 (atomic_load(&shuttingdown)) { + return; + } + + LOCK(&namelock); + if (atomic_load(&finished)) { + ended++; + if (ended == ntasks) { + isc_task_detach(&task); + isc_app_shutdown(); + } + goto unlock; + } + + fname = isc_mem_get(mctx, sizeof(dns_fixedname_t)); + name = dns_fixedname_initname(fname); + node = NULL; + found = false; + while (!found) { + result = dns_dbiterator_current(gdbiter, &node, name); + check_dns_dbiterator_current(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 = true; + } else if (nsec_datatype == dns_rdatatype_nsec3) { + if (dns_name_issubdomain(name, gorigin) && + (zonecut == NULL || + !dns_name_issubdomain(name, zonecut))) + { + if (is_delegation(gdb, gversion, gorigin, name, + node, NULL)) + { + zonecut = savezonecut(&fzonecut, name); + if (!OPTOUT(nsec3flags) || + secure(name, node)) + { + found = true; + } + } else if (has_dname(gdb, gversion, node)) { + zonecut = savezonecut(&fzonecut, name); + found = true; + } else { + found = true; + } + } + } + + if (!found) { + dumpnode(name, node); + dns_db_detachnode(gdb, &node); + } + + next: + result = dns_dbiterator_next(gdbiter); + if (result == ISC_R_NOMORE) { + atomic_store(&finished, 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)); + + 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)); + 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, 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); + } +} + +/* + * Remove records of the given type and their signatures. + */ +static void +remove_records(dns_dbnode_t *node, dns_rdatatype_t which, bool checknsec) { + isc_result_t result; + dns_rdatatype_t type, covers; + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdataset_t rdataset; + + dns_rdataset_init(&rdataset); + + /* + * Delete any records of the given type at the apex. + */ + result = dns_db_allrdatasets(gdb, node, gversion, 0, 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, &rdataset); + type = rdataset.type; + covers = rdataset.covers; + dns_rdataset_disassociate(&rdataset); + if (type == which || covers == which) { + if (which == dns_rdatatype_nsec && checknsec && + !update_chain) + { + fatal("Zone contains NSEC records. Use -u " + "to update to NSEC3."); + } + if (which == dns_rdatatype_nsec3param && checknsec && + !update_chain) + { + fatal("Zone contains NSEC3 chains. Use -u " + "to update to NSEC."); + } + result = dns_db_deleterdataset(gdb, node, gversion, + type, covers); + check_result(result, "dns_db_deleterdataset()"); + } + } + dns_rdatasetiter_destroy(&rdsiter); +} + +/* + * Remove signatures covering the given type. If type == 0, + * then remove all signatures, unless this is a delegation, in + * which case remove all signatures except for DS or nsec_datatype + */ +static void +remove_sigs(dns_dbnode_t *node, bool delegation, dns_rdatatype_t which) { + isc_result_t result; + dns_rdatatype_t type, covers; + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdataset_t rdataset; + + dns_rdataset_init(&rdataset); + result = dns_db_allrdatasets(gdb, node, gversion, 0, 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, &rdataset); + type = rdataset.type; + covers = rdataset.covers; + dns_rdataset_disassociate(&rdataset); + + if (type != dns_rdatatype_rrsig) { + continue; + } + + if (which == 0 && delegation && + (dns_rdatatype_atparent(covers) || + (nsec_datatype == dns_rdatatype_nsec && + covers == nsec_datatype))) + { + continue; + } + + if (which != 0 && covers != which) { + continue; + } + + result = dns_db_deleterdataset(gdb, node, gversion, type, + covers); + check_result(result, "dns_db_deleterdataset()"); + } + dns_rdatasetiter_destroy(&rdsiter); +} + +/*% + * Generate NSEC records for the zone and remove NSEC3/NSEC3PARAM records. + */ +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; + dns_rdataset_t rdataset; + dns_rdatasetiter_t *rdsiter = NULL; + dns_rdatatype_t type, covers; + bool done = false; + isc_result_t result; + uint32_t nsttl = 0; + + dns_rdataset_init(&rdataset); + name = dns_fixedname_initname(&fname); + nextname = dns_fixedname_initname(&fnextname); + zonecut = NULL; + + /* + * Remove any NSEC3 chains. + */ + 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)) + { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + result = dns_db_allrdatasets(gdb, node, gversion, 0, 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, &rdataset); + type = rdataset.type; + covers = rdataset.covers; + dns_rdataset_disassociate(&rdataset); + result = dns_db_deleterdataset(gdb, node, gversion, + type, covers); + check_result(result, "dns_db_deleterdataset(nsec3param/" + "rrsig)"); + } + dns_rdatasetiter_destroy(&rdsiter); + dns_db_detachnode(gdb, &node); + } + dns_dbiterator_destroy(&dbiter); + + 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) { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + /* + * Skip out-of-zone records. + */ + if (!dns_name_issubdomain(name, gorigin)) { + result = dns_dbiterator_next(dbiter); + if (result == ISC_R_NOMORE) { + done = true; + } else { + check_result(result, "dns_dbiterator_next()"); + } + dns_db_detachnode(gdb, &node); + continue; + } + + if (dns_name_equal(name, gorigin)) { + remove_records(node, dns_rdatatype_nsec3param, true); + /* Clean old rrsigs at apex. */ + (void)active_node(node); + } + + if (is_delegation(gdb, gversion, gorigin, name, node, &nsttl)) { + zonecut = savezonecut(&fzonecut, name); + remove_sigs(node, true, 0); + if (generateds) { + add_ds(name, node, nsttl); + } + } else if (has_dname(gdb, gversion, node)) { + zonecut = savezonecut(&fzonecut, name); + } + + result = dns_dbiterator_next(dbiter); + nextnode = NULL; + while (result == ISC_R_SUCCESS) { + bool active = false; + result = dns_dbiterator_current(dbiter, &nextnode, + nextname); + check_dns_dbiterator_current(result); + 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))) + { + remove_sigs(nextnode, false, 0); + remove_records(nextnode, dns_rdatatype_nsec, + false); + 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 = true; + } else if (result != ISC_R_SUCCESS) { + fatal("iterating through the database failed: %s", + isc_result_totext(result)); + } + dns_dbiterator_pause(dbiter); + result = dns_nsec_build(gdb, gversion, node, nextname, + zone_soa_min_ttl); + check_result(result, "dns_nsec_build()"); + dns_db_detachnode(gdb, &node); + } + + dns_dbiterator_destroy(&dbiter); +} + +static void +addnsec3param(const unsigned char *salt, size_t salt_len, + dns_iterations_t 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 = (unsigned char)salt_len; + DE_CONST(salt, nsec3param.salt); + + isc_buffer_init(&b, nsec3parambuf, sizeof(nsec3parambuf)); + result = dns_rdata_fromstruct(&rdata, gclass, dns_rdatatype_nsec3param, + &nsec3param, &b); + check_result(result, "dns_rdata_fromstruct()"); + dns_rdatalist_init(&rdatalist); + rdatalist.rdclass = rdata.rdclass; + rdatalist.type = rdata.type; + 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, true, &node); + check_result(result, "dns_db_findnode(gorigin)"); + + /* + * Delete any current NSEC3PARAM records. + */ + result = dns_db_deleterdataset(gdb, node, gversion, + dns_rdatatype_nsec3param, 0); + if (result == DNS_R_UNCHANGED) { + result = ISC_R_SUCCESS; + } + check_result(result, "dddnsec3param: dns_db_deleterdataset()"); + + 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_len, 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_len; + + 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_len, name, gorigin, + dns_hash_sha1, iterations, salt, salt_len); + 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_len, nexthash, ISC_SHA1_DIGESTLENGTH, + nsec3buffer, &rdata); + check_result(result, "addnsec3: dns_nsec3_buildrdata()"); + dns_rdatalist_init(&rdatalist); + rdatalist.rdclass = rdata.rdclass; + rdatalist.type = rdata.type; + rdatalist.ttl = ttl; + 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), 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_len, + 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; + bool delete_rrsigs = false; + isc_buffer_t target; + isc_result_t result; + unsigned char hash[NSEC3_MAX_HASH_LENGTH + 1]; + bool exists; + + /* + * 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; + + exists = hashlist_exists(hashlist, hash); + + /* + * 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 NSEC3 records which are not part of the current + * NSEC3 chain. + */ + for (result = dns_rdataset_first(&rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(&rdataset)) + { + dns_rdata_init(&rdata); + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + check_result(result, "dns_rdata_tostruct"); + if (exists && nsec3.hash == hashalg && + nsec3.iterations == iterations && + nsec3.salt_length == salt_len && + isc_safe_memequal(nsec3.salt, salt, salt_len)) + { + continue; + } + dns_rdatalist_init(&rdatalist); + rdatalist.rdclass = rdata.rdclass; + rdatalist.type = rdata.type; + if (set_maxttl) { + rdatalist.ttl = ISC_MIN(rdataset.ttl, maxttl); + } + 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_NXRRSET) { + check_result(result, "dns_db_subtractrdataset(NSEC3)"); + } + delete_rrsigs = 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))"); + } +} + +static void +rrset_cleanup(dns_name_t *name, dns_rdataset_t *rdataset, dns_diff_t *add, + dns_diff_t *del) { + isc_result_t result; + unsigned int count1 = 0; + dns_rdataset_t tmprdataset; + char namestr[DNS_NAME_FORMATSIZE]; + char typestr[DNS_RDATATYPE_FORMATSIZE]; + + dns_name_format(name, namestr, sizeof(namestr)); + dns_rdatatype_format(rdataset->type, typestr, sizeof(typestr)); + + dns_rdataset_init(&tmprdataset); + for (result = dns_rdataset_first(rdataset); result == ISC_R_SUCCESS; + result = dns_rdataset_next(rdataset)) + { + dns_rdata_t rdata1 = DNS_RDATA_INIT; + unsigned int count2 = 0; + + count1++; + dns_rdataset_current(rdataset, &rdata1); + dns_rdataset_clone(rdataset, &tmprdataset); + for (result = dns_rdataset_first(&tmprdataset); + result == ISC_R_SUCCESS; + result = dns_rdataset_next(&tmprdataset)) + { + dns_rdata_t rdata2 = DNS_RDATA_INIT; + dns_difftuple_t *tuple = NULL; + count2++; + dns_rdataset_current(&tmprdataset, &rdata2); + if (count1 < count2 && + dns_rdata_casecompare(&rdata1, &rdata2) == 0) + { + vbprintf(2, "removing duplicate at %s/%s\n", + namestr, typestr); + result = dns_difftuple_create( + mctx, DNS_DIFFOP_DELRESIGN, name, + rdataset->ttl, &rdata2, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(del, &tuple); + } else if (set_maxttl && rdataset->ttl > maxttl) { + vbprintf(2, + "reducing ttl of %s/%s " + "from %d to %d\n", + namestr, typestr, rdataset->ttl, + maxttl); + result = dns_difftuple_create( + mctx, DNS_DIFFOP_DELRESIGN, name, + rdataset->ttl, &rdata2, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(del, &tuple); + tuple = NULL; + result = dns_difftuple_create( + mctx, DNS_DIFFOP_ADDRESIGN, name, + maxttl, &rdata2, &tuple); + check_result(result, "dns_difftuple_create"); + dns_diff_append(add, &tuple); + } + } + dns_rdataset_disassociate(&tmprdataset); + } +} + +static void +cleanup_zone(void) { + isc_result_t result; + dns_dbiterator_t *dbiter = NULL; + dns_rdatasetiter_t *rdsiter = NULL; + dns_diff_t add, del; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_fixedname_t fname; + dns_name_t *name; + + dns_diff_init(mctx, &add); + dns_diff_init(mctx, &del); + name = dns_fixedname_initname(&fname); + dns_rdataset_init(&rdataset); + + result = dns_db_createiterator(gdb, 0, &dbiter); + check_result(result, "dns_db_createiterator()"); + + for (result = dns_dbiterator_first(dbiter); result == ISC_R_SUCCESS; + result = dns_dbiterator_next(dbiter)) + { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + result = dns_db_allrdatasets(gdb, node, gversion, 0, 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, &rdataset); + rrset_cleanup(name, &rdataset, &add, &del); + dns_rdataset_disassociate(&rdataset); + } + if (result != ISC_R_NOMORE) { + fatal("rdatasets iteration failed."); + } + dns_rdatasetiter_destroy(&rdsiter); + dns_db_detachnode(gdb, &node); + } + if (result != ISC_R_NOMORE) { + fatal("zone iteration failed."); + } + + result = dns_diff_applysilently(&del, gdb, gversion); + check_result(result, "dns_diff_applysilently"); + + result = dns_diff_applysilently(&add, gdb, gversion); + check_result(result, "dns_diff_applysilently"); + + dns_diff_clear(&del); + dns_diff_clear(&add); + dns_dbiterator_destroy(&dbiter); +} + +/* + * Generate NSEC3 records for the zone. + */ +static void +nsec3ify(unsigned int hashalg, dns_iterations_t iterations, + const unsigned char *salt, size_t salt_len, 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; + dns_rdataset_t rdataset; + int order; + bool active; + bool done = false; + isc_result_t result; + uint32_t nsttl = 0; + unsigned int count, nlabels; + + dns_rdataset_init(&rdataset); + name = dns_fixedname_initname(&fname); + nextname = dns_fixedname_initname(&fnextname); + 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) { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + /* + * Skip out-of-zone records. + */ + if (!dns_name_issubdomain(name, gorigin)) { + result = dns_dbiterator_next(dbiter); + if (result == ISC_R_NOMORE) { + done = true; + } else { + check_result(result, "dns_dbiterator_next()"); + } + dns_db_detachnode(gdb, &node); + continue; + } + + if (dns_name_equal(name, gorigin)) { + remove_records(node, dns_rdatatype_nsec, true); + /* Clean old rrsigs at apex. */ + (void)active_node(node); + } + + if (has_dname(gdb, gversion, node)) { + zonecut = savezonecut(&fzonecut, name); + } + + result = dns_dbiterator_next(dbiter); + nextnode = NULL; + while (result == ISC_R_SUCCESS) { + result = dns_dbiterator_current(dbiter, &nextnode, + nextname); + check_dns_dbiterator_current(result); + 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))) + { + remove_sigs(nextnode, false, 0); + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + if (is_delegation(gdb, gversion, gorigin, nextname, + nextnode, &nsttl)) + { + zonecut = savezonecut(&fzonecut, nextname); + remove_sigs(nextnode, true, 0); + if (generateds) { + add_ds(nextname, nextnode, nsttl); + } + if (OPTOUT(nsec3flags) && + !secure(nextname, nextnode)) + { + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + } else if (has_dname(gdb, gversion, nextnode)) { + zonecut = savezonecut(&fzonecut, nextname); + } + dns_db_detachnode(gdb, &nextnode); + break; + } + if (result == ISC_R_NOMORE) { + dns_name_copynf(gorigin, nextname); + done = 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_len, false); + dns_db_detachnode(gdb, &node); + /* + * Add hashes 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_len); + 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_len, + false); + addnowildcardhash(hashlist, nextname, hashalg, + iterations, salt, salt_len); + } + } + 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 = false; + + addnsec3param(salt, salt_len, iterations); + + /* + * Clean out NSEC3 records which don't match this chain. + */ + 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)) + { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + nsec3clean(name, node, hashalg, iterations, salt, salt_len, + hashlist); + dns_db_detachnode(gdb, &node); + } + dns_dbiterator_destroy(&dbiter); + + /* + * Generate / complete the new chain. + */ + 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) { + result = dns_dbiterator_current(dbiter, &node, name); + check_dns_dbiterator_current(result); + /* + * Skip out-of-zone records. + */ + if (!dns_name_issubdomain(name, gorigin)) { + result = dns_dbiterator_next(dbiter); + if (result == ISC_R_NOMORE) { + done = true; + } else { + check_result(result, "dns_dbiterator_next()"); + } + dns_db_detachnode(gdb, &node); + continue; + } + + if (has_dname(gdb, gversion, node)) { + zonecut = savezonecut(&fzonecut, name); + } + + result = dns_dbiterator_next(dbiter); + nextnode = NULL; + while (result == ISC_R_SUCCESS) { + result = dns_dbiterator_current(dbiter, &nextnode, + nextname); + check_dns_dbiterator_current(result); + 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 (is_delegation(gdb, gversion, gorigin, nextname, + nextnode, NULL)) + { + zonecut = savezonecut(&fzonecut, nextname); + if (OPTOUT(nsec3flags) && + !secure(nextname, nextnode)) + { + dns_db_detachnode(gdb, &nextnode); + result = dns_dbiterator_next(dbiter); + continue; + } + } else if (has_dname(gdb, gversion, nextnode)) { + zonecut = savezonecut(&fzonecut, nextname); + } + dns_db_detachnode(gdb, &nextnode); + break; + } + if (result == ISC_R_NOMORE) { + dns_name_copynf(gorigin, nextname); + done = 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_len, iterations, hashlist, + zone_soa_min_ttl); + 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_len, iterations, + hashlist, zone_soa_min_ttl); + } + } + 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); + + name = dns_fixedname_initname(&fname); + result = dns_name_fromtext(name, &b, dns_rootname, 0, 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_load(*db, file, inputformat, 0); + 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(bool preserve_keys, bool load_public) { + dns_dbnode_t *node; + dns_dbversion_t *currentversion = NULL; + isc_result_t result; + dns_rdataset_t rdataset, keysigs, soasigs; + + node = NULL; + result = dns_db_findnode(gdb, gorigin, false, &node); + if (result != ISC_R_SUCCESS) { + fatal("failed to find the zone's origin: %s", + isc_result_totext(result)); + } + + dns_db_currentversion(gdb, ¤tversion); + + dns_rdataset_init(&rdataset); + dns_rdataset_init(&soasigs); + dns_rdataset_init(&keysigs); + + /* Make note of the keys which signed the SOA, if any */ + result = dns_db_findrdataset(gdb, node, currentversion, + dns_rdatatype_soa, 0, 0, &rdataset, + &soasigs); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + /* Preserve the TTL of the DNSKEY RRset, if any */ + dns_rdataset_disassociate(&rdataset); + result = dns_db_findrdataset(gdb, node, currentversion, + dns_rdatatype_dnskey, 0, 0, &rdataset, + &keysigs); + + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + if (set_keyttl && keyttl != rdataset.ttl) { + fprintf(stderr, + "User-specified TTL %u conflicts " + "with existing DNSKEY RRset TTL.\n", + keyttl); + fprintf(stderr, + "Imported keys will use the RRSet " + "TTL %u instead.\n", + rdataset.ttl); + } + keyttl = rdataset.ttl; + + /* Load keys corresponding to the existing DNSKEY RRset. */ + result = dns_dnssec_keylistfromrdataset( + gorigin, directory, mctx, &rdataset, &keysigs, &soasigs, + preserve_keys, load_public, &keylist); + if (result != ISC_R_SUCCESS) { + fatal("failed to load the zone keys: %s", + isc_result_totext(result)); + } + +cleanup: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (dns_rdataset_isassociated(&keysigs)) { + dns_rdataset_disassociate(&keysigs); + } + if (dns_rdataset_isassociated(&soasigs)) { + dns_rdataset_disassociate(&soasigs); + } + dns_db_detachnode(gdb, &node); + dns_db_closeversion(gdb, ¤tversion, false); +} + +static void +loadexplicitkeys(char *keyfiles[], int n, bool setksk) { + isc_result_t result; + int i; + + for (i = 0; i < n; i++) { + dns_dnsseckey_t *key = NULL; + dst_key_t *newkey = NULL; + + result = dst_key_fromnamedfile( + keyfiles[i], directory, + DST_TYPE_PUBLIC | DST_TYPE_PRIVATE, mctx, &newkey); + if (result != ISC_R_SUCCESS) { + fatal("cannot load dnskey %s: %s", keyfiles[i], + isc_result_totext(result)); + } + + if (!dns_name_equal(gorigin, dst_key_name(newkey))) { + fatal("key %s not at origin\n", keyfiles[i]); + } + + if (!dst_key_isprivate(newkey)) { + fatal("cannot sign zone with non-private dnskey %s", + keyfiles[i]); + } + + /* Skip any duplicates */ + for (key = ISC_LIST_HEAD(keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (dst_key_id(key->key) == dst_key_id(newkey) && + dst_key_alg(key->key) == dst_key_alg(newkey)) + { + break; + } + } + + if (key == NULL) { + /* We haven't seen this key before */ + dns_dnsseckey_create(mctx, &newkey, &key); + ISC_LIST_APPEND(keylist, key, link); + key->source = dns_keysource_user; + } else { + dst_key_free(&key->key); + key->key = newkey; + } + + key->force_publish = true; + key->force_sign = true; + + if (setksk) { + key->ksk = true; + } + } +} + +static void +report(const char *format, ...) { + if (!quiet) { + FILE *out = output_stdout ? stderr : stdout; + char buf[4096]; + va_list args; + + va_start(args, format); + vsnprintf(buf, sizeof(buf), format, args); + va_end(args); + fprintf(out, "%s\n", buf); + } +} + +static void +clear_keylist(dns_dnsseckeylist_t *list) { + dns_dnsseckey_t *key; + while (!ISC_LIST_EMPTY(*list)) { + key = ISC_LIST_HEAD(*list); + ISC_LIST_UNLINK(*list, key, link); + dns_dnsseckey_destroy(mctx, &key); + } +} + +static void +build_final_keylist(void) { + isc_result_t result; + dns_dbnode_t *node = NULL; + dns_dbversion_t *ver = NULL; + dns_diff_t diff; + dns_dnsseckeylist_t rmkeys, matchkeys; + char name[DNS_NAME_FORMATSIZE]; + dns_rdataset_t cdsset, cdnskeyset, soaset; + + ISC_LIST_INIT(rmkeys); + ISC_LIST_INIT(matchkeys); + + dns_rdataset_init(&soaset); + dns_rdataset_init(&cdsset); + dns_rdataset_init(&cdnskeyset); + + /* + * Find keys that match this zone in the key repository. + */ + result = dns_dnssec_findmatchingkeys(gorigin, directory, now, mctx, + &matchkeys); + if (result == ISC_R_NOTFOUND) { + result = ISC_R_SUCCESS; + } + check_result(result, "dns_dnssec_findmatchingkeys"); + + result = dns_db_newversion(gdb, &ver); + check_result(result, "dns_db_newversion"); + + result = dns_db_getoriginnode(gdb, &node); + check_result(result, "dns_db_getoriginnode"); + + /* Get the CDS rdataset */ + result = dns_db_findrdataset(gdb, node, ver, dns_rdatatype_cds, + dns_rdatatype_none, 0, &cdsset, NULL); + if (result != ISC_R_SUCCESS && dns_rdataset_isassociated(&cdsset)) { + dns_rdataset_disassociate(&cdsset); + } + + /* Get the CDNSKEY rdataset */ + result = dns_db_findrdataset(gdb, node, ver, dns_rdatatype_cdnskey, + dns_rdatatype_none, 0, &cdnskeyset, NULL); + if (result != ISC_R_SUCCESS && dns_rdataset_isassociated(&cdnskeyset)) { + dns_rdataset_disassociate(&cdnskeyset); + } + + dns_diff_init(mctx, &diff); + + /* + * Update keylist with information from from the key repository. + */ + dns_dnssec_updatekeys(&keylist, &matchkeys, NULL, gorigin, keyttl, + &diff, mctx, report); + + /* + * Update keylist with sync records. + */ + dns_dnssec_syncupdate(&keylist, &rmkeys, &cdsset, &cdnskeyset, now, + keyttl, &diff, mctx); + + dns_name_format(gorigin, name, sizeof(name)); + + result = dns_diff_applysilently(&diff, gdb, ver); + if (result != ISC_R_SUCCESS) { + fatal("failed to update DNSKEY RRset at node '%s': %s", name, + isc_result_totext(result)); + } + + dns_db_detachnode(gdb, &node); + dns_db_closeversion(gdb, &ver, true); + + dns_diff_clear(&diff); + + if (dns_rdataset_isassociated(&cdsset)) { + dns_rdataset_disassociate(&cdsset); + } + if (dns_rdataset_isassociated(&cdnskeyset)) { + dns_rdataset_disassociate(&cdnskeyset); + } + + clear_keylist(&rmkeys); + clear_keylist(&matchkeys); +} + +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_dnskey_t dnskey; + bool have_non_ksk = false; + + dns_db_currentversion(db, ¤tversion); + + result = dns_db_findnode(db, gorigin, 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, &dnskey, NULL); + check_result(result, "dns_rdata_tostruct"); + if ((dnskey.flags & DNS_KEYFLAG_KSK) == 0) { + have_non_ksk = true; + result = ISC_R_NOMORE; + } else { + result = dns_rdataset_next(&rdataset); + } + dns_rdata_freestruct(&dnskey); + } + dns_rdataset_disassociate(&rdataset); + dns_db_detachnode(db, &node); + dns_db_closeversion(db, ¤tversion, false); + if (!have_non_ksk && !ignore_kskflag) { + if (disable_zone_check) { + fprintf(stderr, + "%s: warning: No non-KSK DNSKEY found; " + "supply a ZSK or use '-z'.\n", + program); + } else { + fatal("No non-KSK DNSKEY found; " + "supply a ZSK or use '-z'."); + } + } +} + +static void +set_nsec3params(bool update, bool set_salt, bool set_optout, bool set_iter) { + isc_result_t result; + dns_dbversion_t *ver = NULL; + dns_dbnode_t *node = NULL; + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + dns_rdata_nsec3_t nsec3; + dns_fixedname_t fname; + dns_name_t *hashname; + unsigned char orig_salt[255]; + size_t orig_saltlen; + dns_hash_t orig_hash; + uint16_t orig_iter; + + dns_db_currentversion(gdb, &ver); + dns_rdataset_init(&rdataset); + + orig_saltlen = sizeof(orig_salt); + result = dns_db_getnsec3parameters(gdb, ver, &orig_hash, NULL, + &orig_iter, orig_salt, + &orig_saltlen); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + nsec_datatype = dns_rdatatype_nsec3; + + if (!update && set_salt) { + if (salt_length != orig_saltlen || + !isc_safe_memequal(saltbuf, orig_salt, salt_length)) + { + fatal("An NSEC3 chain exists with a different salt. " + "Use -u to update it."); + } + } else if (!set_salt) { + salt_length = orig_saltlen; + memmove(saltbuf, orig_salt, orig_saltlen); + gsalt = saltbuf; + } + + if (!update && set_iter) { + if (nsec3iter != orig_iter) { + fatal("An NSEC3 chain exists with different " + "iterations. Use -u to update it."); + } + } else if (!set_iter) { + nsec3iter = orig_iter; + } + + /* + * Find an NSEC3 record to get the current OPTOUT value. + * (This assumes all NSEC3 records agree.) + */ + + hashname = dns_fixedname_initname(&fname); + result = dns_nsec3_hashname(&fname, NULL, NULL, gorigin, gorigin, + dns_hash_sha1, orig_iter, orig_salt, + orig_saltlen); + check_result(result, "dns_nsec3_hashname"); + + result = dns_db_findnsec3node(gdb, hashname, false, &node); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_db_findrdataset(gdb, node, ver, dns_rdatatype_nsec3, 0, 0, + &rdataset, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } + + result = dns_rdataset_first(&rdataset); + check_result(result, "dns_rdataset_first"); + dns_rdataset_current(&rdataset, &rdata); + result = dns_rdata_tostruct(&rdata, &nsec3, NULL); + check_result(result, "dns_rdata_tostruct"); + + if (!update && set_optout) { + if (nsec3flags != nsec3.flags) { + fatal("An NSEC3 chain exists with%s OPTOUT. " + "Use -u -%s to %s it.", + OPTOUT(nsec3.flags) ? "" : "out", + OPTOUT(nsec3.flags) ? "AA" : "A", + OPTOUT(nsec3.flags) ? "clear" : "set"); + } + } else if (!set_optout) { + nsec3flags = nsec3.flags; + } + + dns_rdata_freestruct(&nsec3); + +cleanup: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (node != NULL) { + dns_db_detachnode(gdb, &node); + } + dns_db_closeversion(gdb, &ver, false); +} + +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 *dbversion = NULL; + dns_diff_t diff; + dns_difftuple_t *tuple = NULL; + dns_name_t *name; + dns_rdata_t rdata, ds; + bool have_ksk = false; + bool have_non_ksk = false; + isc_buffer_t b; + isc_buffer_t namebuf; + isc_region_t r; + isc_result_t result; + dns_dnsseckey_t *key, *curr; + 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, false, &namebuf); + check_result(result, "dns_name_tofilenametext"); + isc_buffer_putuint8(&namebuf, 0); + filenamelen = strlen(prefix) + strlen(namestr) + 1; + if (dsdir != NULL) { + filenamelen += strlen(dsdir) + 1; + } + filename = isc_mem_get(mctx, filenamelen); + if (dsdir != NULL) { + snprintf(filename, filenamelen, "%s/", dsdir); + } else { + filename[0] = 0; + } + strlcat(filename, prefix, filenamelen); + strlcat(filename, namestr, filenamelen); + + dns_diff_init(mctx, &diff); + + name = gorigin; + + for (key = ISC_LIST_HEAD(keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + if (REVOKE(key->key)) { + continue; + } + if (isksk(key)) { + have_ksk = true; + have_non_ksk = false; + } else { + have_ksk = false; + have_non_ksk = true; + } + for (curr = ISC_LIST_HEAD(keylist); curr != NULL; + curr = ISC_LIST_NEXT(curr, link)) + { + if (dst_key_alg(key->key) != dst_key_alg(curr->key)) { + continue; + } + if (REVOKE(curr->key)) { + continue; + } + if (isksk(curr)) { + have_ksk = true; + } else { + have_non_ksk = true; + } + } + if (have_ksk && have_non_ksk && !isksk(key)) { + 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_SHA256, dsbuf, + &ds); + check_result(result, "dns_ds_buildrdata"); + result = dns_difftuple_create(mctx, + DNS_DIFFOP_ADDRESIGN, + name, 0, &ds, &tuple); + } else { + result = dns_difftuple_create( + mctx, DNS_DIFFOP_ADDRESIGN, gorigin, + zone_soa_min_ttl, &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, &dbversion); + check_result(result, "dns_db_newversion"); + + result = dns_diff_apply(&diff, db, dbversion); + check_result(result, "dns_diff_apply"); + dns_diff_clear(&diff); + + result = dns_master_dump(mctx, db, dbversion, style, filename, + dns_masterformat_text, NULL); + check_result(result, "dns_master_dump"); + + isc_mem_put(mctx, filename, filenamelen); + + dns_db_closeversion(db, &dbversion, false); + dns_db_detach(&db); +} + +static void +print_time(FILE *fp) { + time_t currenttime = time(NULL); + struct tm t, *tm = localtime_r(¤ttime, &t); + unsigned int flen; + char timebuf[80]; + + if (tm == NULL || outputformat != dns_masterformat_text) { + return; + } + + flen = strftime(timebuf, sizeof(timebuf), "%a %b %e %H:%M:%S %Y", tm); + INSIST(flen > 0U && flen < sizeof(timebuf)); + fprintf(fp, "; File written on %s\n", timebuf); +} + +static void +print_version(FILE *fp) { + if (outputformat != dns_masterformat_text) { + return; + } + + fprintf(fp, "; dnssec_signzone version " VERSION "\n"); +} + +ISC_PLATFORM_NORETURN_PRE static void +usage(void) ISC_PLATFORM_NORETURN_POST; + +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-S:\tsmart signing: automatically finds key files\n" + "\t\tfor the zone and determines how they are to " + "be used\n"); + fprintf(stderr, "\t-K directory:\n"); + fprintf(stderr, "\t\tdirectory to find key files (.)\n"); + fprintf(stderr, "\t-d directory:\n"); + fprintf(stderr, "\t\tdirectory to find dsset-* files (.)\n"); + fprintf(stderr, "\t-g:\t"); + fprintf(stderr, "update DS records based on child zones' " + "dsset-* 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-X [YYYYMMDDHHMMSS|+offset|\"now\"+offset]:\n"); + fprintf(stderr, "\t\tDNSKEY RRSIG end " + "- absolute|from start|from now " + "(matches -e)\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-q quiet\n"); + fprintf(stderr, "\t-V:\tprint version information\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-D:\n"); + fprintf(stderr, "\t\toutput only DNSSEC-related records\n"); + fprintf(stderr, "\t-a:\t"); + fprintf(stderr, "verify generated signatures\n"); + fprintf(stderr, "\t-c class (IN)\n"); + fprintf(stderr, "\t-E engine:\n"); +#if USE_PKCS11 + fprintf(stderr, + "\t\tpath to PKCS#11 provider library " + "(default is %s)\n", + PK11_LIB_LOCATION); +#else /* if USE_PKCS11 */ + fprintf(stderr, "\t\tname of an OpenSSL engine to use\n"); +#endif /* if USE_PKCS11 */ + fprintf(stderr, "\t-P:\t"); + fprintf(stderr, "disable post-sign verification\n"); + fprintf(stderr, "\t-Q:\t"); + fprintf(stderr, "remove signatures from keys that are no " + "longer active\n"); + fprintf(stderr, "\t-R:\t"); + fprintf(stderr, "remove signatures from keys that no longer exist\n"); + fprintf(stderr, "\t-T TTL:\tTTL for newly added DNSKEYs\n"); + fprintf(stderr, "\t-t:\t"); + fprintf(stderr, "print statistics\n"); + fprintf(stderr, "\t-u:\t"); + fprintf(stderr, "update or replace an existing NSEC/NSEC3 chain\n"); + fprintf(stderr, "\t-x:\tsign DNSKEY record with KSKs only, not ZSKs\n"); + fprintf(stderr, "\t-z:\tsign all records with KSKs\n"); + fprintf(stderr, "\t-C:\tgenerate a keyset file, for compatibility\n" + "\t\twith older versions of dnssec-signzone -g\n"); + fprintf(stderr, "\t-n ncpus (number of cpus present)\n"); + fprintf(stderr, "\t-k key_signing_key\n"); + fprintf(stderr, "\t-3 NSEC3 salt\n"); + fprintf(stderr, "\t-H NSEC3 iterations (10)\n"); + fprintf(stderr, "\t-A NSEC3 optout\n"); + + 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_time_t *sign_start, isc_time_t *sign_finish) { + uint64_t time_us; /* Time in microseconds */ + uint64_t time_ms; /* Time in milliseconds */ + uint64_t sig_ms; /* Signatures per millisecond */ + FILE *out = output_stdout ? stderr : stdout; + + fprintf(out, "Signatures generated: %10u\n", nsigned); + fprintf(out, "Signatures retained: %10u\n", nretained); + fprintf(out, "Signatures dropped: %10u\n", ndropped); + fprintf(out, "Signatures successfully verified: %10u\n", nverified); + fprintf(out, + "Signatures unsuccessfully " + "verified: %10u\n", + nverifyfailed); + + time_us = isc_time_microdiff(sign_finish, sign_start); + time_ms = time_us / 1000; + fprintf(out, "Signing time in seconds: %7u.%03u\n", + (unsigned int)(time_ms / 1000), (unsigned int)(time_ms % 1000)); + if (time_us > 0) { + sig_ms = ((uint64_t)nsigned * 1000000000) / time_us; + fprintf(out, "Signatures per second: %7u.%03u\n", + (unsigned int)sig_ms / 1000, + (unsigned int)sig_ms % 1000); + } + + time_us = isc_time_microdiff(timer_finish, timer_start); + time_ms = time_us / 1000; + fprintf(out, "Runtime in seconds: %7u.%03u\n", + (unsigned int)(time_ms / 1000), (unsigned int)(time_ms % 1000)); +} + +int +main(int argc, char *argv[]) { + int i, ch; + char *startstr = NULL, *endstr = NULL, *classname = NULL; + char *dnskey_endstr = 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; + isc_time_t sign_start, sign_finish; + dns_dnsseckey_t *key; + isc_result_t result, vresult; + isc_log_t *log = NULL; + const char *engine = NULL; + bool free_output = false; + int tempfilelen = 0; + dns_rdataclass_t rdclass; + isc_task_t **tasks = NULL; + hashlist_t hashlist; + bool make_keyset = false; + bool set_salt = false; + bool set_optout = false; + bool set_iter = false; + bool nonsecify = false; + + atomic_init(&shuttingdown, false); + atomic_init(&finished, false); + + /* Unused letters: Bb G J q Yy (and F is reserved). */ +#define CMDLINE_FLAGS \ + "3:AaCc:Dd:E:e:f:FghH:i:I:j:K:k:L:l:m:M:n:N:o:O:PpQqRr:s:ST:tuUv:VX:" \ + "xzZ:" + + /* + * Process memory debugging argument 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 = true; + +#ifdef _WIN32 + InitSockets(); +#endif /* ifdef _WIN32 */ + + masterstyle = &dns_master_style_explicitttl; + + check_result(isc_app_start(), "isc_app_start"); + + isc_mem_create(&mctx); + +#if USE_PKCS11 + pk11_result_register(); +#endif /* if USE_PKCS11 */ + dns_result_register(); + + isc_commandline_errprint = false; + + while ((ch = isc_commandline_parse(argc, argv, CMDLINE_FLAGS)) != -1) { + switch (ch) { + case '3': + set_salt = true; + nsec_datatype = dns_rdatatype_nsec3; + if (strcmp(isc_commandline_argument, "-") != 0) { + 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_length = isc_buffer_usedlength(&target); + } + break; + + case 'A': + set_optout = true; + if (OPTOUT(nsec3flags)) { + nsec3flags &= ~DNS_NSEC3FLAG_OPTOUT; + } else { + nsec3flags |= DNS_NSEC3FLAG_OPTOUT; + } + break; + + case 'a': + tryverify = true; + break; + + case 'C': + make_keyset = true; + break; + + case 'c': + classname = isc_commandline_argument; + break; + + case 'd': + dsdir = isc_commandline_argument; + if (strlen(dsdir) == 0U) { + fatal("DS directory must be non-empty string"); + } + result = try_dir(dsdir); + if (result != ISC_R_SUCCESS) { + fatal("cannot open directory %s: %s", dsdir, + isc_result_totext(result)); + } + break; + + case 'D': + output_dnssec_only = true; + break; + + case 'E': + engine = isc_commandline_argument; + break; + + case 'e': + endstr = isc_commandline_argument; + break; + + case 'f': + output = isc_commandline_argument; + if (strcmp(output, "-") == 0) { + output_stdout = true; + } + break; + + case 'g': + generateds = true; + break; + + case 'H': + set_iter = true; + /* too-many is NOT DOCUMENTED */ + if (strcmp(isc_commandline_argument, "too-many") == 0) { + nsec3iter = 151; + no_max_check = true; + break; + } + nsec3iter = strtoul(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fatal("iterations must be numeric"); + } + if (nsec3iter > 0xffffU) { + fatal("iterations too big"); + } + break; + + case 'I': + inputformatstr = isc_commandline_argument; + break; + + 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 'j': + endp = NULL; + jitter = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0' || jitter < 0) { + fatal("jitter must be numeric and positive"); + } + break; + + case 'K': + directory = isc_commandline_argument; + break; + + case 'k': + if (ndskeys == MAXDSKEYS) { + fatal("too many key-signing keys specified"); + } + dskeyfile[ndskeys++] = isc_commandline_argument; + break; + + case 'L': + snset = true; + endp = NULL; + serialnum = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fprintf(stderr, "source serial number " + "must be numeric"); + exit(1); + } + break; + + case 'l': + fatal("-l option (DLV lookaside) is obsolete"); + break; + + case 'M': + endp = NULL; + set_maxttl = true; + maxttl = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fprintf(stderr, "maximum TTL " + "must be numeric"); + exit(1); + } + break; + + case 'm': + break; + + case 'N': + serialformatstr = isc_commandline_argument; + break; + + case 'n': + endp = NULL; + ntasks = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0' || ntasks > INT32_MAX) { + fatal("number of cpus must be numeric"); + } + break; + + case 'O': + outputformatstr = isc_commandline_argument; + break; + + case 'o': + origin = isc_commandline_argument; + break; + + case 'P': + disable_zone_check = true; + break; + + case 'p': + fatal("The -p option has been deprecated.\n"); + break; + + case 'Q': + remove_inactkeysigs = true; + break; + + case 'R': + remove_orphansigs = true; + break; + + case 'r': + fatal("The -r options has been deprecated.\n"); + break; + + case 'S': + smartsign = true; + break; + + case 's': + startstr = isc_commandline_argument; + break; + + case 'T': + endp = NULL; + set_keyttl = true; + keyttl = strtottl(isc_commandline_argument); + break; + + case 't': + printstats = true; + break; + + case 'U': /* Undocumented for testing only. */ + unknownalg = true; + break; + + case 'u': + update_chain = true; + break; + + case 'v': + endp = NULL; + verbose = strtol(isc_commandline_argument, &endp, 0); + if (*endp != '\0') { + fatal("verbose level must be numeric"); + } + break; + + case 'q': + quiet = true; + break; + + case 'X': + dnskey_endstr = isc_commandline_argument; + break; + + case 'x': + keyset_kskonly = true; + break; + + case 'z': + ignore_kskflag = true; + break; + + case 'F': + /* Reserved for FIPS mode */ + FALLTHROUGH; + case '?': + if (isc_commandline_option != '?') { + fprintf(stderr, "%s: invalid argument -%c\n", + program, isc_commandline_option); + } + FALLTHROUGH; + case 'h': + /* Does not return. */ + usage(); + + case 'V': + /* Does not return. */ + version(program); + + case 'Z': /* Undocumented test options */ + if (!strcmp(isc_commandline_argument, "nonsecify")) { + nonsecify = true; + } + break; + + default: + fprintf(stderr, "%s: unhandled option -%c\n", program, + isc_commandline_option); + exit(1); + } + } + + result = dst_lib_init(mctx, engine); + if (result != ISC_R_SUCCESS) { + fatal("could not initialize dst: %s", + isc_result_totext(result)); + } + + isc_stdtime_get(&now); + + if (startstr != NULL) { + starttime = strtotime(startstr, now, now, NULL); + } else { + starttime = now - 3600; /* Allow for some clock skew. */ + } + + if (endstr != NULL) { + endtime = strtotime(endstr, now, starttime, NULL); + } else { + endtime = starttime + (30 * 24 * 60 * 60); + } + + if (dnskey_endstr != NULL) { + dnskey_endtime = strtotime(dnskey_endstr, now, starttime, NULL); + if (endstr != NULL && dnskey_endtime == endtime) { + fprintf(stderr, "WARNING: -e and -X were both set, " + "but have identical values.\n"); + } + } else { + dnskey_endtime = endtime; + } + + 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); + + if (directory == NULL) { + directory = "."; + } + + setup_logging(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) { + size_t size; + free_output = true; + size = strlen(file) + strlen(".signed") + 1; + output = isc_mem_allocate(mctx, size); + snprintf(output, size, "%s.signed", file); + } + + if (inputformatstr != NULL) { + if (strcasecmp(inputformatstr, "text") == 0) { + inputformat = dns_masterformat_text; + } else if (strcasecmp(inputformatstr, "map") == 0) { + inputformat = dns_masterformat_map; + } else if (strcasecmp(inputformatstr, "raw") == 0) { + inputformat = dns_masterformat_raw; + } else if (strncasecmp(inputformatstr, "raw=", 4) == 0) { + inputformat = dns_masterformat_raw; + fprintf(stderr, "WARNING: input format version " + "ignored\n"); + } else { + fatal("unknown file format: %s", inputformatstr); + } + } + + if (outputformatstr != NULL) { + if (strcasecmp(outputformatstr, "text") == 0) { + outputformat = dns_masterformat_text; + } else if (strcasecmp(outputformatstr, "full") == 0) { + outputformat = dns_masterformat_text; + masterstyle = &dns_master_style_full; + } else if (strcasecmp(outputformatstr, "map") == 0) { + outputformat = dns_masterformat_map; + } else if (strcasecmp(outputformatstr, "raw") == 0) { + outputformat = dns_masterformat_raw; + } else if (strncasecmp(outputformatstr, "raw=", 4) == 0) { + char *end; + + outputformat = dns_masterformat_raw; + rawversion = strtol(outputformatstr + 4, &end, 10); + if (end == outputformatstr + 4 || *end != '\0' || + rawversion > 1U) + { + fprintf(stderr, "unknown raw format version\n"); + exit(1); + } + } else { + fatal("unknown file format: %s", 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 if (strcasecmp(serialformatstr, "date") == 0) { + serialformat = SOA_SERIAL_DATE; + } else { + fatal("unknown soa serial format: %s", serialformatstr); + } + } + + if (output_dnssec_only && outputformat != dns_masterformat_text) { + fatal("option -D can only be used with \"-O text\""); + } + + if (output_dnssec_only && serialformat != SOA_SERIAL_KEEP) { + fatal("option -D can only be used with \"-N keep\""); + } + + if (output_dnssec_only && set_maxttl) { + fatal("option -D cannot be used with -M"); + } + + result = dns_master_stylecreate(&dsstyle, DNS_STYLEFLAG_NO_TTL, 0, 24, + 0, 0, 0, 8, 0xffffffff, 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); + get_soa_ttls(); + + if (set_maxttl && set_keyttl && keyttl > maxttl) { + fprintf(stderr, + "%s: warning: Specified key TTL %u " + "exceeds maximum zone TTL; reducing to %u\n", + program, keyttl, maxttl); + keyttl = maxttl; + } + + if (!set_keyttl) { + keyttl = soa_ttl; + } + + /* + * Check for any existing NSEC3 parameters in the zone, + * and use them as defaults if -u was not specified. + */ + if (update_chain && !set_optout && !set_iter && !set_salt) { + nsec_datatype = dns_rdatatype_nsec; + } else { + set_nsec3params(update_chain, set_salt, set_optout, set_iter); + } + + /* + * We need to do this early on, as we start messing with the list + * of keys rather early. + */ + ISC_LIST_INIT(keylist); + isc_rwlock_init(&keylist_lock, 0, 0); + + /* + * Fill keylist with: + * 1) Keys listed in the DNSKEY set that have + * private keys associated, *if* no keys were + * set on the command line. + * 2) ZSKs set on the command line + * 3) KSKs set on the command line + * 4) Any keys remaining in the DNSKEY set which + * do not have private keys associated and were + * not specified on the command line. + */ + if (argc == 0 || smartsign) { + loadzonekeys(!smartsign, false); + } + loadexplicitkeys(argv, argc, false); + loadexplicitkeys(dskeyfile, ndskeys, true); + loadzonekeys(!smartsign, true); + + /* + * If we're doing smart signing, look in the key repository for + * key files with metadata, and merge them with the keylist + * we have now. + */ + if (smartsign) { + build_final_keylist(); + } + + /* Now enumerate the key list */ + for (key = ISC_LIST_HEAD(keylist); key != NULL; + key = ISC_LIST_NEXT(key, link)) + { + key->index = keycount++; + } + + if (keycount == 0) { + if (disable_zone_check) { + fprintf(stderr, + "%s: warning: No keys specified " + "or found\n", + program); + } else { + fatal("No signing keys specified or found."); + } + nokeys = true; + } + + warnifallksk(gdb); + + if (IS_NSEC3) { + bool 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); + if (result == ISC_R_NOTFOUND) { + fprintf(stderr, + "%s: warning: NSEC3 generation " + "requested with no DNSKEY; ignoring\n", + program); + } else if (result != ISC_R_SUCCESS) { + check_result(result, "dns_nsec_nseconly"); + } else if (answer) { + fatal("NSEC3 generation requested with " + "NSEC-only DNSKEY"); + } + + if (nsec3iter > dns_nsec3_maxiterations()) { + if (no_max_check) { + fprintf(stderr, + "Ignoring max iterations check.\n"); + } else { + fatal("NSEC3 iterations too big. Maximum " + "iterations allowed %u.", + dns_nsec3_maxiterations()); + } + } + } else { + hashlist_init(&hashlist, 0, 0); /* silence clang */ + } + + gversion = NULL; + result = dns_db_newversion(gdb, &gversion); + check_result(result, "dns_db_newversion()"); + + switch (serialformat) { + case SOA_SERIAL_INCREMENT: + setsoaserial(0, dns_updatemethod_increment); + break; + case SOA_SERIAL_UNIXTIME: + setsoaserial(now, dns_updatemethod_unixtime); + break; + case SOA_SERIAL_DATE: + setsoaserial(now, dns_updatemethod_date); + break; + case SOA_SERIAL_KEEP: + default: + /* do nothing */ + break; + } + + /* Remove duplicates and cap TTLs at maxttl */ + cleanup_zone(); + + if (!nonsecify) { + if (IS_NSEC3) { + nsec3ify(dns_hash_sha1, nsec3iter, gsalt, salt_length, + &hashlist); + } else { + nsecify(); + } + } + + if (!nokeys) { + writeset("dsset-", dns_rdatatype_ds); + if (make_keyset) { + writeset("keyset-", dns_rdatatype_dnskey); + } + } + + if (output_stdout) { + outfp = stdout; + if (outputformatstr == NULL) { + masterstyle = &dns_master_style_full; + } + } else { + tempfilelen = strlen(output) + 20; + tempfile = isc_mem_get(mctx, tempfilelen); + + result = isc_file_mktemplate(output, tempfile, tempfilelen); + check_result(result, "isc_file_mktemplate"); + + if (outputformat == dns_masterformat_text) { + result = isc_file_openunique(tempfile, &outfp); + } else { + result = isc_file_bopenunique(tempfile, &outfp); + } + if (result != ISC_R_SUCCESS) { + fatal("failed to open temporary output file: %s", + isc_result_totext(result)); + } + removefile = true; + setfatalcallback(&removetempfile); + } + + print_time(outfp); + print_version(outfp); + + result = isc_managers_create(mctx, ntasks, 0, &netmgr, &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 *)); + 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)); + } + } + + isc_mutex_init(&namelock); + + if (printstats) { + isc_mutex_init(&statslock); + } + + presign(); + TIME_NOW(&sign_start); + signapex(); + if (!atomic_load(&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 (!atomic_load(&finished)) { + fatal("process aborted by user"); + } + } else { + isc_task_detach(&master); + } + atomic_store(&shuttingdown, true); + for (i = 0; i < (int)ntasks; i++) { + isc_task_detach(&tasks[i]); + } + isc_managers_destroy(&netmgr, &taskmgr); + isc_mem_put(mctx, tasks, ntasks * sizeof(isc_task_t *)); + postsign(); + TIME_NOW(&sign_finish); + + if (disable_zone_check) { + vresult = ISC_R_SUCCESS; + } else { + vresult = dns_zoneverify_dnssec(NULL, gdb, gversion, gorigin, + NULL, mctx, ignore_kskflag, + keyset_kskonly, report); + if (vresult != ISC_R_SUCCESS) { + fprintf(output_stdout ? stderr : stdout, + "Zone verification failed (%s)\n", + isc_result_totext(vresult)); + } + } + + if (outputformat != dns_masterformat_text) { + dns_masterrawheader_t header; + dns_master_initrawheader(&header); + if (rawversion == 0U) { + header.flags = DNS_MASTERRAW_COMPAT; + } else if (snset) { + header.flags = DNS_MASTERRAW_SOURCESERIALSET; + header.sourceserial = serialnum; + } + result = dns_master_dumptostream(mctx, gdb, gversion, + masterstyle, outputformat, + &header, outfp); + check_result(result, "dns_master_dumptostream3"); + } + + isc_mutex_destroy(&namelock); + if (printstats) { + isc_mutex_destroy(&statslock); + } + + if (!output_stdout) { + result = isc_stdio_close(outfp); + check_result(result, "isc_stdio_close"); + removefile = false; + + if (vresult == ISC_R_SUCCESS) { + result = isc_file_rename(tempfile, output); + if (result != ISC_R_SUCCESS) { + fatal("failed to rename temp file to %s: %s", + output, isc_result_totext(result)); + } + printf("%s\n", output); + } else { + isc_file_remove(tempfile); + } + } + + dns_db_closeversion(gdb, &gversion, false); + dns_db_detach(&gdb); + + hashlist_free(&hashlist); + + while (!ISC_LIST_EMPTY(keylist)) { + key = ISC_LIST_HEAD(keylist); + ISC_LIST_UNLINK(keylist, key, link); + dns_dnsseckey_destroy(mctx, &key); + } + + if (tempfilelen != 0) { + 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(); + 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, &sign_start, + &sign_finish); + } + +#ifdef _WIN32 + DestroySockets(); +#endif /* ifdef _WIN32 */ + return (vresult == ISC_R_SUCCESS ? 0 : 1); +} |