update to 9.7.1-P2
[tridge/bind9.git] / bin / named / update.c
index 68e7e320795b0c982a9b7eacb5742ccc3cb36c72..59fa3cb0d90fe67ed815bd7d83a482cef7efb803 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2004-2009  Internet Systems Consortium, Inc. ("ISC")
+ * Copyright (C) 2004-2010  Internet Systems Consortium, Inc. ("ISC")
  * Copyright (C) 1999-2003  Internet Software Consortium.
  *
  * Permission to use, copy, modify, and/or distribute this software for any
@@ -15,7 +15,7 @@
  * PERFORMANCE OF THIS SOFTWARE.
  */
 
-/* $Id: update.c,v 1.151.12.5.12.1 2009/07/28 14:18:08 marka Exp $ */
+/* $Id: update.c,v 1.176.4.6 2010/05/18 01:40:34 marka Exp $ */
 
 #include <config.h>
 
@@ -38,6 +38,7 @@
 #include <dns/message.h>
 #include <dns/nsec.h>
 #include <dns/nsec3.h>
+#include <dns/private.h>
 #include <dns/rdataclass.h>
 #include <dns/rdataset.h>
 #include <dns/rdatasetiter.h>
@@ -280,6 +281,47 @@ inc_stats(dns_zone_t *zone, isc_statscounter_t counter) {
        }
 }
 
+/*%
+ * Check if we could have queried for the contents of this zone or
+ * if the zone is potentially updateable.
+ * If the zone can potentially be updated and the check failed then
+ * log a error otherwise we log a informational message.
+ */
+static isc_result_t
+checkqueryacl(ns_client_t *client, dns_acl_t *queryacl, dns_name_t *zonename,
+             dns_acl_t *updateacl, dns_ssutable_t *ssutable)
+{
+       char namebuf[DNS_NAME_FORMATSIZE];
+       char classbuf[DNS_RDATACLASS_FORMATSIZE];
+       int level;
+       isc_result_t result;
+
+       result = ns_client_checkaclsilent(client, NULL, queryacl, ISC_TRUE);
+       if (result != ISC_R_SUCCESS) {
+               dns_name_format(zonename, namebuf, sizeof(namebuf));
+               dns_rdataclass_format(client->view->rdclass, classbuf,
+                                     sizeof(classbuf));
+
+               level = (updateacl == NULL && ssutable == NULL) ?
+                               ISC_LOG_INFO : ISC_LOG_ERROR;
+
+               ns_client_log(client, NS_LOGCATEGORY_UPDATE_SECURITY,
+                             NS_LOGMODULE_UPDATE, level,
+                             "update '%s/%s' denied due to allow-query",
+                             namebuf, classbuf);
+       } else if (updateacl == NULL && ssutable == NULL) {
+               dns_name_format(zonename, namebuf, sizeof(namebuf));
+               dns_rdataclass_format(client->view->rdclass, classbuf,
+                                     sizeof(classbuf));
+
+               result = DNS_R_REFUSED;
+               ns_client_log(client, NS_LOGCATEGORY_UPDATE_SECURITY,
+                             NS_LOGMODULE_UPDATE, ISC_LOG_INFO,
+                             "update '%s/%s' denied", namebuf, classbuf);
+       }
+       return (result);
+}
+
 /*%
  * Override the default acl logging when checking whether a client
  * can update the zone or whether we can forward the request to the
@@ -331,7 +373,7 @@ checkupdateacl(ns_client_t *client, dns_acl_t *acl, const char *message,
                              sizeof(classbuf));
 
        ns_client_log(client, NS_LOGCATEGORY_UPDATE_SECURITY,
-                     NS_LOGMODULE_UPDATE, level, "(tridge) %s '%s/%s' %s",
+                     NS_LOGMODULE_UPDATE, level, "%s '%s/%s' %s",
                      message, namebuf, classbuf, msg);
        return (result);
 }
@@ -889,7 +931,7 @@ temp_check_rrset(dns_difftuple_t *a, dns_difftuple_t *b) {
                       b->op == DNS_DIFFOP_EXISTS);
                INSIST(a->rdata.type == b->rdata.type);
                INSIST(dns_name_equal(&a->name, &b->name));
-               if (dns_rdata_compare(&a->rdata, &b->rdata) != 0)
+               if (dns_rdata_casecompare(&a->rdata, &b->rdata) != 0)
                        return (DNS_R_NXRRSET);
                a = ISC_LIST_NEXT(a, link);
                b = ISC_LIST_NEXT(b, link);
@@ -917,7 +959,7 @@ temp_order(const void *av, const void *bv) {
        r = (b->rdata.type - a->rdata.type);
        if (r != 0)
                return (r);
-       r = dns_rdata_compare(&a->rdata, &b->rdata);
+       r = dns_rdata_casecompare(&a->rdata, &b->rdata);
        return (r);
 }
 
@@ -1146,7 +1188,7 @@ rr_equal_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
         *         dns_rdata_equal() (that used dns_name_equal()), since it
         *         would be faster.  Not a priority.
         */
-       return (dns_rdata_compare(update_rr, db_rr) == 0 ?
+       return (dns_rdata_casecompare(update_rr, db_rr) == 0 ?
                ISC_TRUE : ISC_FALSE);
 }
 
@@ -1211,8 +1253,8 @@ replaces_p(dns_rdata_t *update_rr, dns_rdata_t *db_rr) {
                 * Replace records added in this UPDATE request.
                 */
                if (db_rr->data[0] == update_rr->data[0] &&
-                   db_rr->data[1] & DNS_NSEC3FLAG_UPDATE &&
-                   update_rr->data[1] & DNS_NSEC3FLAG_UPDATE &&
+                   (db_rr->data[1] & DNS_NSEC3FLAG_UPDATE) != 0 &&
+                   (update_rr->data[1] & DNS_NSEC3FLAG_UPDATE) != 0 &&
                    memcmp(db_rr->data+2, update_rr->data+2,
                           update_rr->length - 2) == 0)
                        return (ISC_TRUE);
@@ -1293,7 +1335,7 @@ add_rr_prepare_action(void *data, rr_t *rr) {
         * If the update RR is a "duplicate" of the update RR,
         * the update should be silently ignored.
         */
-       equal = ISC_TF(dns_rdata_compare(&rr->rdata, ctx->update_rr) == 0);
+       equal = ISC_TF(dns_rdata_casecompare(&rr->rdata, ctx->update_rr) == 0);
        if (equal && rr->ttl == ctx->update_rr_ttl) {
                ctx->ignore_add = ISC_TRUE;
                return (ISC_R_SUCCESS);
@@ -1717,35 +1759,6 @@ next_active(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
        return (result);
 }
 
-static isc_boolean_t
-has_opt_bit(dns_db_t *db, dns_dbversion_t *version, dns_dbnode_t *node) {
-       isc_result_t result;
-       dns_rdata_t rdata = DNS_RDATA_INIT;
-       dns_rdataset_t rdataset;
-       isc_boolean_t has_bit = ISC_FALSE;
-
-       dns_rdataset_init(&rdataset);
-       CHECK(dns_db_findrdataset(db, node, version, dns_rdatatype_nsec,
-                                 dns_rdatatype_none, 0, &rdataset, NULL));
-       CHECK(dns_rdataset_first(&rdataset));
-       dns_rdataset_current(&rdataset, &rdata);
-       has_bit = dns_nsec_typepresent(&rdata, dns_rdatatype_opt);
- failure:
-       if (dns_rdataset_isassociated(&rdataset))
-               dns_rdataset_disassociate(&rdataset);
-       return (has_bit);
-}
-
-static void
-set_bit(unsigned char *array, unsigned int index) {
-       unsigned int shift, bit;
-
-       shift = 7 - (index % 8);
-       bit = 1 << shift;
-
-       array[index / 8] |= bit;
-}
-
 /*%
  * Add a NSEC record for "name", recording the change in "diff".
  * The existing NSEC is removed.
@@ -1777,24 +1790,6 @@ add_nsec(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
        CHECK(dns_db_findnode(db, name, ISC_FALSE, &node));
        dns_rdata_init(&rdata);
        CHECK(dns_nsec_buildrdata(db, ver, node, target, buffer, &rdata));
-       /*
-        * Preserve the status of the OPT bit in the origin's NSEC record.
-        */
-       if (dns_name_equal(dns_db_origin(db), name) &&
-           has_opt_bit(db, ver, node))
-       {
-                       isc_region_t region;
-                       dns_name_t next;
-
-                       dns_name_init(&next, NULL);
-                       dns_rdata_toregion(&rdata, &region);
-                       dns_name_fromregion(&next, &region);
-                       isc_region_consume(&region, next.length);
-                       INSIST(region.length > (2 + dns_rdatatype_opt / 8) &&
-                              region.base[0] == 0 &&
-                              region.base[1] > dns_rdatatype_opt / 8);
-                       set_bit(region.base + 2, dns_rdatatype_opt);
-       }
        dns_db_detachnode(db, &node);
 
        /*
@@ -1856,44 +1851,6 @@ find_zone_keys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver,
        return (result);
 }
 
-static isc_boolean_t
-ksk_sanity(dns_db_t *db, dns_dbversion_t *ver) {
-       isc_boolean_t ret = ISC_FALSE;
-       isc_boolean_t have_ksk = ISC_FALSE, have_nonksk = ISC_FALSE;
-       isc_result_t result;
-       dns_dbnode_t *node = NULL;
-       dns_rdataset_t rdataset;
-       dns_rdata_t rdata = DNS_RDATA_INIT;
-       dns_rdata_dnskey_t dnskey;
-
-       dns_rdataset_init(&rdataset);
-       CHECK(dns_db_findnode(db, dns_db_origin(db), ISC_FALSE, &node));
-       CHECK(dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey, 0, 0,
-                                  &rdataset, NULL));
-       CHECK(dns_rdataset_first(&rdataset));
-       while (result == ISC_R_SUCCESS && (!have_ksk || !have_nonksk)) {
-               dns_rdataset_current(&rdataset, &rdata);
-               CHECK(dns_rdata_tostruct(&rdata, &dnskey, NULL));
-               if ((dnskey.flags & (DNS_KEYFLAG_OWNERMASK|DNS_KEYTYPE_NOAUTH))
-                                == DNS_KEYOWNER_ZONE) {
-                       if ((dnskey.flags & DNS_KEYFLAG_KSK) != 0)
-                               have_ksk = ISC_TRUE;
-                       else
-                               have_nonksk = ISC_TRUE;
-               }
-               dns_rdata_reset(&rdata);
-               result = dns_rdataset_next(&rdataset);
-       }
-       if (have_ksk && have_nonksk)
-               ret = ISC_TRUE;
- failure:
-       if (dns_rdataset_isassociated(&rdataset))
-               dns_rdataset_disassociate(&rdataset);
-       if (node != NULL)
-               dns_db_detachnode(db, &node);
-       return (ret);
-}
-
 /*%
  * Add RRSIG records for an RRset, recording the change in "diff".
  */
@@ -1902,7 +1859,7 @@ add_sigs(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
         dns_dbversion_t *ver, dns_name_t *name, dns_rdatatype_t type,
         dns_diff_t *diff, dst_key_t **keys, unsigned int nkeys,
         isc_stdtime_t inception, isc_stdtime_t expire,
-        isc_boolean_t check_ksk)
+        isc_boolean_t check_ksk, isc_boolean_t keyset_kskonly)
 {
        isc_result_t result;
        dns_dbnode_t *node = NULL;
@@ -1910,7 +1867,7 @@ add_sigs(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
        dns_rdata_t sig_rdata = DNS_RDATA_INIT;
        isc_buffer_t buffer;
        unsigned char data[1024]; /* XXX */
-       unsigned int i;
+       unsigned int i, j;
        isc_boolean_t added_sig = ISC_FALSE;
        isc_mem_t *mctx = client->mctx;
 
@@ -1926,13 +1883,52 @@ add_sigs(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                                  (isc_stdtime_t) 0, &rdataset, NULL));
        dns_db_detachnode(db, &node);
 
+#define REVOKE(x) ((dst_key_flags(x) & DNS_KEYFLAG_REVOKE) != 0)
+#define KSK(x) ((dst_key_flags(x) & DNS_KEYFLAG_KSK) != 0)
+#define ALG(x) dst_key_alg(x)
+
+       /*
+        * If we are honoring KSK flags then we need to check that we
+        * have both KSK and non-KSK keys that are not revoked per
+        * algorithm.
+        */
        for (i = 0; i < nkeys; i++) {
+               isc_boolean_t both = ISC_FALSE;
 
-               if (check_ksk && type != dns_rdatatype_dnskey &&
-                   (dst_key_flags(keys[i]) & DNS_KEYFLAG_KSK) != 0)
+               if (!dst_key_isprivate(keys[i]))
                        continue;
 
-               if (!dst_key_isprivate(keys[i]))
+               if (check_ksk && !REVOKE(keys[i])) {
+                       isc_boolean_t have_ksk, have_nonksk;
+                       if (KSK(keys[i])) {
+                               have_ksk = ISC_TRUE;
+                               have_nonksk = ISC_FALSE;
+                       } else {
+                               have_ksk = ISC_FALSE;
+                               have_nonksk = ISC_TRUE;
+                       }
+                       for (j = 0; j < nkeys; j++) {
+                               if (j == i || ALG(keys[i]) != ALG(keys[j]))
+                                       continue;
+                               if (REVOKE(keys[j]))
+                                       continue;
+                               if (KSK(keys[j]))
+                                       have_ksk = ISC_TRUE;
+                               else
+                                       have_nonksk = ISC_TRUE;
+                               both = have_ksk && have_nonksk;
+                               if (both)
+                                       break;
+                       }
+               }
+
+               if (both) {
+                       if (type == dns_rdatatype_dnskey) {
+                               if (!KSK(keys[i]) && keyset_kskonly)
+                                       continue;
+                       } else if (KSK(keys[i]))
+                               continue;
+               } else if (REVOKE(keys[i]) && type != dns_rdatatype_dnskey)
                        continue;
 
                /* Calculate the signature, creating a RRSIG RDATA. */
@@ -1945,11 +1941,12 @@ add_sigs(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                CHECK(update_one_rr(db, ver, diff, DNS_DIFFOP_ADDRESIGN, name,
                                    rdataset.ttl, &sig_rdata));
                dns_rdata_reset(&sig_rdata);
+               isc_buffer_init(&buffer, data, sizeof(data));
                added_sig = ISC_TRUE;
        }
        if (!added_sig) {
                update_log(client, zone, ISC_LOG_ERROR,
-                          "found no private keys, "
+                          "found no active private keys, "
                           "unable to generate any signatures");
                result = ISC_R_NOTFOUND;
        }
@@ -2043,7 +2040,7 @@ add_exposed_sigs(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                 dns_dbversion_t *ver, dns_name_t *name, isc_boolean_t cut,
                 dns_diff_t *diff, dst_key_t **keys, unsigned int nkeys,
                 isc_stdtime_t inception, isc_stdtime_t expire,
-                isc_boolean_t check_ksk)
+                isc_boolean_t check_ksk, isc_boolean_t keyset_kskonly)
 {
        isc_result_t result;
        dns_dbnode_t *node;
@@ -2089,7 +2086,8 @@ add_exposed_sigs(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                if (flag)
                        continue;;
                result = add_sigs(client, zone, db, ver, name, type, diff,
-                                 keys, nkeys, inception, expire, check_ksk);
+                                         keys, nkeys, inception, expire,
+                                         check_ksk, keyset_kskonly);
                if (result != ISC_R_SUCCESS)
                        goto cleanup_iterator;
        }
@@ -2119,8 +2117,7 @@ add_exposed_sigs(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
 static isc_result_t
 update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                  dns_dbversion_t *oldver, dns_dbversion_t *newver,
-                 dns_diff_t *diff, isc_uint32_t sigvalidityinterval,
-                 isc_boolean_t *deleted_zsk)
+                 dns_diff_t *diff, isc_uint32_t sigvalidityinterval)
 {
        isc_result_t result;
        dns_difftuple_t *t;
@@ -2129,7 +2126,7 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
        dns_diff_t sig_diff;
        dns_diff_t nsec_diff;
        dns_diff_t nsec_mindiff;
-       isc_boolean_t flag;
+       isc_boolean_t flag, build_nsec, build_nsec3;
        dst_key_t *zone_keys[MAXZONEKEYS];
        unsigned int nkeys = 0;
        unsigned int i;
@@ -2139,9 +2136,10 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
        dns_rdata_t rdata = DNS_RDATA_INIT;
        dns_rdataset_t rdataset;
        dns_dbnode_t *node = NULL;
-       isc_boolean_t check_ksk;
+       isc_boolean_t check_ksk, keyset_kskonly;
        isc_boolean_t unsecure;
        isc_boolean_t cut;
+       dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
 
        dns_diff_init(client->mctx, &diffnames);
        dns_diff_init(client->mctx, &affected);
@@ -2171,27 +2169,8 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
         */
        check_ksk = ISC_TF((dns_zone_getoptions(zone) &
                            DNS_ZONEOPT_UPDATECHECKKSK) != 0);
-       /*
-        * If we are not checking the ZSK flag then all DNSKEY's are
-        * already signing all RRsets so we don't need to trigger special
-        * changes.
-        */
-       if (*deleted_zsk && (!check_ksk || !ksk_sanity(db, oldver)))
-               *deleted_zsk = ISC_FALSE;
-
-       if (check_ksk) {
-               check_ksk = ksk_sanity(db, newver);
-               if (!check_ksk && ksk_sanity(db, oldver))
-                       update_log(client, zone, ISC_LOG_WARNING,
-                                  "disabling update-check-ksk");
-       }
-
-       /*
-        * If we have deleted a ZSK and we we still have some ZSK's
-        * we don't need to convert the KSK's to a ZSK's.
-        */
-       if (*deleted_zsk && check_ksk)
-               *deleted_zsk = ISC_FALSE;
+       keyset_kskonly = ISC_TF((dns_zone_getoptions(zone) &
+                               DNS_ZONEOPT_DNSKEYKSKONLY) != 0);
 
        /*
         * Get the NSEC/NSEC3 TTL from the SOA MINIMUM field.
@@ -2258,7 +2237,7 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                                CHECK(add_sigs(client, zone, db, newver, name,
                                               type, &sig_diff, zone_keys,
                                               nkeys, inception, expire,
-                                              check_ksk));
+                                              check_ksk, keyset_kskonly));
                        }
                skip:
                        /* Skip any other updates to the same RRset. */
@@ -2288,12 +2267,11 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                   "removed any orphaned NSEC records");
 
        /*
-        * If we don't have a NSEC record at the origin then we need to
-        * update the NSEC3 records.
+        * See if we need to build NSEC or NSEC3 chains.
         */
-       CHECK(rrset_exists(db, newver, dns_db_origin(db), dns_rdatatype_nsec,
-                          0, &flag));
-       if (!flag)
+       CHECK(dns_private_chains(db, newver, privatetype, &build_nsec,
+                                &build_nsec3));
+       if (!build_nsec)
                goto update_nsec3;
 
        update_log(client, zone, ISC_LOG_DEBUG(3), "rebuilding NSEC chain");
@@ -2397,16 +2375,25 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                                        dns_rdatatype_any, 0, NULL, diff));
                } else {
                        /*
-                        * This name is not obscured.  It should have a NSEC.
+                        * This name is not obscured.  It needs to have a
+                        * NSEC unless it is the at the origin, in which
+                        * case it should already exist if there is a complete
+                        * NSEC chain and if there isn't a complete NSEC chain
+                        * we don't want to add one as that would signal that
+                        * there is a complete NSEC chain.
                         */
-                       CHECK(rrset_exists(db, newver, name,
-                                          dns_rdatatype_nsec, 0, &flag));
-                       if (! flag)
-                               CHECK(add_placeholder_nsec(db, newver, name,
-                                                          diff));
+                       if (!dns_name_equal(name, dns_db_origin(db))) {
+                               CHECK(rrset_exists(db, newver, name,
+                                                  dns_rdatatype_nsec, 0,
+                                                  &flag));
+                               if (!flag)
+                                       CHECK(add_placeholder_nsec(db, newver,
+                                                                  name, diff));
+                       }
                        CHECK(add_exposed_sigs(client, zone, db, newver, name,
                                               cut, diff, zone_keys, nkeys,
-                                              inception, expire, check_ksk));
+                                              inception, expire, check_ksk,
+                                              keyset_kskonly));
                }
        }
 
@@ -2468,7 +2455,7 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                        CHECK(add_sigs(client, zone, db, newver, &t->name,
                                       dns_rdatatype_nsec, &sig_diff,
                                       zone_keys, nkeys, inception, expire,
-                                      check_ksk));
+                                      check_ksk, keyset_kskonly));
                } else {
                        INSIST(0);
                }
@@ -2490,13 +2477,7 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
        INSIST(ISC_LIST_EMPTY(nsec_diff.tuples));
        INSIST(ISC_LIST_EMPTY(nsec_mindiff.tuples));
 
-       /*
-        * Check if we have any active NSEC3 chains by looking for a
-        * NSEC3PARAM RRset.
-        */
-       CHECK(rrset_exists(db, newver, dns_db_origin(db),
-                          dns_rdatatype_nsec3param, 0, &flag));
-       if (!flag) {
+       if (!build_nsec3) {
                update_log(client, zone, ISC_LOG_DEBUG(3),
                           "no NSEC3 chains to rebuild");
                goto failure;
@@ -2520,6 +2501,7 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
 
                isc_boolean_t ns_existed, dname_existed;
                isc_boolean_t ns_exists, dname_exists;
+               isc_boolean_t exists, existed;
 
                if (t->rdata.type == dns_rdatatype_nsec ||
                    t->rdata.type == dns_rdatatype_rrsig) {
@@ -2538,7 +2520,9 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                CHECK(rrset_exists(db, newver, name, dns_rdatatype_dname, 0,
                                   &dname_exists));
 
-               if ((ns_exists || dname_exists) == (ns_existed || dname_existed))
+               exists = ns_exists || dname_exists;
+               existed = ns_existed || dname_existed;
+               if (exists == existed)
                        goto nextname;
                /*
                 * There was a delegation change.  Mark all subdomains
@@ -2562,14 +2546,16 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                if (!flag) {
                        CHECK(delete_if(rrsig_p, db, newver, name,
                                        dns_rdatatype_any, 0, NULL, diff));
-                       CHECK(dns_nsec3_delnsec3s(db, newver, name,
-                                                 &nsec_diff));
+                       CHECK(dns_nsec3_delnsec3sx(db, newver, name,
+                                                  privatetype, &nsec_diff));
                } else {
                        CHECK(add_exposed_sigs(client, zone, db, newver, name,
                                               cut, diff, zone_keys, nkeys,
-                                              inception, expire, check_ksk));
-                       CHECK(dns_nsec3_addnsec3s(db, newver, name, nsecttl,
-                                                 unsecure, &nsec_diff));
+                                              inception, expire, check_ksk,
+                                              keyset_kskonly));
+                       CHECK(dns_nsec3_addnsec3sx(db, newver, name, nsecttl,
+                                                  unsecure, privatetype,
+                                                  &nsec_diff));
                }
        }
 
@@ -2600,7 +2586,8 @@ update_signatures(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                        CHECK(add_sigs(client, zone, db, newver, &t->name,
                                       dns_rdatatype_nsec3,
                                       &sig_diff, zone_keys, nkeys,
-                                      inception, expire, check_ksk));
+                                      inception, expire, check_ksk,
+                                      keyset_kskonly));
                } else {
                        INSIST(0);
                }
@@ -2942,7 +2929,7 @@ rr_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
             result = dns_rdataset_next(&rdataset)) {
                dns_rdata_t myrdata = DNS_RDATA_INIT;
                dns_rdataset_current(&rdataset, &myrdata);
-               if (!dns_rdata_compare(&myrdata, rdata))
+               if (!dns_rdata_casecompare(&myrdata, rdata))
                        break;
        }
        dns_rdataset_disassociate(&rdataset);
@@ -2960,7 +2947,9 @@ rr_exists(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *name,
 }
 
 static isc_result_t
-get_iterations(dns_db_t *db, dns_dbversion_t *ver, unsigned int *iterationsp) {
+get_iterations(dns_db_t *db, dns_dbversion_t *ver, dns_rdatatype_t privatetype,
+              unsigned int *iterationsp)
+{
        dns_dbnode_t *node = NULL;
        dns_rdata_nsec3param_t nsec3param;
        dns_rdataset_t rdataset;
@@ -2974,7 +2963,33 @@ get_iterations(dns_db_t *db, dns_dbversion_t *ver, unsigned int *iterationsp) {
                return (result);
        result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param,
                                     0, (isc_stdtime_t) 0, &rdataset, NULL);
-       dns_db_detachnode(db, &node);
+       if (result == ISC_R_NOTFOUND)
+               goto try_private;
+       if (result != ISC_R_SUCCESS)
+               goto failure;
+
+       for (result = dns_rdataset_first(&rdataset);
+            result == ISC_R_SUCCESS;
+            result = dns_rdataset_next(&rdataset)) {
+               dns_rdata_t rdata = DNS_RDATA_INIT;
+               dns_rdataset_current(&rdataset, &rdata);
+               CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL));
+               if ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0)
+                       continue;
+               if (nsec3param.iterations > iterations)
+                       iterations = nsec3param.iterations;
+       }
+       if (result != ISC_R_NOMORE)
+               goto failure;
+
+       dns_rdataset_disassociate(&rdataset);
+
+ try_private:
+       if (privatetype == 0)
+               goto success;
+
+       result = dns_db_findrdataset(db, node, ver, privatetype,
+                                    0, (isc_stdtime_t) 0, &rdataset, NULL);
        if (result == ISC_R_NOTFOUND)
                goto success;
        if (result != ISC_R_SUCCESS)
@@ -2983,8 +2998,14 @@ get_iterations(dns_db_t *db, dns_dbversion_t *ver, unsigned int *iterationsp) {
        for (result = dns_rdataset_first(&rdataset);
             result == ISC_R_SUCCESS;
             result = dns_rdataset_next(&rdataset)) {
+               unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+               dns_rdata_t private = DNS_RDATA_INIT;
                dns_rdata_t rdata = DNS_RDATA_INIT;
+
                dns_rdataset_current(&rdataset, &rdata);
+               if (!dns_nsec3param_fromprivate(&private, &rdata,
+                                               buf, sizeof(buf)))
+                       continue;
                CHECK(dns_rdata_tostruct(&rdata, &nsec3param, NULL));
                if ((nsec3param.flags & DNS_NSEC3FLAG_REMOVE) != 0)
                        continue;
@@ -2999,6 +3020,8 @@ get_iterations(dns_db_t *db, dns_dbversion_t *ver, unsigned int *iterationsp) {
        result = ISC_R_SUCCESS;
 
  failure:
+       if (node != NULL)
+               dns_db_detachnode(db, &node);
        if (dns_rdataset_isassociated(&rdataset))
                dns_rdataset_disassociate(&rdataset);
        return (result);
@@ -3012,77 +3035,81 @@ static isc_result_t
 check_dnssec(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
             dns_dbversion_t *ver, dns_diff_t *diff)
 {
-       dns_diff_t temp_diff;
-       dns_diffop_t op;
-       dns_difftuple_t *tuple, *newtuple = NULL, *next;
-       isc_boolean_t flag;
+       dns_difftuple_t *tuple;
+       isc_boolean_t nseconly = ISC_FALSE, nsec3 = ISC_FALSE;
        isc_result_t result;
        unsigned int iterations = 0, max;
+       dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
 
-       dns_diff_init(diff->mctx, &temp_diff);
-
-       CHECK(dns_nsec_nseconly(db, ver, &flag));
+       /* Scan the tuples for an NSEC-only DNSKEY or an NSEC3PARAM */
+       for (tuple = ISC_LIST_HEAD(diff->tuples);
+            tuple != NULL;
+            tuple = ISC_LIST_NEXT(tuple, link)) {
+               if (tuple->op != DNS_DIFFOP_ADD)
+                       continue;
 
-       if (flag)
-               CHECK(dns_nsec3_active(db, ver, ISC_FALSE, &flag));
-       if (flag) {
-               update_log(client, zone, ISC_LOG_WARNING,
-                          "NSEC only DNSKEYs and NSEC3 chains not allowed");
-       } else {
-               CHECK(get_iterations(db, ver, &iterations));
-               CHECK(dns_nsec3_maxiterations(db, ver, client->mctx, &max));
-               if (iterations > max) {
-                       flag = ISC_TRUE;
-                       update_log(client, zone, ISC_LOG_WARNING,
-                                  "too many NSEC3 iterations (%u) for "
-                                  "weakest DNSKEY (%u)", iterations, max);
+               if (tuple->rdata.type == dns_rdatatype_dnskey) {
+                       isc_uint8_t alg;
+                       alg = tuple->rdata.data[3];
+                       if (alg == DST_ALG_RSAMD5 || alg == DST_ALG_RSASHA1 ||
+                           alg == DST_ALG_DSA || alg == DST_ALG_ECC) {
+                               nseconly = ISC_TRUE;
+                               break;
+                       }
+               } else if (tuple->rdata.type == dns_rdatatype_nsec3param) {
+                       nsec3 = ISC_TRUE;
+                       break;
                }
        }
-       if (flag) {
-               for (tuple = ISC_LIST_HEAD(diff->tuples);
-                    tuple != NULL;
-                    tuple = next) {
-                       next = ISC_LIST_NEXT(tuple, link);
-                       if (tuple->rdata.type != dns_rdatatype_dnskey &&
-                           tuple->rdata.type != dns_rdatatype_nsec3param)
-                               continue;
-                       op = (tuple->op == DNS_DIFFOP_DEL) ?
-                            DNS_DIFFOP_ADD : DNS_DIFFOP_DEL;
-                       CHECK(dns_difftuple_create(temp_diff.mctx, op,
-                                                  &tuple->name, tuple->ttl,
-                                                  &tuple->rdata, &newtuple));
-                       CHECK(do_one_tuple(&newtuple, db, ver, &temp_diff));
-                       INSIST(newtuple == NULL);
-               }
-               for (tuple = ISC_LIST_HEAD(temp_diff.tuples);
-                    tuple != NULL;
-                    tuple = ISC_LIST_HEAD(temp_diff.tuples)) {
-                       ISC_LIST_UNLINK(temp_diff.tuples, tuple, link);
-                       dns_diff_appendminimal(diff, &tuple);
-               }
+
+       /* Check existing DB for NSEC-only DNSKEY */
+       if (!nseconly)
+               CHECK(dns_nsec_nseconly(db, ver, &nseconly));
+
+       /* Check existing DB for NSEC3 */
+       if (!nsec3)
+               CHECK(dns_nsec3_activex(db, ver, ISC_FALSE,
+                                       privatetype, &nsec3));
+
+       /* Refuse to allow NSEC3 with NSEC-only keys */
+       if (nseconly && nsec3) {
+               update_log(client, zone, ISC_LOG_ERROR,
+                          "NSEC only DNSKEYs and NSEC3 chains not allowed");
+               result = DNS_R_REFUSED;
+               goto failure;
        }
 
+       /* Verify NSEC3 params */
+       CHECK(get_iterations(db, ver, privatetype, &iterations));
+       CHECK(dns_nsec3_maxiterations(db, ver, client->mctx, &max));
+       if (max != 0 && iterations > max) {
+               update_log(client, zone, ISC_LOG_ERROR,
+                          "too many NSEC3 iterations (%u) for "
+                          "weakest DNSKEY (%u)", iterations, max);
+               result = DNS_R_REFUSED;
+               goto failure;
+       }
 
  failure:
-       dns_diff_clear(&temp_diff);
        return (result);
 }
 
-#ifdef ALLOW_NSEC3PARAM_UPDATE
 /*
  * Delay NSEC3PARAM changes as they need to be applied to the whole zone.
  */
 static isc_result_t
 add_nsec3param_records(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
-                      dns_name_t *name, dns_dbversion_t *ver, dns_diff_t *diff)
+                      dns_dbversion_t *ver, dns_diff_t *diff)
 {
        isc_result_t result = ISC_R_SUCCESS;
        dns_difftuple_t *tuple, *newtuple = NULL, *next;
        dns_rdata_t rdata = DNS_RDATA_INIT;
-       unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+       unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE + 1];
        dns_diff_t temp_diff;
        dns_diffop_t op;
        isc_boolean_t flag;
+       dns_name_t *name = dns_zone_getorigin(zone);
+       dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);;
 
        update_log(client, zone, ISC_LOG_DEBUG(3),
                    "checking for NSEC3PARAM changes");
@@ -3140,12 +3167,10 @@ add_nsec3param_records(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                        /*
                         * See if we already have a CREATE request in progress.
                         */
-                       dns_rdata_clone(&tuple->rdata, &rdata);
-                       INSIST(rdata.length <= sizeof(buf));
-                       memcpy(buf, rdata.data, rdata.length);
-                       buf[1] |= DNS_NSEC3FLAG_CREATE;
-                       buf[1] &= ~DNS_NSEC3FLAG_UPDATE;
-                       rdata.data = buf;
+                       dns_nsec3param_toprivate(&tuple->rdata, &rdata,
+                                                privatetype, buf, sizeof(buf));
+                       buf[2] |= DNS_NSEC3FLAG_CREATE;
+                       buf[2] &= ~DNS_NSEC3FLAG_UPDATE;
 
                        CHECK(rr_exists(db, ver, name, &rdata, &flag));
 
@@ -3157,6 +3182,24 @@ add_nsec3param_records(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                                                           &newtuple));
                                CHECK(do_one_tuple(&newtuple, db, ver, diff));
                        }
+
+                       /*
+                        * Remove any existing CREATE request to add an
+                        * otherwise indentical chain with a reversed
+                        * OPTOUT state.
+                        */
+                       buf[2] ^= DNS_NSEC3FLAG_OPTOUT;
+                       CHECK(rr_exists(db, ver, name, &rdata, &flag));
+
+                       if (flag) {
+                               CHECK(dns_difftuple_create(diff->mctx,
+                                                          DNS_DIFFOP_DEL,
+                                                          name, tuple->ttl,
+                                                          &rdata,
+                                                          &newtuple));
+                               CHECK(do_one_tuple(&newtuple, db, ver, diff));
+                       }
+
                        /*
                         * Remove the temporary add record.
                         */
@@ -3199,11 +3242,9 @@ add_nsec3param_records(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
                /*
                 * See if we already have a REMOVE request in progress.
                 */
-               dns_rdata_clone(&tuple->rdata, &rdata);
-               INSIST(rdata.length <= sizeof(buf));
-               memcpy(buf, rdata.data, rdata.length);
-               buf[1] |= DNS_NSEC3FLAG_REMOVE;
-               rdata.data = buf;
+               dns_nsec3param_toprivate(&tuple->rdata, &rdata,
+                                        privatetype, buf, sizeof(buf));
+               buf[2] |= DNS_NSEC3FLAG_REMOVE;
 
                CHECK(rr_exists(db, ver, name, &rdata, &flag));
 
@@ -3227,15 +3268,74 @@ add_nsec3param_records(ns_client_t *client, dns_zone_t *zone, dns_db_t *db,
        dns_diff_clear(&temp_diff);
        return (result);
 }
-#endif
+
+static isc_result_t
+rollback_private(dns_db_t *db, dns_rdatatype_t privatetype,
+                dns_dbversion_t *ver, dns_diff_t *diff)
+{
+       dns_diff_t temp_diff;
+       dns_diffop_t op;
+       dns_difftuple_t *tuple, *newtuple = NULL, *next;
+       dns_name_t *name = dns_db_origin(db);
+       isc_mem_t *mctx = diff->mctx;
+       isc_result_t result;
+
+       if (privatetype == 0)
+               return (ISC_R_SUCCESS);
+
+       dns_diff_init(mctx, &temp_diff);
+
+       /*
+        * Extract the changes to be rolled back.
+        */
+       for (tuple = ISC_LIST_HEAD(diff->tuples);
+            tuple != NULL;
+            tuple = next) {
+
+               next = ISC_LIST_NEXT(tuple, link);
+
+               if (tuple->rdata.type != privatetype ||
+                   !dns_name_equal(name, &tuple->name))
+                       continue;
+
+               /*
+                * Allow records which indicate that a zone has been
+                * signed with a DNSKEY to be be removed.
+                */
+               if (tuple->op == DNS_DIFFOP_DEL &&
+                   tuple->rdata.length == 5 &&
+                   tuple->rdata.data[0] != 0 &&
+                   tuple->rdata.data[4] != 0)
+                       continue;
+
+               ISC_LIST_UNLINK(diff->tuples, tuple, link);
+               ISC_LIST_PREPEND(temp_diff.tuples, tuple, link);
+       }
+
+       /*
+        * Rollback the changes.
+        */
+       while ((tuple = ISC_LIST_HEAD(temp_diff.tuples)) != NULL) {
+               op = (tuple->op == DNS_DIFFOP_DEL) ?
+                     DNS_DIFFOP_ADD : DNS_DIFFOP_DEL;
+               CHECK(dns_difftuple_create(mctx, op, name, tuple->ttl,
+                                          &tuple->rdata, &newtuple));
+               CHECK(do_one_tuple(&newtuple, db, ver, &temp_diff));
+       }
+       result = ISC_R_SUCCESS;
+
+ failure:
+       dns_diff_clear(&temp_diff);
+       return (result);
+}
 
 /*
  * Add records to cause the delayed signing of the zone by added DNSKEY
  * to remove the RRSIG records generated by a deleted DNSKEY.
  */
 static isc_result_t
-add_signing_records(dns_db_t *db, dns_name_t *name, dns_dbversion_t *ver,
-                   dns_rdatatype_t privatetype, dns_diff_t *diff)
+add_signing_records(dns_db_t *db, dns_rdatatype_t privatetype,
+                   dns_dbversion_t *ver, dns_diff_t *diff)
 {
        dns_difftuple_t *tuple, *newtuple = NULL;
        dns_rdata_dnskey_t dnskey;
@@ -3245,6 +3345,7 @@ add_signing_records(dns_db_t *db, dns_name_t *name, dns_dbversion_t *ver,
        isc_result_t result = ISC_R_SUCCESS;
        isc_uint16_t keyid;
        unsigned char buf[5];
+       dns_name_t *name = dns_db_origin(db);
 
        for (tuple = ISC_LIST_HEAD(diff->tuples);
             tuple != NULL;
@@ -3259,6 +3360,7 @@ add_signing_records(dns_db_t *db, dns_name_t *name, dns_dbversion_t *ver,
                        continue;
 
                dns_rdata_toregion(&tuple->rdata, &r);
+
                keyid = dst_region_computeid(&r, dnskey.algorithm);
 
                buf[0] = dnskey.algorithm;
@@ -3295,83 +3397,19 @@ add_signing_records(dns_db_t *db, dns_name_t *name, dns_dbversion_t *ver,
        return (result);
 }
 
-#ifdef ALLOW_NSEC3PARAM_UPDATE
-/*
- * Mark all NSEC3 chains for deletion without creating a NSEC chain as
- * a side effect of deleting the last chain.
- */
-static isc_result_t
-delete_chains(dns_db_t *db, dns_dbversion_t *ver, dns_name_t *origin,
-             dns_diff_t *diff)
-{
-       dns_dbnode_t *node = NULL;
-       dns_difftuple_t *tuple = NULL;
-       dns_name_t next;
-       dns_rdata_t rdata = DNS_RDATA_INIT;
-       dns_rdataset_t rdataset;
-       isc_boolean_t flag;
-       isc_result_t result = ISC_R_SUCCESS;
-       unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
-
-       dns_name_init(&next, NULL);
-       dns_rdataset_init(&rdataset);
-
-       result = dns_db_getoriginnode(db, &node);
-       if (result != ISC_R_SUCCESS)
-               return (result);
-
-       /*
-        * Cause all NSEC3 chains to be deleted.
-        */
-       result = dns_db_findrdataset(db, node, ver, dns_rdatatype_nsec3param,
-                                    0, (isc_stdtime_t) 0, &rdataset, NULL);
-       if (result == ISC_R_NOTFOUND)
-               goto success;
-       if (result != ISC_R_SUCCESS)
-               goto failure;
-
-       for (result = dns_rdataset_first(&rdataset);
-            result == ISC_R_SUCCESS;
-            result = dns_rdataset_next(&rdataset)) {
-               dns_rdataset_current(&rdataset, &rdata);
-               INSIST(rdata.length <= sizeof(buf));
-               memcpy(buf, rdata.data, rdata.length);
-
-               if (buf[1] == (DNS_NSEC3FLAG_REMOVE | DNS_NSEC3FLAG_NONSEC)) {
-                       dns_rdata_reset(&rdata);
-                       continue;
-               }
-
-               CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_DEL,
-                                          origin, 0, &rdata, &tuple));
-               CHECK(do_one_tuple(&tuple, db, ver, diff));
-               INSIST(tuple == NULL);
-
-               buf[1] = DNS_NSEC3FLAG_REMOVE | DNS_NSEC3FLAG_NONSEC;
-               rdata.data = buf;
-
-               CHECK(rr_exists(db, ver, origin, &rdata, &flag));
+static isc_boolean_t
+isdnssec(dns_db_t *db, dns_dbversion_t *ver, dns_rdatatype_t privatetype) {
+       isc_result_t result;
+       isc_boolean_t build_nsec, build_nsec3;
 
-               if (!flag) {
-                       CHECK(dns_difftuple_create(diff->mctx, DNS_DIFFOP_ADD,
-                                                  origin, 0, &rdata, &tuple));
-                       CHECK(do_one_tuple(&tuple, db, ver, diff));
-                       INSIST(tuple == NULL);
-               }
-               dns_rdata_reset(&rdata);
-       }
-       if (result != ISC_R_NOMORE)
-               goto failure;
- success:
-       result = ISC_R_SUCCESS;
+       if (dns_db_issecure(db))
+               return (ISC_TRUE);
 
- failure:
-       if (dns_rdataset_isassociated(&rdataset))
-               dns_rdataset_disassociate(&rdataset);
-       dns_db_detachnode(db, &node);
-       return (result);
+       result = dns_private_chains(db, ver, privatetype,
+                                   &build_nsec, &build_nsec3);
+       RUNTIME_CHECK(result == ISC_R_SUCCESS);
+       return (build_nsec || build_nsec3);
 }
-#endif
 
 static void
 update_action(isc_task_t *task, isc_event_t *event) {
@@ -3395,15 +3433,11 @@ update_action(isc_task_t *task, isc_event_t *event) {
        dns_fixedname_t tmpnamefixed;
        dns_name_t *tmpname = NULL;
        unsigned int options;
-       isc_boolean_t deleted_zsk;
        dns_difftuple_t *tuple;
        dns_rdata_dnskey_t dnskey;
-#ifdef ALLOW_NSEC3PARAM_UPDATE
        unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
-#endif
-#if !defined(ALLOW_SECURE_TO_INSECURE) || !defined(ALLOW_INSECURE_TO_SECURE)
        isc_boolean_t had_dnskey;
-#endif
+       dns_rdatatype_t privatetype = dns_zone_getprivatetype(zone);
 
        INSIST(event->ev_type == DNS_EVENT_UPDATE);
 
@@ -3414,6 +3448,18 @@ update_action(isc_task_t *task, isc_event_t *event) {
        zonename = dns_db_origin(db);
        zoneclass = dns_db_class(db);
        dns_zone_getssutable(zone, &ssutable);
+
+       /*
+        * Update message processing can leak record existance information
+        * so check that we are allowed to query this zone.  Additionally
+        * if we would refuse all updates for this zone we bail out here.
+        */
+       CHECK(checkqueryacl(client, dns_zone_getqueryacl(zone), zonename,
+                           dns_zone_getupdateacl(zone), ssutable));
+
+       /*
+        * Get old and new versions now that queryacl has been checked.
+        */
        dns_db_currentversion(db, &oldver);
        CHECK(dns_db_newversion(db, &ver));
 
@@ -3506,7 +3552,6 @@ update_action(isc_task_t *task, isc_event_t *event) {
        if (result != ISC_R_NOMORE)
                FAIL(result);
 
-
        /*
         * Perform the final check of the "rrset exists (value dependent)"
         * prerequisites.
@@ -3600,27 +3645,26 @@ update_action(isc_task_t *task, isc_event_t *event) {
                                   update_class);
                        FAIL(DNS_R_FORMERR);
                }
+
                /*
                 * draft-ietf-dnsind-simple-secure-update-01 says
                 * "Unlike traditional dynamic update, the client
                 * is forbidden from updating NSEC records."
                 */
-               if (dns_db_issecure(db)) {
-                       if (rdata.type == dns_rdatatype_nsec3) {
-                               FAILC(DNS_R_REFUSED,
-                                     "explicit NSEC3 updates are not allowed "
-                                     "in secure zones");
-                       } else if (rdata.type == dns_rdatatype_nsec) {
-                               FAILC(DNS_R_REFUSED,
-                                     "explicit NSEC updates are not allowed "
-                                     "in secure zones");
-                       } else if (rdata.type == dns_rdatatype_rrsig &&
-                                  !dns_name_equal(name, zonename)) {
-                               FAILC(DNS_R_REFUSED,
-                                     "explicit RRSIG updates are currently "
-                                     "not supported in secure zones except "
-                                     "at the apex");
-                       }
+               if (rdata.type == dns_rdatatype_nsec3) {
+                       FAILC(DNS_R_REFUSED,
+                             "explicit NSEC3 updates are not allowed "
+                             "in secure zones");
+               } else if (rdata.type == dns_rdatatype_nsec) {
+                       FAILC(DNS_R_REFUSED,
+                             "explicit NSEC updates are not allowed "
+                             "in secure zones");
+               } else if (rdata.type == dns_rdatatype_rrsig &&
+                          !dns_name_equal(name, zonename)) {
+                       FAILC(DNS_R_REFUSED,
+                             "explicit RRSIG updates are currently "
+                             "not supported in secure zones except "
+                             "at the apex");
                }
 
                if (ssutable != NULL) {
@@ -3755,7 +3799,14 @@ update_action(isc_task_t *task, isc_event_t *event) {
                                soa_serial_changed = ISC_TRUE;
                        }
 
-#ifdef ALLOW_NSEC3PARAM_UPDATE
+                       if (rdata.type == privatetype) {
+                               update_log(client, zone, LOGLEVEL_PROTOCOL,
+                                          "attempt to add a private type "
+                                          "(%u) record rejected internal "
+                                          "use only", privatetype);
+                               continue;
+                       }
+
                        if (rdata.type == dns_rdatatype_nsec3param) {
                                /*
                                 * Ignore attempts to add NSEC3PARAM records
@@ -3771,25 +3822,18 @@ update_action(isc_task_t *task, isc_event_t *event) {
                                }
 
                                /*
-                                * Set the NSEC3CHAIN creation flag.
+                                * NSEC3CHAIN creation flag.
                                 */
                                INSIST(rdata.length <= sizeof(buf));
                                memcpy(buf, rdata.data, rdata.length);
                                buf[1] |= DNS_NSEC3FLAG_UPDATE;
                                rdata.data = buf;
+
                                /*
                                 * Force the TTL to zero for NSEC3PARAM records.
                                 */
                                ttl = 0;
                        }
-#else
-                       if (rdata.type == dns_rdatatype_nsec3param) {
-                               update_log(client, zone, LOGLEVEL_PROTOCOL,
-                                          "attempt to add NSEC3PARAM "
-                                          "record ignored");
-                               continue;
-                       };
-#endif
 
                        if ((options & DNS_ZONEOPT_CHECKWILDCARD) != 0 &&
                            dns_name_internalwildcard(name)) {
@@ -3866,13 +3910,6 @@ update_action(isc_task_t *task, isc_event_t *event) {
                                                        dns_rdatatype_any, 0,
                                                        &rdata, &diff));
                                }
-#ifndef ALLOW_NSEC3PARAM_UPDATE
-                       } else if (rdata.type == dns_rdatatype_nsec3param) {
-                               update_log(client, zone, LOGLEVEL_PROTOCOL,
-                                          "attempt to delete a NSEC3PARAM "
-                                          "records ignored");
-                               continue;
-#endif
                        } else if (dns_name_equal(name, zonename) &&
                                   (rdata.type == dns_rdatatype_soa ||
                                    rdata.type == dns_rdatatype_ns)) {
@@ -3901,6 +3938,9 @@ update_action(isc_task_t *task, isc_event_t *event) {
                                                &diff));
                        }
                } else if (update_class == dns_rdataclass_none) {
+                       char namestr[DNS_NAME_FORMATSIZE];
+                       char typestr[DNS_RDATATYPE_FORMATSIZE];
+
                        /*
                         * The (name == zonename) condition appears in
                         * RFC2136 3.4.2.4 but is missing from the pseudocode.
@@ -3928,11 +3968,13 @@ update_action(isc_task_t *task, isc_event_t *event) {
                                        }
                                }
                        }
-                       update_log(client, zone,
-                                  LOGLEVEL_PROTOCOL,
-                                  "deleting an RR");
-                       CHECK(delete_if(rr_equal_p, db, ver, name,
-                                       rdata.type, covers, &rdata, &diff));
+                       dns_name_format(name, namestr, sizeof(namestr));
+                       dns_rdatatype_format(rdata.type, typestr,
+                                            sizeof(typestr));
+                       update_log(client, zone, LOGLEVEL_PROTOCOL,
+                                  "deleting an RR at %s %s", namestr, typestr);
+                       CHECK(delete_if(rr_equal_p, db, ver, name, rdata.type,
+                                       covers, &rdata, &diff));
                }
        }
        if (result != ISC_R_NOMORE)
@@ -3946,6 +3988,18 @@ update_action(isc_task_t *task, isc_event_t *event) {
        if (! ISC_LIST_EMPTY(diff.tuples))
                CHECK(check_dnssec(client, zone, db, ver, &diff));
 
+       if (! ISC_LIST_EMPTY(diff.tuples)) {
+               unsigned int errors = 0;
+               CHECK(dns_zone_nscheck(zone, db, ver, &errors));
+               if (errors != 0) {
+                       update_log(client, zone, LOGLEVEL_PROTOCOL,
+                                  "update rejected: post update name server "
+                                  "sanity check failed");
+                       result = DNS_R_REFUSED;
+                       goto failure;
+               }
+       }
+
        /*
         * If any changes were made, increment the SOA serial number,
         * update RRSIGs and NSECs (if zone is secure), and write the update
@@ -3971,37 +4025,29 @@ update_action(isc_task_t *task, isc_event_t *event) {
                CHECK(rrset_exists(db, ver, zonename, dns_rdatatype_dnskey,
                                   0, &has_dnskey));
 
-#if !defined(ALLOW_SECURE_TO_INSECURE) || !defined(ALLOW_INSECURE_TO_SECURE)
-               CHECK(rrset_exists(db, oldver, zonename, dns_rdatatype_dnskey,
-                                  0, &had_dnskey));
+#define ALLOW_SECURE_TO_INSECURE(zone) \
+       ((dns_zone_getoptions(zone) & DNS_ZONEOPT_SECURETOINSECURE) != 0)
 
-#ifndef ALLOW_SECURE_TO_INSECURE
-               if (had_dnskey && !has_dnskey) {
-                       update_log(client, zone, LOGLEVEL_PROTOCOL,
-                                  "update rejected: all DNSKEY records "
-                                  "removed");
-                       result = DNS_R_REFUSED;
-                       goto failure;
-               }
-#endif
-#ifndef ALLOW_INSECURE_TO_SECURE
-               if (!had_dnskey && has_dnskey) {
-                       update_log(client, zone, LOGLEVEL_PROTOCOL,
-                                  "update rejected: DNSKEY record added");
-                       result = DNS_R_REFUSED;
-                       goto failure;
+               if (!ALLOW_SECURE_TO_INSECURE(zone)) {
+                       CHECK(rrset_exists(db, oldver, zonename,
+                                          dns_rdatatype_dnskey, 0,
+                                          &had_dnskey));
+                       if (had_dnskey && !has_dnskey) {
+                               update_log(client, zone, LOGLEVEL_PROTOCOL,
+                                          "update rejected: all DNSKEY "
+                                          "records removed and "
+                                          "'dnssec-secure-to-insecure' "
+                                          "not set");
+                               result = DNS_R_REFUSED;
+                               goto failure;
+                       }
                }
-#endif
-#endif
 
-               CHECK(add_signing_records(db, zonename, ver,
-                                         dns_zone_getprivatetype(zone),
-                                         &diff));
+               CHECK(rollback_private(db, privatetype, ver, &diff));
 
-#ifdef ALLOW_NSEC3PARAM_UPDATE
-               CHECK(add_nsec3param_records(client, zone, db, zonename,
-                                            ver, &diff));
-#endif
+               CHECK(add_signing_records(db, privatetype, ver, &diff));
+
+               CHECK(add_nsec3param_records(client, zone, db, ver, &diff));
 
                if (!has_dnskey) {
                        /*
@@ -4010,15 +4056,13 @@ update_action(isc_task_t *task, isc_event_t *event) {
                         * the last signature for the DNSKEY records are
                         * remove any NSEC chain present will also be removed.
                         */
-#ifdef ALLOW_NSEC3PARAM_UPDATE
-                        CHECK(delete_chains(db, ver, zonename, &diff));
-#endif
-               } else if (has_dnskey && dns_db_isdnssec(db)) {
+                        CHECK(dns_nsec3param_deletechains(db, ver, zone,
+                                                          &diff));
+               } else if (has_dnskey && isdnssec(db, ver, privatetype)) {
                        isc_uint32_t interval;
                        interval = dns_zone_getsigvalidityinterval(zone);
                        result = update_signatures(client, zone, db, oldver,
-                                                  ver, &diff, interval,
-                                                  &deleted_zsk);
+                                                  ver, &diff, interval);
                        if (result != ISC_R_SUCCESS) {
                                update_log(client, zone,
                                           ISC_LOG_ERROR,
@@ -4104,7 +4148,6 @@ update_action(isc_task_t *task, isc_event_t *event) {
                        }
                }
 
-#ifdef ALLOW_NSEC3PARAM_UPDATE
                /*
                 * Cause the zone to add/delete NSEC3 chains for the
                 * deferred NSEC3PARAM changes.
@@ -4114,13 +4157,18 @@ update_action(isc_task_t *task, isc_event_t *event) {
                for (tuple = ISC_LIST_HEAD(diff.tuples);
                     tuple != NULL;
                     tuple = ISC_LIST_NEXT(tuple, link)) {
+                       unsigned char buf[DNS_NSEC3PARAM_BUFFERSIZE];
+                       dns_rdata_t rdata = DNS_RDATA_INIT;
                        dns_rdata_nsec3param_t nsec3param;
 
-                       if (tuple->rdata.type != dns_rdatatype_nsec3param ||
+                       if (tuple->rdata.type != privatetype ||
                            tuple->op != DNS_DIFFOP_ADD)
                                continue;
 
-                       dns_rdata_tostruct(&tuple->rdata, &nsec3param, NULL);
+                       if (!dns_nsec3param_fromprivate(&tuple->rdata, &rdata,
+                                                  buf, sizeof(buf)))
+                               continue;
+                       dns_rdata_tostruct(&rdata, &nsec3param, NULL);
                        if (nsec3param.flags == 0)
                                continue;
 
@@ -4131,7 +4179,6 @@ update_action(isc_task_t *task, isc_event_t *event) {
                                           dns_result_totext(result));
                        }
                }
-#endif
        } else {
                update_log(client, zone, LOGLEVEL_DEBUG, "redundant request");
                dns_db_closeversion(db, &ver, ISC_TRUE);
@@ -4140,9 +4187,6 @@ update_action(isc_task_t *task, isc_event_t *event) {
        goto common;
 
  failure:
-       if (result == DNS_R_REFUSED)
-               inc_stats(zone, dns_nsstatscounter_updaterej);
-
        /*
         * The reason for failure should have been logged at this point.
         */