diff --git a/bin/named/server.c b/bin/named/server.c index dd2740f42d..2cb60e46c0 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -2669,7 +2669,6 @@ catz_addmodzone_taskaction(isc_task_t *task, isc_event_t *event0) { dns_name_totext(dns_catz_entry_getname(ev->entry), true, &namebuf); isc_buffer_putuint8(&namebuf, 0); - /* Zone shouldn't already exist */ result = dns_zt_find(ev->view->zonetable, dns_catz_entry_getname(ev->entry), 0, NULL, &zone); @@ -2680,7 +2679,7 @@ catz_addmodzone_taskaction(isc_task_t *task, isc_event_t *event0) { isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, "catz: error \"%s\" while trying to " - "modify zone \"%s\"", + "modify zone '%s'", isc_result_totext(result), nameb); goto cleanup; } @@ -2718,19 +2717,37 @@ catz_addmodzone_taskaction(isc_task_t *task, isc_event_t *event0) { dns_zone_detach(&zone); } else { + /* Zone shouldn't already exist when adding */ if (result == ISC_R_SUCCESS) { - isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, - NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, - "catz: zone \"%s\" is overridden " - "by explicitly configured zone", - nameb); + if (dns_zone_get_parentcatz(zone) == NULL) { + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "catz: " + "catz_addmodzone_taskaction: " + "zone '%s' will not be added " + "because it is an explicitly " + "configured zone", + nameb); + } else { + isc_log_write( + named_g_lctx, NAMED_LOGCATEGORY_GENERAL, + NAMED_LOGMODULE_SERVER, ISC_LOG_INFO, + "catz: " + "catz_addmodzone_taskaction: " + "zone '%s' will not be added " + "because another catalog zone " + "already contains an entry with " + "that zone", + nameb); + } goto cleanup; } else if (result != ISC_R_NOTFOUND && result != DNS_R_PARTIALMATCH) { isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, "catz: error \"%s\" while trying to " - "add zone \"%s\"", + "add zone '%s'", isc_result_totext(result), nameb); goto cleanup; } else { /* this can happen in case of DNS_R_PARTIALMATCH */ @@ -2757,7 +2774,7 @@ catz_addmodzone_taskaction(isc_task_t *task, isc_event_t *event0) { isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, ISC_LOG_ERROR, "catz: error \"%s\" while trying to generate " - "config for zone \"%s\"", + "config for zone '%s'", isc_result_totext(result), nameb); goto cleanup; } @@ -2784,8 +2801,8 @@ catz_addmodzone_taskaction(isc_task_t *task, isc_event_t *event0) { if (result != ISC_R_SUCCESS) { isc_log_write(named_g_lctx, NAMED_LOGCATEGORY_GENERAL, NAMED_LOGMODULE_SERVER, ISC_LOG_WARNING, - "catz: failed to configure zone \"%s\" - %d", - nameb, result); + "catz: failed to configure zone '%s' - %d", nameb, + result); goto cleanup; } diff --git a/bin/tests/system/catz/.gitignore b/bin/tests/system/catz/.gitignore index b5ba95a12a..aaae88df39 100644 --- a/bin/tests/system/catz/.gitignore +++ b/bin/tests/system/catz/.gitignore @@ -10,6 +10,7 @@ /ns2/catalog*.example.db /ns1/*dom*.example.db +/ns3/dom2.example.db /ns3/dom13.example.db /ns3/dom14.example.db /ns3/dom17.example.db diff --git a/bin/tests/system/catz/clean.sh b/bin/tests/system/catz/clean.sh index 375587cc00..783ed4e763 100644 --- a/bin/tests/system/catz/clean.sh +++ b/bin/tests/system/catz/clean.sh @@ -20,7 +20,7 @@ rm -f ns*/named.run.prev rm -f ns1/*dom*example.db rm -f ns2/__catz__*db rm -f ns2/named.conf.tmp -rm -f ns3/dom13.example.db ns3/dom14.example.db ns3/dom17.example.db ns3/dom18.example.db +rm -f ns3/dom2.example.db ns3/dom13.example.db ns3/dom14.example.db ns3/dom17.example.db ns3/dom18.example.db rm -f nsupdate.out.* rm -f ns[123]/catalog[1234].example.db rm -rf ns2/zonedir diff --git a/bin/tests/system/catz/ns2/dom-existing.example.db b/bin/tests/system/catz/ns2/dom-existing.example.db new file mode 100644 index 0000000000..b8402de301 --- /dev/null +++ b/bin/tests/system/catz/ns2/dom-existing.example.db @@ -0,0 +1,13 @@ +; 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. + +@ 3600 SOA . . 1 86400 3600 86400 3600 +@ 3600 IN NS invalid. diff --git a/bin/tests/system/catz/ns2/named1.conf.in b/bin/tests/system/catz/ns2/named1.conf.in index 041264832c..ed7f87943b 100644 --- a/bin/tests/system/catz/ns2/named1.conf.in +++ b/bin/tests/system/catz/ns2/named1.conf.in @@ -54,6 +54,11 @@ options { #T3 database "dlopen bad-dlz.so example.org"; #T3}; +zone "dom-existing.example" { + type primary; + file "dom-existing.example.db"; +}; + zone "catalog1.example" { type secondary; file "catalog1.example.db"; diff --git a/bin/tests/system/catz/ns2/named2.conf.in b/bin/tests/system/catz/ns2/named2.conf.in index b6f0745ffc..12884c6797 100644 --- a/bin/tests/system/catz/ns2/named2.conf.in +++ b/bin/tests/system/catz/ns2/named2.conf.in @@ -32,6 +32,11 @@ options { # identical to named1.conf.in }; +zone "dom-existing.example" { + type primary; + file "dom-existing.example.db"; +}; + zone "catalog1.example" { type secondary; file "catalog1.example.db"; diff --git a/bin/tests/system/catz/tests.sh b/bin/tests/system/catz/tests.sh index ae17b72e42..6785dd84e9 100644 --- a/bin/tests/system/catz/tests.sh +++ b/bin/tests/system/catz/tests.sh @@ -266,10 +266,21 @@ echo_i "adding domain dom2.example. to primary via RNDC ($n)" ret=0 echo "@ 3600 IN SOA . . 1 3600 3600 3600 3600" > ns1/dom2.example.db echo "@ IN NS invalid." >> ns1/dom2.example.db +echo "@ IN A 192.0.2.1" >> ns1/dom2.example.db rndccmd 10.53.0.1 addzone dom2.example. '{type primary; file "dom2.example.db";};' || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status+ret)) +n=$((n+1)) +echo_i "adding domain dom2.example. to primary ns3 via RNDC ($n)" +ret=0 +echo "@ 3600 IN SOA . . 1 3600 3600 3600 3600" > ns3/dom2.example.db +echo "@ IN NS invalid." >> ns3/dom2.example.db +echo "@ IN A 192.0.2.2" >> ns3/dom2.example.db +rndccmd 10.53.0.3 addzone dom2.example. '{type primary; file "dom2.example.db";};' || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + n=$((n+1)) echo_i "adding domain dom4.example. to primary via RNDC ($n)" ret=0 @@ -296,7 +307,6 @@ $NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 update add blahblah.636722929740e507aaf27c502812fc395d30fb17.zones.catalog1.example. 3600 IN TXT "blah blah" update add version.catalog1.example. 3600 IN A 1.2.3.4 send - END if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status+ret)) @@ -369,6 +379,166 @@ wait_for_soa @10.53.0.2 dom3.example. dig.out.test$n || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status+ret)) +nextpart ns2/named.run >/dev/null + +n=$((n+1)) +echo_i "adding domain dom2.example. to catalog2 zone to test change of ownership ($n)" +ret=0 +$NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.3 ${PORT} + update add dom2-without-coo.zones.catalog2.example. 3600 IN PTR dom2.example. + update add primaries.dom2-without-coo.zones.catalog2.example. 3600 IN A 10.53.0.3 + send +END +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "waiting for secondary to sync up ($n)" +ret=0 +wait_for_message ns2/named.run "catz: adding zone 'dom2.example' from catalog 'catalog2.example'" || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "checking that unpermitted change of ownership did not happen ($n)" +ret=0 +wait_for_message ns2/named.run "catz_addmodzone_taskaction: zone 'dom2.example' will not be added because another catalog zone already contains an entry with that zone" || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "checking that dom2.example. is served by secondary and that it's the one from ns1 ($n)" +ret=0 +wait_for_a @10.53.0.2 dom2.example. dig.out.test$n || ret=1 +grep "192.0.2.1" dig.out.test$n > /dev/null || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +nextpart ns2/named.run >/dev/null + +n=$((n+1)) +echo_i "adding change of ownership permission record for dom2.example. into catalog1 zone ($n)" +ret=0 +$NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.1 ${PORT} + update add coo.636722929740e507aaf27c502812fc395d30fb17.zones.catalog1.example. 3600 IN PTR catalog2.example. + send +END +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "waiting for secondary to sync up ($n)" +ret=0 +wait_for_message ns2/named.run "catz: updating catalog zone 'catalog1.example'" && +wait_for_message ns2/named.run "catz: update_from_db: new zone merged" || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "updating catalog2 zone to initiate a zone transfer ($n)" +ret=0 +$NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.3 ${PORT} + update delete dom2-without-coo.zones.catalog2.example. 3600 IN PTR dom2.example. + update delete primaries.dom2-without-coo.zones.catalog2.example. 3600 IN A 10.53.0.3 + update add dom2-with-coo.zones.catalog2.example. 3600 IN PTR dom2.example. + update add primaries.dom2-with-coo.zones.catalog2.example. 3600 IN A 10.53.0.3 + send +END +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "waiting for secondary to sync up and checking that the change of ownership was successful ($n)" +ret=0 +wait_for_message ns2/named.run "catz: zone 'dom2.example' change of ownership from 'catalog1.example' to 'catalog2.example'" && +wait_for_message ns2/named.run "catz: deleting zone 'dom2.example' from catalog 'catalog1.example' - success" && +wait_for_message ns2/named.run "catz: adding zone 'dom2.example' from catalog 'catalog2.example'" && +wait_for_message ns2/named.run "transfer of 'dom2.example/IN' from 10.53.0.3#${PORT}: Transfer status: success" || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "checking that dom2.example. is served by secondary and that it's now the one from ns3 ($n)" +ret=0 +wait_for_a @10.53.0.2 dom2.example. dig.out.test$n || ret=1 +grep "192.0.2.2" dig.out.test$n > /dev/null || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +nextpart ns2/named.run >/dev/null + +n=$((n+1)) +echo_i "removing dom2.example. and its change of ownership permission record from catalog1 zone ($n)" +ret=0 +$NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.1 ${PORT} + update delete 636722929740e507aaf27c502812fc395d30fb17.zones.catalog1.example. 3600 IN PTR dom2.example. + update delete coo.636722929740e507aaf27c502812fc395d30fb17.zones.catalog1.example. 3600 IN PTR catalog2.example. + send +END +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "waiting for secondary to sync up ($n)" +ret=0 +wait_for_message ns2/named.run "catz: update_from_db: iteration finished" || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +nextpart ns2/named.run >/dev/null + +n=$((n+1)) +echo_i "adding change of ownership permission record for dom2.example. into catalog2 zone ($n)" +ret=0 +$NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.3 ${PORT} + update add coo.dom2-with-coo.zones.catalog2.example. 3600 IN PTR catalog1.example. + send +END +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "waiting for secondary to sync up ($n)" +ret=0 +wait_for_message ns2/named.run "catz: update_from_db: iteration finished" || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +nextpart ns2/named.run >/dev/null + +n=$((n+1)) +echo_i "adding back dom2.example. into catalog1 zone ($n)" +ret=0 +$NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.1 ${PORT} + update add 636722929740e507aaf27c502812fc395d30fb17.zones.catalog1.example. 3600 IN PTR dom2.example. + send +END +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "checking that the change of ownership did not happen because version '1' catalog2 zone does not support the 'coo' property ($n)" +ret=0 +wait_for_message ns2/named.run "catz_addmodzone_taskaction: zone 'dom2.example' will not be added because another catalog zone already contains an entry with that zone" || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "checking that dom2.example. is still served by secondary and that it's still the one from ns3 ($n)" +ret=0 +wait_for_a @10.53.0.2 dom2.example. dig.out.test$n || ret=1 +grep "192.0.2.2" dig.out.test$n > /dev/null || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +nextpart ns2/named.run >/dev/null + n=$((n+1)) echo_i "reconfiguring secondary - checking if catz survives a certain class of failed reconfiguration attempts ($n)" ret=0 @@ -393,6 +563,52 @@ rndccmd 10.53.0.2 reconfig || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status+ret)) +nextpart ns2/named.run >/dev/null + +n=$((n+1)) +echo_i "adding a domain dom-existing.example. to primary via RNDC ($n)" +ret=0 +echo "@ 3600 IN SOA . . 1 3600 3600 3600 3600" > ns1/dom-existing.example.db +echo "@ IN NS invalid." >> ns1/dom-existing.example.db +echo "@ IN A 192.0.2.1" >> ns1/dom-existing.example.db +rndccmd 10.53.0.1 addzone dom-existing.example. '{type primary; file "dom-existing.example.db"; also-notify { 10.53.0.2; }; notify explicit; };' || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "checking that dom-existing.example. is served by primary ($n)" +ret=0 +wait_for_a @10.53.0.1 dom-existing.example. dig.out.test$n || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "adding domain dom-existing.example. to catalog1 zone to test that existing zones don't get overwritten ($n)" +ret=0 +$NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 + server 10.53.0.1 ${PORT} + update add dom-existing.zones.catalog1.example. 3600 IN PTR dom-existing.example. + send +END +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "waiting for secondary to sync up ($n)" +ret=0 +wait_for_message ns2/named.run "catz: adding zone 'dom-existing.example' from catalog 'catalog1.example'" && +wait_for_message ns2/named.run "catz_addmodzone_taskaction: zone 'dom-existing.example' will not be added because it is an explicitly configured zone" || ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + +n=$((n+1)) +echo_i "checking that dom-existing.example. is served by secondary and that it's not the one from the primary ns1 ($n)" +ret=0 +wait_for_a @10.53.0.2 dom-existing.example. dig.out.test$n || ret=1 +grep "192.0.2.1" dig.out.test$n > /dev/null && ret=1 +if [ $ret -ne 0 ]; then echo_i "failed"; fi +status=$((status+ret)) + n=$((n+1)) echo_i "removing all records from catalog1 zone ($n)" ret=0 @@ -409,8 +625,8 @@ $NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 update delete foobarbaz.b901f492f3ebf6c1e5b597e51766f02f0479eb03.zones.catalog1.example. 3600 IN APL 1:1.2.3.4/30 update delete blahblah.636722929740e507aaf27c502812fc395d30fb17.zones.catalog1.example. 3600 IN TXT "blah blah" update delete version.catalog1.example. 3600 IN A 1.2.3.4 + update delete dom-existing.zones.catalog1.example. 3600 IN PTR dom-existing.example. send - END if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status+ret)) @@ -420,6 +636,9 @@ echo_i "removing all records from catalog2 zone ($n)" ret=0 $NSUPDATE -d <> nsupdate.out.test$n 2>&1 || ret=1 server 10.53.0.3 ${PORT} + update delete dom2-with-coo.zones.catalog2.example. 3600 IN PTR dom2.example. + update delete primaries.dom2-with-coo.zones.catalog2.example. 3600 IN A 10.53.0.3 + update delete coo.dom2-with-coo.zones.catalog2.example. 3600 IN PTR catalog1.example. update delete de26b88d855397a03f77ff1162fd055d8b419584.zones.catalog2.example. 3600 IN PTR dom4.example. send END @@ -557,7 +776,7 @@ n=$((n+1)) echo_i "waiting for secondary to sync up ($n)" ret=0 wait_for_message ns2/named.run "catz: adding zone 'dom6.example' from catalog 'catalog1.example'" && -wait_for_message ns2/named.run "error \"failure\" while trying to generate config for zone \"dom6.example\"" || ret=1 +wait_for_message ns2/named.run "error \"failure\" while trying to generate config for zone 'dom6.example'" || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status+ret)) @@ -847,7 +1066,7 @@ n=$((n+1)) echo_i "waiting for secondary to sync up ($n)" ret=0 wait_for_message ns2/named.run "catz: adding zone 'dom9.example' from catalog 'catalog1.example'" && -wait_for_message ns2/named.run "error \"failure\" while trying to generate config for zone \"dom9.example\"" || ret=1 +wait_for_message ns2/named.run "error \"failure\" while trying to generate config for zone 'dom9.example'" || ret=1 if [ $ret -ne 0 ]; then echo_i "failed"; fi status=$((status+ret)) diff --git a/doc/arm/catz.rst b/doc/arm/catz.rst index 70521d33eb..0a13fc8655 100644 --- a/doc/arm/catz.rst +++ b/doc/arm/catz.rst @@ -261,4 +261,34 @@ Note that none of the global records for a custom property are inherited if any records are defined for that custom property for the specific zone. For example, if the zone had a ``primaries`` record of type A but not AAAA, it would *not* inherit the type AAAA record from the global custom property -or from global the option in the configuration file. +or from the global option in the configuration file. + +Change of Ownership (coo) +~~~~~~~~~~~~~~~~~~~~~~~~~ + +BIND supports the catalog zones "Change of Ownership" (coo) property. When the +same entry which exists in one catalog zone is added into another catalog zone, +the default behavior for BIND is to ignore it, and continue serving the zone +using the catalog zone where it was originally existed, unless it is removed +from there, then it can be added into the new one. + +Using the ``coo`` property it is possible to gracefully move a zone from one +catalog zone into another, by letting the catalog consumers know that it is +permitted to do so. To do that, the original catalog zone should be updated with +a new record with ``coo`` custom property: + +:: + + uniquelabel.zones.catalog.example. IN PTR domain2.example. + coo.uniquelabel.zones.catalog.example. IN PTR catalog2.example. + +Here, the ``catalog.example`` catalog zone gives permission for the member zone +with label "uniquelabel" to be transferred into ``catalog2.example`` catalog +zone. Catalog consumers which support the ``coo`` property will then take note, +and when the zone is finally added into ``catalog2.example`` catalog zone, +catalog consumers will change the ownership of the zone from ``catalog.example`` +to ``catalog2.example``. BIND's implementation simply deletes the zone from the +old catalog zone and adds it back into the new catalog zone. The record with +``coo`` custom property can be later deleted by the catalog zone operator, if it +is confirmed that all the consumers have received it and have successfully +changed the ownership of the zone. diff --git a/lib/dns/catz.c b/lib/dns/catz.c index ab3d3f14b3..4a9e0576aa 100644 --- a/lib/dns/catz.c +++ b/lib/dns/catz.c @@ -35,13 +35,24 @@ #define DNS_CATZ_ZONE_MAGIC ISC_MAGIC('c', 'a', 't', 'z') #define DNS_CATZ_ZONES_MAGIC ISC_MAGIC('c', 'a', 't', 's') #define DNS_CATZ_ENTRY_MAGIC ISC_MAGIC('c', 'a', 't', 'e') +#define DNS_CATZ_COO_MAGIC ISC_MAGIC('c', 'a', 't', 'c') #define DNS_CATZ_ZONE_VALID(catz) ISC_MAGIC_VALID(catz, DNS_CATZ_ZONE_MAGIC) #define DNS_CATZ_ZONES_VALID(catzs) ISC_MAGIC_VALID(catzs, DNS_CATZ_ZONES_MAGIC) #define DNS_CATZ_ENTRY_VALID(entry) ISC_MAGIC_VALID(entry, DNS_CATZ_ENTRY_MAGIC) +#define DNS_CATZ_COO_VALID(coo) ISC_MAGIC_VALID(coo, DNS_CATZ_COO_MAGIC) #define DNS_CATZ_VERSION_UNDEFINED ((uint32_t)(-1)) +/*% + * Change of ownership permissions + */ +struct dns_catz_coo { + unsigned int magic; + dns_name_t name; + isc_refcount_t refs; +}; + /*% * Single member zone in a catalog */ @@ -62,6 +73,9 @@ struct dns_catz_zone { dns_rdata_t soa; /* key in entries is 'mhash', not domain name! */ isc_ht_t *entries; + /* key in coos is domain name */ + isc_ht_t *coos; + /* * defoptions are taken from named.conf * zoneoptions are global options from zone @@ -208,6 +222,43 @@ dns_catz_options_setdefault(isc_mem_t *mctx, const dns_catz_options_t *defaults, opts->in_memory = defaults->in_memory; } +static void +catz_coo_new(isc_mem_t *mctx, const dns_name_t *domain, + dns_catz_coo_t **ncoop) { + dns_catz_coo_t *ncoo; + + REQUIRE(mctx != NULL); + REQUIRE(domain != NULL); + REQUIRE(ncoop != NULL && *ncoop == NULL); + + ncoo = isc_mem_get(mctx, sizeof(dns_catz_coo_t)); + dns_name_init(&ncoo->name, NULL); + dns_name_dup(domain, mctx, &ncoo->name); + isc_refcount_init(&ncoo->refs, 1); + ncoo->magic = DNS_CATZ_COO_MAGIC; + *ncoop = ncoo; +} + +static void +catz_coo_detach(dns_catz_zone_t *zone, dns_catz_coo_t **coop) { + dns_catz_coo_t *coo; + + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + REQUIRE(coop != NULL && DNS_CATZ_COO_VALID(*coop)); + coo = *coop; + *coop = NULL; + + if (isc_refcount_decrement(&coo->refs) == 1) { + isc_mem_t *mctx = zone->catzs->mctx; + coo->magic = 0; + isc_refcount_destroy(&coo->refs); + if (dns_name_dynamic(&coo->name)) { + dns_name_free(&coo->name, mctx); + } + isc_mem_put(mctx, coo, sizeof(dns_catz_coo_t)); + } +} + void dns_catz_entry_new(isc_mem_t *mctx, const dns_name_t *domain, dns_catz_entry_t **nentryp) { @@ -418,6 +469,7 @@ dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone) { result = delcur ? isc_ht_iter_delcurrent_next(iter1) : isc_ht_iter_next(iter1)) { + isc_result_t zt_find_result; dns_catz_entry_t *nentry = NULL; dns_catz_entry_t *oentry = NULL; dns_zone_t *zone = NULL; @@ -449,6 +501,53 @@ dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone) { &target->zoneoptions, &nentry->opts); + /* Try to find the zone in the view */ + zt_find_result = dns_zt_find(target->catzs->view->zonetable, + dns_catz_entry_getname(nentry), 0, + NULL, &zone); + if (zt_find_result == ISC_R_SUCCESS) { + dns_catz_zone_t *parentcatz = NULL; + dns_catz_coo_t *coo = NULL; + char pczname[DNS_NAME_FORMATSIZE]; + + /* + * Change of ownership (coo) processing, if required + */ + parentcatz = dns_zone_get_parentcatz(zone); + if (parentcatz != NULL && parentcatz != target && + isc_ht_find(parentcatz->coos, nentry->name.ndata, + nentry->name.length, + (void **)&coo) == ISC_R_SUCCESS && + dns_name_equal(&coo->name, &target->name)) + { + dns_name_format(&parentcatz->name, pczname, + DNS_NAME_FORMATSIZE); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, + ISC_LOG_DEBUG(3), + "catz: zone '%s' " + "change of ownership from " + "'%s' to '%s'", + zname, pczname, czname); + result = delzone(nentry, parentcatz, + parentcatz->catzs->view, + parentcatz->catzs->taskmgr, + parentcatz->catzs->zmm->udata); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_MASTER, + ISC_LOG_INFO, + "catz: deleting zone '%s' " + "from catalog '%s' - %s", + zname, pczname, + isc_result_totext(result)); + } + } + if (zt_find_result == ISC_R_SUCCESS || + zt_find_result == DNS_R_PARTIALMATCH) { + dns_zone_detach(&zone); + } + + /* Try to find the zone in the old catalog zone */ result = isc_ht_find(target->entries, key, (uint32_t)keysize, (void **)&oentry); if (result != ISC_R_SUCCESS) { @@ -458,10 +557,7 @@ dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone) { continue; } - result = dns_zt_find(target->catzs->view->zonetable, - dns_catz_entry_getname(nentry), 0, NULL, - &zone); - if (result != ISC_R_SUCCESS) { + if (zt_find_result != ISC_R_SUCCESS) { isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, DNS_LOGMODULE_MASTER, ISC_LOG_DEBUG(3), "catz: zone '%s' was expected to exist " @@ -472,7 +568,6 @@ dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone) { czname); continue; } - dns_zone_detach(&zone); if (dns_catz_entry_cmp(oentry, nentry) != true) { catz_entry_add_or_mod(target, tomod, key, keysize, @@ -554,6 +649,33 @@ dns_catz_zones_merge(dns_catz_zone_t *target, dns_catz_zone_t *newzone) { target->entries = newzone->entries; newzone->entries = NULL; + /* + * We do not need to merge old coo (change of ownership) permission + * records with the new ones, just replace them. + */ + if (target->coos != NULL && newzone->coos != NULL) { + isc_ht_iter_t *iter = NULL; + + isc_ht_iter_create(target->coos, &iter); + for (result = isc_ht_iter_first(iter); result == ISC_R_SUCCESS; + result = isc_ht_iter_delcurrent_next(iter)) + { + dns_catz_coo_t *coo = NULL; + + isc_ht_iter_current(iter, (void **)&coo); + catz_coo_detach(target, &coo); + } + INSIST(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter); + + /* The hashtable has to be empty now. */ + INSIST(isc_ht_count(target->coos) == 0); + isc_ht_destroy(&target->coos); + + target->coos = newzone->coos; + newzone->coos = NULL; + } + result = ISC_R_SUCCESS; isc_ht_iter_destroy(&iteradd); @@ -633,6 +755,7 @@ dns_catz_new_zone(dns_catz_zones_t *catzs, dns_catz_zone_t **zonep, dns_name_dup(name, catzs->mctx, &new_zone->name); isc_ht_init(&new_zone->entries, catzs->mctx, 4, ISC_HT_CASE_SENSITIVE); + isc_ht_init(&new_zone->coos, catzs->mctx, 4, ISC_HT_CASE_INSENSITIVE); new_zone->updatetimer = NULL; isc_timer_create(catzs->timermgr, catzs->updater, @@ -766,6 +889,26 @@ dns_catz_zone_detach(dns_catz_zone_t **zonep) { INSIST(isc_ht_count(zone->entries) == 0); isc_ht_destroy(&zone->entries); } + if (zone->coos != NULL) { + isc_ht_iter_t *iter = NULL; + isc_result_t result; + isc_ht_iter_create(zone->coos, &iter); + for (result = isc_ht_iter_first(iter); + result == ISC_R_SUCCESS; + result = isc_ht_iter_delcurrent_next(iter)) + { + dns_catz_coo_t *coo = NULL; + + isc_ht_iter_current(iter, (void **)&coo); + catz_coo_detach(zone, &coo); + } + INSIST(result == ISC_R_NOMORE); + isc_ht_iter_destroy(&iter); + + /* The hashtable has to be empty now. */ + INSIST(isc_ht_count(zone->coos) == 0); + isc_ht_destroy(&zone->coos); + } zone->magic = 0; isc_timer_destroy(&zone->updatetimer); if (zone->db_registered) { @@ -826,6 +969,7 @@ dns_catz_catzs_detach(dns_catz_zones_t **catzsp) { typedef enum { CATZ_OPT_NONE, CATZ_OPT_ZONES, + CATZ_OPT_COO, CATZ_OPT_VERSION, CATZ_OPT_CUSTOM_START, /* CATZ custom properties must go below this */ CATZ_OPT_EXT, @@ -859,6 +1003,8 @@ catz_get_option(const dns_label_t *option) { return (CATZ_OPT_ALLOW_QUERY); } else if (catz_opt_cmp(option, "allow-transfer")) { return (CATZ_OPT_ALLOW_TRANSFER); + } else if (catz_opt_cmp(option, "coo")) { + return (CATZ_OPT_COO); } else if (catz_opt_cmp(option, "version")) { return (CATZ_OPT_VERSION); } else { @@ -896,6 +1042,80 @@ catz_process_zones(dns_catz_zone_t *zone, dns_rdataset_t *value, } } +static isc_result_t +catz_process_coo(dns_catz_zone_t *zone, dns_label_t *mhash, + dns_rdataset_t *value) { + isc_result_t result; + dns_rdata_t rdata; + dns_rdata_ptr_t ptr; + dns_catz_entry_t *entry = NULL; + dns_catz_coo_t *ncoo = NULL; + dns_catz_coo_t *ocoo = NULL; + + REQUIRE(DNS_CATZ_ZONE_VALID(zone)); + REQUIRE(mhash != NULL); + REQUIRE(DNS_RDATASET_VALID(value)); + + /* Change of Ownership was introduced in version "2" of the schema. */ + if (zone->version < 2) { + return (ISC_R_FAILURE); + } + + if (value->rdclass != dns_rdataclass_in || + value->type != dns_rdatatype_ptr) { + return (ISC_R_FAILURE); + } + + result = dns_rdataset_first(value); + if (result != ISC_R_SUCCESS) { + return (result); + } + + dns_rdata_init(&rdata); + dns_rdataset_current(value, &rdata); + + result = dns_rdata_tostruct(&rdata, &ptr, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + + if (dns_name_countlabels(&ptr.ptr) == 0) { + result = ISC_R_FAILURE; + goto cleanup; + } + + result = isc_ht_find(zone->entries, mhash->base, mhash->length, + (void **)&entry); + if (result != ISC_R_SUCCESS) { + /* The entry was not found .*/ + goto cleanup; + } + + if (dns_name_countlabels(&entry->name) == 0) { + result = ISC_R_FAILURE; + goto cleanup; + } + + result = isc_ht_find(zone->coos, entry->name.ndata, entry->name.length, + (void **)&ocoo); + if (result == ISC_R_SUCCESS) { + /* The change of ownership permission was already registered. */ + goto cleanup; + } + + catz_coo_new(zone->catzs->mctx, &ptr.ptr, &ncoo); + result = isc_ht_add(zone->coos, entry->name.ndata, entry->name.length, + ncoo); + if (result != ISC_R_SUCCESS) { + catz_coo_detach(zone, &ncoo); + } + +cleanup: + dns_rdata_freestruct(&ptr); + + return (result); +} + static isc_result_t catz_process_zones_entry(dns_catz_zone_t *zone, dns_rdataset_t *value, dns_label_t *mhash) { @@ -1330,6 +1550,8 @@ catz_process_zones_suboption(dns_catz_zone_t *zone, dns_rdataset_t *value, dns_name_init(&prefix, NULL); dns_name_split(name, suffix_labels, &prefix, NULL); switch (opt) { + case CATZ_OPT_COO: + return (catz_process_coo(zone, mhash, value)); case CATZ_OPT_MASTERS: return (catz_process_primaries(zone, &entry->opts.masters, value, &prefix)); diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 787200b489..6465962bd4 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -42,6 +42,7 @@ typedef struct dns_byaddr dns_byaddr_t; typedef struct dns_catz_zonemodmethods dns_catz_zonemodmethods_t; typedef struct dns_catz_entry_options dns_catz_options_t; typedef struct dns_catz_entry dns_catz_entry_t; +typedef struct dns_catz_coo dns_catz_coo_t; typedef struct dns_catz_zone dns_catz_zone_t; typedef struct dns_catz_changed dns_catz_changed_t; typedef struct dns_catz_zones dns_catz_zones_t;