Hello,

attached patch set implements
https://fedorahosted.org/bind-dyndb-ldap/ticket/160
described in
https://fedorahosted.org/bind-dyndb-ldap/wiki/BIND9/Design/AutomaticEmptyZones

It will be accompanied with upgrade code in FreeIPA which will automatically
convert forward zones as necessary.

-- 
Petr^2 Spacek
From 2a58867fd4438ffc654e4f20ed4d5aa6a3f79a0b Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Mon, 11 Jan 2016 16:19:34 +0100
Subject: [PATCH] Move zone_isempty() and delete_bind_zone() to
 zone_register.c.

This is preparation for further work which will use both functions from
other modules than ldap_helper.c.

https://fedorahosted.org/bind-dyndb-ldap/ticket/160
---
 src/ldap_helper.c   | 63 -----------------------------------------------------
 src/zone_register.c | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/zone_register.h |  8 +++++++
 3 files changed, 71 insertions(+), 63 deletions(-)

diff --git a/src/ldap_helper.c b/src/ldap_helper.c
index 415786d31776d8780f44e75f48674c47a2f61b21..c03cf0a29dd8042e27504ef52234b5fb0b891136 100644
--- a/src/ldap_helper.c
+++ b/src/ldap_helper.c
@@ -721,69 +721,6 @@ destroy_ldap_connection(ldap_connection_t **ldap_connp)
 	MEM_PUT_AND_DETACH(*ldap_connp);
 }
 
-/* Test if the existing zone is 'empty zone' per RFC 6303. */
-static isc_boolean_t ATTR_NONNULLS ATTR_CHECKRESULT
-zone_isempty(dns_zone_t *zone) {
-	char **argv = NULL;
-	isc_mem_t *mctx = NULL;
-	isc_boolean_t result = ISC_FALSE;
-
-	mctx = dns_zone_getmctx(zone);
-	if (dns_zone_getdbtype(zone, &argv, mctx) != ISC_R_SUCCESS)
-		CLEANUP_WITH(ISC_FALSE);
-
-	if (argv[0] != NULL && strcmp("_builtin", argv[0]) == 0 &&
-	    argv[1] != NULL && strcmp("empty", argv[1]) == 0) {
-		result = ISC_TRUE;
-	} else {
-		result = ISC_FALSE;
-	}
-	isc_mem_free(mctx, argv);
-
-cleanup:
-	return result;
-}
-
-/**
- * Delete a zone from plain BIND. LDAP zones require further steps for complete
- * removal, like deletion from zone register etc.
- *
- * @pre A zone pointer has to be attached to *zonep.
- *
- * @returns Values returned by dns_zt_unmount().
- */
-static isc_result_t ATTR_NONNULLS ATTR_CHECKRESULT
-delete_bind_zone(dns_zt_t *zt, dns_zone_t **zonep) {
-	dns_zone_t *zone;
-	dns_db_t *dbp = NULL;
-	dns_zonemgr_t *zmgr;
-	isc_result_t result;
-
-	REQUIRE (zonep != NULL && *zonep != NULL);
-
-	zone = *zonep;
-
-	/* Do not unload partially loaded zones, they have uninitialized
-	 * structures. */
-	if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) {
-		dns_db_detach(&dbp); /* dns_zone_getdb() attaches DB implicitly */
-		dns_zone_unload(zone);
-		dns_zone_log(zone, ISC_LOG_INFO, "shutting down");
-	} else {
-		dns_zone_log(zone, ISC_LOG_DEBUG(1), "not loaded - unload skipped");
-	}
-
-	result = dns_zt_unmount(zt, zone);
-	if (result == ISC_R_NOTFOUND) /* zone wasn't part of a view */
-		result = ISC_R_SUCCESS;
-	zmgr = dns_zone_getmgr(zone);
-	if (zmgr != NULL)
-		dns_zonemgr_releasezone(zmgr, zone);
-	dns_zone_detach(zonep);
-
-	return result;
-}
-
 static isc_result_t ATTR_NONNULLS
 cleanup_zone_files(dns_zone_t *zone) {
 	isc_result_t result;
diff --git a/src/zone_register.c b/src/zone_register.c
index 07e08561ae0a77fb744f74f601ac152e22265544..be9265ec6ea0577ee5aa04713085c1739df3de58 100644
--- a/src/zone_register.c
+++ b/src/zone_register.c
@@ -561,3 +561,66 @@ zr_get_zone_settings(zone_register_t *zr, dns_name_t *name, settings_set_t **set
 
 	return result;
 }
+
+/**
+ * Delete a zone from plain BIND. LDAP zones require further steps for complete
+ * removal, like deletion from zone register etc.
+ *
+ * @pre A zone pointer has to be attached to *zonep.
+ *
+ * @returns Values returned by dns_zt_unmount().
+ */
+isc_result_t ATTR_NONNULLS ATTR_CHECKRESULT
+delete_bind_zone(dns_zt_t *zt, dns_zone_t **zonep) {
+	dns_zone_t *zone;
+	dns_db_t *dbp = NULL;
+	dns_zonemgr_t *zmgr;
+	isc_result_t result;
+
+	REQUIRE (zonep != NULL && *zonep != NULL);
+
+	zone = *zonep;
+
+	/* Do not unload partially loaded zones, they have uninitialized
+	 * structures. */
+	if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) {
+		dns_db_detach(&dbp); /* dns_zone_getdb() attaches DB implicitly */
+		dns_zone_unload(zone);
+		dns_zone_log(zone, ISC_LOG_INFO, "shutting down");
+	} else {
+		dns_zone_log(zone, ISC_LOG_DEBUG(1), "not loaded - unload skipped");
+	}
+
+	result = dns_zt_unmount(zt, zone);
+	if (result == ISC_R_NOTFOUND) /* zone wasn't part of a view */
+		result = ISC_R_SUCCESS;
+	zmgr = dns_zone_getmgr(zone);
+	if (zmgr != NULL)
+		dns_zonemgr_releasezone(zmgr, zone);
+	dns_zone_detach(zonep);
+
+	return result;
+}
+
+/* Test if the existing zone is 'empty zone' per RFC 6303. */
+isc_boolean_t ATTR_NONNULLS ATTR_CHECKRESULT
+zone_isempty(dns_zone_t *zone) {
+	char **argv = NULL;
+	isc_mem_t *mctx = NULL;
+	isc_boolean_t result = ISC_FALSE;
+
+	mctx = dns_zone_getmctx(zone);
+	if (dns_zone_getdbtype(zone, &argv, mctx) != ISC_R_SUCCESS)
+		CLEANUP_WITH(ISC_FALSE);
+
+	if (argv[0] != NULL && strcmp("_builtin", argv[0]) == 0 &&
+	    argv[1] != NULL && strcmp("empty", argv[1]) == 0) {
+		result = ISC_TRUE;
+	} else {
+		result = ISC_FALSE;
+	}
+	isc_mem_free(mctx, argv);
+
+cleanup:
+	return result;
+}
diff --git a/src/zone_register.h b/src/zone_register.h
index 6d53e9265ece5d2343cd1831c6b847bfc521eb8f..60040786a7d7b8cc4d32ec09e990dbed0ded4827 100644
--- a/src/zone_register.h
+++ b/src/zone_register.h
@@ -5,6 +5,8 @@
 #ifndef _LD_ZONE_REGISTER_H_
 #define _LD_ZONE_REGISTER_H_
 
+#include <dns/zt.h>
+
 #include "settings.h"
 #include "rbt_helper.h"
 #include "ldap_helper.h"
@@ -52,4 +54,10 @@ zr_rbt_iter_init(zone_register_t *zr, rbt_iterator_t **iter,
 isc_mem_t *
 zr_get_mctx(zone_register_t *zr) ATTR_NONNULLS ATTR_CHECKRESULT;
 
+isc_result_t
+delete_bind_zone(dns_zt_t *zt, dns_zone_t **zonep) ATTR_NONNULLS ATTR_CHECKRESULT;
+
+isc_boolean_t
+zone_isempty(dns_zone_t *zone) ATTR_NONNULLS ATTR_CHECKRESULT;
+
 #endif /* !_LD_ZONE_REGISTER_H_ */
-- 
2.5.5

From 339c4bbe261fd3bed04442a40bf2cf50c88ddb7c Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Mon, 11 Jan 2016 16:27:55 +0100
Subject: [PATCH] Add missing includes to util.h.

https://fedorahosted.org/bind-dyndb-ldap/ticket/160
---
 src/util.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/util.h b/src/util.h
index 402503c339a5ab6ca5273cae420e743b9fc252ab..ab49ba9eea86dcd2ba5a12f31e75804f6f857f2d 100644
--- a/src/util.h
+++ b/src/util.h
@@ -7,10 +7,13 @@
 
 #include <string.h>
 
+#include <isc/boolean.h>
 #include <isc/mem.h>
 #include <isc/buffer.h>
+#include <isc/result.h>
 #include <dns/types.h>
 #include <dns/name.h>
+#include <dns/result.h>
 
 #include "log.h"
 
-- 
2.5.5

From 6dc81dfb4980c881745a43723c87f8b0efc3f582 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Wed, 13 Jan 2016 14:23:18 +0100
Subject: [PATCH] Do not touch global forwarders from named.conf if there is no
 config in LDAP.

Without this change, fwdtbl_update_requested variable in
configure_zone_forwarders() was True even in cases where no forwarding
was configured in named.conf and in LDAP.

That would cause false positives in automatic empty zone unloading logic.

https://fedorahosted.org/bind-dyndb-ldap/ticket/160
---
 src/ldap_helper.c | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/src/ldap_helper.c b/src/ldap_helper.c
index c03cf0a29dd8042e27504ef52234b5fb0b891136..24df66fe4b6f9cfb5595884f82b232f7b455472a 100644
--- a/src/ldap_helper.c
+++ b/src/ldap_helper.c
@@ -1556,13 +1556,14 @@ configure_zone_forwarders(ldap_entry_t *entry, ldap_instance_t *inst,
 
 		if (!fwdtbl_update_requested && ((s1 != NULL) || (s2 != NULL)))
 			fwdtbl_update_requested = ISC_TRUE;
-	} else {
+
+	} else if (!(result == ISC_R_NOTFOUND && fwdpolicy == dns_fwdpolicy_none)) {
+		/* No forwarder in the table and policy 'none' = no change. */
 		fwdtbl_update_requested = ISC_TRUE;
-		if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND)
-			log_error_r("%s %s: can't obtain old forwarding "
-				    "settings", msg_obj_type,
-				    ldap_entry_logname(entry));
 	}
+	if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND)
+		log_error_r("%s %s: can't obtain old forwarding settings",
+			    msg_obj_type, ldap_entry_logname(entry));
 
 	if (fwdtbl_update_requested) {
 		/* Shutdown automatic empty zone if it is present. */
-- 
2.5.5

From 3ce22cc69b0db6295497d4c2dcc69e35d7be720e Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Mon, 11 Jan 2016 16:37:20 +0100
Subject: [PATCH] Unload automatic empty zones which are super/sub/equal domain
 as forward zone.

It allows queries to leak to the public Internet if:
a) The query name does not belong to forwarded domain:
   - empty zone = 10.in-addr.arpa
   - forwarder 1.10.in-addr.arpa
   - qname 2.10.in-addr.arpa

b) Forwarder is a superdomain but it failed and user configured policy != only.

Limitation:
Unloading logic is triggered only by zones in LDAP + global forwarder in
named.conf. BIND does not have an API to iterate over complete list
of forwarders configured from other sources (namely named.conf).

https://fedorahosted.org/bind-dyndb-ldap/ticket/160
---
 src/Makefile.am   |   2 +
 src/empty_zones.c | 317 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/empty_zones.h |  32 ++++++
 src/ldap_helper.c |  38 ++++---
 4 files changed, 372 insertions(+), 17 deletions(-)
 create mode 100644 src/empty_zones.c
 create mode 100644 src/empty_zones.h

diff --git a/src/Makefile.am b/src/Makefile.am
index acbb49191a03d71d13a645f32c4437201e420914..58f73ec9d37471471036fb532ac239523512eb80 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -4,6 +4,7 @@ bindplugindir=$(libdir)/bind
 HDRS =				\
 	acl.h			\
 	compat.h		\
+	empty_zones.h		\
 	fs.h			\
 	fwd_register.h		\
 	krb5_helper.h		\
@@ -30,6 +31,7 @@ HDRS =				\
 ldap_la_SOURCES =		\
 	$(HDRS)			\
 	acl.c			\
+	empty_zones.c		\
 	fwd_register.c		\
 	fs.c			\
 	krb5_helper.c		\
diff --git a/src/empty_zones.c b/src/empty_zones.c
new file mode 100644
index 0000000000000000000000000000000000000000..0ecb74db3d177d8ed68554718a720f0aeadceb86
--- /dev/null
+++ b/src/empty_zones.c
@@ -0,0 +1,317 @@
+#include <stdio.h>
+
+#include <isc/result.h>
+#include <isc/types.h>
+#include <isc/util.h>
+
+#include <dns/name.h>
+#include <dns/zone.h>
+
+#include "empty_zones.h"
+#include "util.h"
+#include "zone_register.h"
+
+/**
+ * These zones should not leak onto the Internet.
+ * The list matches BIND commit 8f20f6c9d7ce5a0f0af6ee4c5361832d97b1c5d4
+ * (2015-05-15T08:22+1000).
+ */
+const char *empty_zones[] = {
+	/* RFC 1918 */
+	"10.IN-ADDR.ARPA",
+	"16.172.IN-ADDR.ARPA",
+	"17.172.IN-ADDR.ARPA",
+	"18.172.IN-ADDR.ARPA",
+	"19.172.IN-ADDR.ARPA",
+	"20.172.IN-ADDR.ARPA",
+	"21.172.IN-ADDR.ARPA",
+	"22.172.IN-ADDR.ARPA",
+	"23.172.IN-ADDR.ARPA",
+	"24.172.IN-ADDR.ARPA",
+	"25.172.IN-ADDR.ARPA",
+	"26.172.IN-ADDR.ARPA",
+	"27.172.IN-ADDR.ARPA",
+	"28.172.IN-ADDR.ARPA",
+	"29.172.IN-ADDR.ARPA",
+	"30.172.IN-ADDR.ARPA",
+	"31.172.IN-ADDR.ARPA",
+	"168.192.IN-ADDR.ARPA",
+
+	/* RFC 6598 */
+	"64.100.IN-ADDR.ARPA",
+	"65.100.IN-ADDR.ARPA",
+	"66.100.IN-ADDR.ARPA",
+	"67.100.IN-ADDR.ARPA",
+	"68.100.IN-ADDR.ARPA",
+	"69.100.IN-ADDR.ARPA",
+	"70.100.IN-ADDR.ARPA",
+	"71.100.IN-ADDR.ARPA",
+	"72.100.IN-ADDR.ARPA",
+	"73.100.IN-ADDR.ARPA",
+	"74.100.IN-ADDR.ARPA",
+	"75.100.IN-ADDR.ARPA",
+	"76.100.IN-ADDR.ARPA",
+	"77.100.IN-ADDR.ARPA",
+	"78.100.IN-ADDR.ARPA",
+	"79.100.IN-ADDR.ARPA",
+	"80.100.IN-ADDR.ARPA",
+	"81.100.IN-ADDR.ARPA",
+	"82.100.IN-ADDR.ARPA",
+	"83.100.IN-ADDR.ARPA",
+	"84.100.IN-ADDR.ARPA",
+	"85.100.IN-ADDR.ARPA",
+	"86.100.IN-ADDR.ARPA",
+	"87.100.IN-ADDR.ARPA",
+	"88.100.IN-ADDR.ARPA",
+	"89.100.IN-ADDR.ARPA",
+	"90.100.IN-ADDR.ARPA",
+	"91.100.IN-ADDR.ARPA",
+	"92.100.IN-ADDR.ARPA",
+	"93.100.IN-ADDR.ARPA",
+	"94.100.IN-ADDR.ARPA",
+	"95.100.IN-ADDR.ARPA",
+	"96.100.IN-ADDR.ARPA",
+	"97.100.IN-ADDR.ARPA",
+	"98.100.IN-ADDR.ARPA",
+	"99.100.IN-ADDR.ARPA",
+	"100.100.IN-ADDR.ARPA",
+	"101.100.IN-ADDR.ARPA",
+	"102.100.IN-ADDR.ARPA",
+	"103.100.IN-ADDR.ARPA",
+	"104.100.IN-ADDR.ARPA",
+	"105.100.IN-ADDR.ARPA",
+	"106.100.IN-ADDR.ARPA",
+	"107.100.IN-ADDR.ARPA",
+	"108.100.IN-ADDR.ARPA",
+	"109.100.IN-ADDR.ARPA",
+	"110.100.IN-ADDR.ARPA",
+	"111.100.IN-ADDR.ARPA",
+	"112.100.IN-ADDR.ARPA",
+	"113.100.IN-ADDR.ARPA",
+	"114.100.IN-ADDR.ARPA",
+	"115.100.IN-ADDR.ARPA",
+	"116.100.IN-ADDR.ARPA",
+	"117.100.IN-ADDR.ARPA",
+	"118.100.IN-ADDR.ARPA",
+	"119.100.IN-ADDR.ARPA",
+	"120.100.IN-ADDR.ARPA",
+	"121.100.IN-ADDR.ARPA",
+	"122.100.IN-ADDR.ARPA",
+	"123.100.IN-ADDR.ARPA",
+	"124.100.IN-ADDR.ARPA",
+	"125.100.IN-ADDR.ARPA",
+	"126.100.IN-ADDR.ARPA",
+	"127.100.IN-ADDR.ARPA",
+
+	/* RFC 5735 and RFC 5737 */
+	"0.IN-ADDR.ARPA",	/* THIS NETWORK */
+	"127.IN-ADDR.ARPA",	/* LOOPBACK */
+	"254.169.IN-ADDR.ARPA",	/* LINK LOCAL */
+	"2.0.192.IN-ADDR.ARPA",	/* TEST NET */
+	"100.51.198.IN-ADDR.ARPA",	/* TEST NET 2 */
+	"113.0.203.IN-ADDR.ARPA",	/* TEST NET 3 */
+	"255.255.255.255.IN-ADDR.ARPA",	/* BROADCAST */
+
+	/* Local IPv6 Unicast Addresses */
+	"0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA",
+	"1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.IP6.ARPA",
+	/* LOCALLY ASSIGNED LOCAL ADDRESS SCOPE */
+	"D.F.IP6.ARPA",
+	"8.E.F.IP6.ARPA",	/* LINK LOCAL */
+	"9.E.F.IP6.ARPA",	/* LINK LOCAL */
+	"A.E.F.IP6.ARPA",	/* LINK LOCAL */
+	"B.E.F.IP6.ARPA",	/* LINK LOCAL */
+
+	/* Example Prefix, RFC 3849. */
+	"8.B.D.0.1.0.0.2.IP6.ARPA",
+
+	/* RFC 7534 */
+	"EMPTY.AS112.ARPA",
+
+	NULL
+};
+
+/**
+ * Continue search for qname among automatic empty zones.
+ *
+ * @param[in,out] iter Intermediate state which must be passed to subsequent
+ * 		       empty_zone_search_next() call.
+ *
+ * @retval ISC_R_SUCCESS A automatic empty zone which is super/sub/equal domain
+ * 			 name was found and stored into the iter structure along
+ * 			 with information about relation between
+ * 			 qname and empty zone name.
+ * @retval ISC_R_NOMORE  No other matching empty zone was found.
+ * @retval others        Errors from dns_name_fromtext().
+ */
+isc_result_t
+empty_zone_search_next(empty_zone_search_t *iter) {
+	isc_result_t result;
+	const char *ezchar = NULL;
+	isc_buffer_t buffer;
+	int order;
+	unsigned int nlabels;
+
+	REQUIRE(iter != NULL);
+	REQUIRE(iter->nextidx < sizeof(empty_zones));
+
+	INIT_BUFFERED_NAME(iter->ezname);
+	iter->namerel = dns_namereln_none;
+
+	for (ezchar = empty_zones[iter->nextidx];
+	     ezchar != NULL;
+	     ezchar = empty_zones[++iter->nextidx])
+	{
+		isc_buffer_constinit(&buffer, ezchar, strlen(ezchar));
+		isc_buffer_add(&buffer, strlen(ezchar));
+		CHECK(dns_name_fromtext(&iter->ezname, &buffer, dns_rootname, 0,
+					NULL));
+		iter->namerel = dns_name_fullcompare(&iter->ezname,
+						     &iter->qname,
+						     &order, &nlabels);
+		if (iter->namerel == dns_namereln_commonancestor ||
+		    iter->namerel == dns_namereln_none) {
+			/* empty zone and domain in question are not related */
+			continue;
+		} else {
+			++iter->nextidx;
+			CLEANUP_WITH(ISC_R_SUCCESS);
+		}
+	}
+
+	result = ISC_R_NOMORE;
+
+cleanup:
+	return result;
+};
+
+
+/**
+ * Start search for qname among automatic empty zones.
+ *
+ * @param[in]  qname  Name to compare with list of automatic empty zones.
+ * @param[out] iter   Intermediate state which must be passed to subsequent
+ * 		      empty_zone_search_next() call. At the same time,
+ * 		      the structure contains name of first matching
+ * 		      automatic empty zone and relation between names.
+ * @returns @see empty_zone_search_next
+ */
+isc_result_t
+empty_zone_search_init(empty_zone_search_t *iter, dns_name_t *qname) {
+	isc_result_t result;
+
+	REQUIRE(iter != NULL);
+	REQUIRE(dns_name_isabsolute(qname));
+
+	INIT_BUFFERED_NAME(iter->qname);
+	CHECK(dns_name_copy(qname, &iter->qname, NULL));
+
+	INIT_BUFFERED_NAME(iter->ezname);
+	iter->nextidx = 0;
+	iter->namerel = dns_namereln_none;
+
+	CHECK(empty_zone_search_next(iter));
+
+cleanup:
+	return result;
+}
+
+/**
+ * Shutdown automatic empty zone if it is present.
+ *
+ * @param[in]     ezname     Empty zone name
+ * @param[in,out] zonetable  Zonetable from affected view.
+ *
+ * @retval ISC_R_SUCCESS     Empty zone was found and unloaded.
+ * @retval DNS_R_DISALLOWED  Nothing was done because the zone is not
+ * 			     an automatic empty zone.
+ * @retval ISC_R_NOTFOUND    No zone with given name is present in zone table.
+ */
+isc_result_t
+empty_zone_unload(dns_name_t *ezname, dns_zt_t *zonetable)
+{
+	isc_result_t result;
+	dns_zone_t *zone = NULL;
+
+	CHECK(dns_zt_find(zonetable, ezname, 0, NULL, &zone));
+	if (zone_isempty(zone))
+		CHECK(delete_bind_zone(zonetable, &zone));
+	else
+		CLEANUP_WITH(DNS_R_DISALLOWED);
+
+cleanup:
+	if (zone != NULL)
+		dns_zone_detach(&zone);
+
+	if (result == DNS_R_PARTIALMATCH)
+		result = ISC_R_NOTFOUND;
+
+	return result;
+}
+
+
+/**
+ * Detect if given name is super/sub/equal domain to any of automatic empty
+ * zones. If such empty zone is found, it will be automatically unloaded so
+ * forwarding will work as configured by user.
+ *
+ * It allows queries to leak to the public Internet if:
+ * a) The query name does not belong to forwarded domain:
+ *    - empty zone = 10.in-addr.arpa
+ *    - forwarder 1.10.in-addr.arpa
+ *    - qname 2.10.in-addr.arpa
+ *
+ * b) Forwarder is a superdomain but it failed and user configured policy != only.
+ */
+isc_result_t
+empty_zone_handle_conflicts(dns_name_t *name, dns_zt_t *zonetable)
+{
+	isc_result_t result;
+	isc_boolean_t first = ISC_TRUE;
+	empty_zone_search_t eziter;
+
+	for (result = empty_zone_search_init(&eziter, name);
+	     result == ISC_R_SUCCESS;
+	     result = empty_zone_search_next(&eziter))
+	{
+		/* Shutdown automatic empty zone if it is present. */
+		result = empty_zone_unload(&eziter.ezname, zonetable);
+		if (result == ISC_R_SUCCESS) {
+			if (first == ISC_TRUE) {
+				char name_char[DNS_NAME_FORMATSIZE];
+				dns_name_format(name, name_char, DNS_NAME_FORMATSIZE);
+				log_info("shutting down automatic empty zones to "
+					 "enable forwarding for domain '%s'", name_char);
+				first = ISC_FALSE;
+			}
+		} else if (result == DNS_R_DISALLOWED) {
+			/* A normal (non-empty) zone exists:
+			 * Do not change its forwarding configuration. */
+			continue;
+		} else if (result != ISC_R_NOTFOUND)
+			goto cleanup;
+	}
+	if (result == ISC_R_NOMORE)
+		result = ISC_R_SUCCESS;
+
+cleanup:
+	return result;
+}
+
+/**
+ * Handle all empty zones which conflict with global forwarder.
+ */
+void
+empty_zone_handle_globalfwd_ev(isc_task_t *task, isc_event_t *event)
+{
+	ldap_globalfwd_handleez_t *pevent = NULL;
+
+	UNUSED(task);
+	REQUIRE(event != NULL);
+
+	pevent = (ldap_globalfwd_handleez_t *)event;
+	RUNTIME_CHECK(empty_zone_handle_conflicts(dns_rootname, pevent->ev_arg)
+		      == ISC_R_SUCCESS);
+
+	isc_event_free(&event);
+}
diff --git a/src/empty_zones.h b/src/empty_zones.h
new file mode 100644
index 0000000000000000000000000000000000000000..3f805672173656f16ff373b1ef2c831d15babf88
--- /dev/null
+++ b/src/empty_zones.h
@@ -0,0 +1,32 @@
+#include <isc/event.h>
+
+#include "util.h"
+
+extern const char *empty_zones[];
+
+typedef struct empty_zone_search {
+	DECLARE_BUFFERED_NAME(qname);
+	DECLARE_BUFFERED_NAME(ezname);
+	unsigned int nextidx;
+	dns_namereln_t namerel;
+} empty_zone_search_t;
+
+isc_result_t
+empty_zone_search_next(empty_zone_search_t *iter) ATTR_NONNULLS ATTR_CHECKRESULT;
+
+isc_result_t
+empty_zone_search_init(empty_zone_search_t *iter, dns_name_t *qname) ATTR_NONNULLS ATTR_CHECKRESULT;
+
+isc_result_t
+empty_zone_handle_conflicts(dns_name_t *name, dns_zt_t *zonetable) ATTR_NONNULLS ATTR_CHECKRESULT;
+
+/* Trigger to execute empty_zone_handle_conflicts() for dns_rootname. */
+#define LDAPDB_EVENT_GLOBALFWD_HANDLEEZ	(LDAPDB_EVENTCLASS + 5)
+
+typedef struct ldap_globalfwd_handleez ldap_globalfwd_handleez_t;
+struct ldap_globalfwd_handleez {
+	ISC_EVENT_COMMON(ldap_globalfwd_handleez_t);
+};
+
+void
+empty_zone_handle_globalfwd_ev(isc_task_t *task, isc_event_t *event) ATTR_NONNULLS;
diff --git a/src/ldap_helper.c b/src/ldap_helper.c
index 24df66fe4b6f9cfb5595884f82b232f7b455472a..5338ab114b7426a6ed30299c07e43bfb8cc2373d 100644
--- a/src/ldap_helper.c
+++ b/src/ldap_helper.c
@@ -55,6 +55,7 @@
 #include <netdb.h>
 
 #include "acl.h"
+#include "empty_zones.h"
 #include "fs.h"
 #include "krb5_helper.h"
 #include "ldap_convert.h"
@@ -510,6 +511,7 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
 	dns_forwarders_t *orig_global_forwarders = NULL;
 	isc_uint32_t connections;
 	char settings_name[PRINT_BUFF_SIZE];
+	ldap_globalfwd_handleez_t *gfwdevent = NULL;
 
 	REQUIRE(ldap_instp != NULL && *ldap_instp == NULL);
 
@@ -579,6 +581,21 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
 		ldap_inst->orig_global_forwarders.fwdpolicy =
 				orig_global_forwarders->fwdpolicy;
 
+		/* Make sure we disable conflicting automatic empty zones.
+		 * This will be done in event to prevent the plugin from
+		 * interfering with BIND start-up. */
+		CHECK(sync_task_add(ldap_inst->sctx, task));
+		gfwdevent = (ldap_globalfwd_handleez_t *)isc_event_allocate(
+					ldap_inst->mctx, ldap_inst,
+					LDAPDB_EVENT_GLOBALFWD_HANDLEEZ,
+					empty_zone_handle_globalfwd_ev,
+					ldap_inst->view->zonetable,
+					sizeof(ldap_globalfwd_handleez_t));
+		if (gfwdevent == NULL)
+			CLEANUP_WITH(ISC_R_NOMEMORY);
+
+		isc_task_send(task, (isc_event_t **)&gfwdevent);
+
 	} else if (result == ISC_R_NOTFOUND) {
 		/* global forwarders are not configured */
 		ldap_inst->orig_global_forwarders.fwdpolicy = dns_fwdpolicy_none;
@@ -1403,7 +1420,6 @@ configure_zone_forwarders(ldap_entry_t *entry, ldap_instance_t *inst,
 	isc_boolean_t fwdtbl_update_requested = ISC_FALSE;
 	dns_forwarders_t *old_setting = NULL;
 	dns_fixedname_t foundname;
-	dns_zone_t *zone = NULL;
 	const char *msg_use_global_fwds;
 	const char *msg_obj_type;
 	const char *msg_forwarders_not_def;
@@ -1566,23 +1582,11 @@ configure_zone_forwarders(ldap_entry_t *entry, ldap_instance_t *inst,
 			    msg_obj_type, ldap_entry_logname(entry));
 
 	if (fwdtbl_update_requested) {
-		/* Shutdown automatic empty zone if it is present. */
-		result = dns_zt_find(inst->view->zonetable, name, 0, NULL,
-				     &zone);
-		if (result == ISC_R_SUCCESS || result == DNS_R_PARTIALMATCH) {
-			if (zone_isempty(zone)) {
-				dns_zone_log(zone, ISC_LOG_INFO, "automatic "
-					     "empty zone will be shut down "
-					     "to enable forwarding");
-				result = delete_bind_zone(inst->view->zonetable,
-							  &zone);
-			} else {
-				dns_zone_detach(&zone);
-				result = ISC_R_SUCCESS;
-			}
+		if (fwdpolicy != dns_fwdpolicy_none) {
+			/* Handle collisions with automatic empty zones. */
+			CHECK(empty_zone_handle_conflicts(name,
+							  inst->view->zonetable));
 		}
-		if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND)
-			goto cleanup;
 
 		/* Something was changed - set forward table up. */
 		CHECK(delete_forwarding_table(inst, name, msg_obj_type,
-- 
2.5.5

From cb68dd344b86060b7a6983b2f83d834e68e28a03 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Wed, 6 Apr 2016 13:16:56 +0200
Subject: [PATCH] Add ability to log warnings.

https://fedorahosted.org/bind-dyndb-ldap/ticket/160
---
 src/log.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/log.h b/src/log.h
index db84b597adfe736816306d53bfbab61a4d6d3668..da71f8ba9911d6d1cca55525757556c2fdc5b644 100644
--- a/src/log.h
+++ b/src/log.h
@@ -34,6 +34,9 @@
 #define log_error(format, ...)	\
 	log_write(GET_LOG_LEVEL(ISC_LOG_ERROR), format, ##__VA_ARGS__)
 
+#define log_warn(format, ...)	\
+	log_write(GET_LOG_LEVEL(ISC_LOG_WARNING), format, ##__VA_ARGS__)
+
 #define log_info(format, ...)	\
 	log_write(GET_LOG_LEVEL(ISC_LOG_INFO), format, ##__VA_ARGS__)
 
-- 
2.5.5

From da5c01746e6322f0d860335150e1bfcf474eb351 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Wed, 6 Apr 2016 13:29:03 +0200
Subject: [PATCH] Unload automatic empty zones only if conflicting forward zone
 has policy 'only'.

This is exactly what BIND does so now we are compatible with BIND
semantics.

A warning message is printed when an conflicting forward zone with
policy 'first' is configured. This mimics behavior implemented in
BIND RT #41441 for BIND 9.11.

Unfortunatelly this is incompatible change when compared to
bind-dyndb-ldap 3.3+ which was unloading zones without regard to the forwarding
policy. On the other hand, bind-dyndb-ldap 3.3+ was unloading only some
subset of empty zones so its behavior was broken anyway.

During upgrade users should to convert all forward zones which are
conflicting with automatic empty zones to forward policy 'only'.

Master zones are not affected by this change.

https://fedorahosted.org/bind-dyndb-ldap/ticket/160
---
 src/empty_zones.c | 24 +++++++++++++++++++-----
 src/empty_zones.h |  4 +++-
 src/ldap_helper.c | 11 +++++++++--
 3 files changed, 31 insertions(+), 8 deletions(-)

diff --git a/src/empty_zones.c b/src/empty_zones.c
index 0ecb74db3d177d8ed68554718a720f0aeadceb86..f5a07b8507b684c3b1e2151300d2a5cc9683f5d8 100644
--- a/src/empty_zones.c
+++ b/src/empty_zones.c
@@ -252,7 +252,8 @@ cleanup:
 
 /**
  * Detect if given name is super/sub/equal domain to any of automatic empty
- * zones. If such empty zone is found, it will be automatically unloaded so
+ * zones. If such empty zone is found and warn_only == FALSE,
+ * the conflicting empty zone will be automatically unloaded so
  * forwarding will work as configured by user.
  *
  * It allows queries to leak to the public Internet if:
@@ -264,22 +265,34 @@ cleanup:
  * b) Forwarder is a superdomain but it failed and user configured policy != only.
  */
 isc_result_t
-empty_zone_handle_conflicts(dns_name_t *name, dns_zt_t *zonetable)
+empty_zone_handle_conflicts(dns_name_t *name, dns_zt_t *zonetable,
+			    isc_boolean_t warn_only)
 {
 	isc_result_t result;
 	isc_boolean_t first = ISC_TRUE;
 	empty_zone_search_t eziter;
+	char name_char[DNS_NAME_FORMATSIZE];
+	char ezname_char[DNS_NAME_FORMATSIZE];
 
 	for (result = empty_zone_search_init(&eziter, name);
 	     result == ISC_R_SUCCESS;
 	     result = empty_zone_search_next(&eziter))
 	{
+		dns_name_format(name, name_char, DNS_NAME_FORMATSIZE);
+		if (warn_only == ISC_TRUE) {
+			dns_name_format(&eziter.ezname, ezname_char,
+					DNS_NAME_FORMATSIZE);
+			log_warn("ignoring inherited 'forward first;' for zone "
+				 "'%s' - did you want 'forward only;' "
+				 "to override automatic empty zone '%s'?",
+				 name_char, ezname_char);
+			continue;
+		}
+
 		/* Shutdown automatic empty zone if it is present. */
 		result = empty_zone_unload(&eziter.ezname, zonetable);
 		if (result == ISC_R_SUCCESS) {
 			if (first == ISC_TRUE) {
-				char name_char[DNS_NAME_FORMATSIZE];
-				dns_name_format(name, name_char, DNS_NAME_FORMATSIZE);
 				log_info("shutting down automatic empty zones to "
 					 "enable forwarding for domain '%s'", name_char);
 				first = ISC_FALSE;
@@ -310,7 +323,8 @@ empty_zone_handle_globalfwd_ev(isc_task_t *task, isc_event_t *event)
 	REQUIRE(event != NULL);
 
 	pevent = (ldap_globalfwd_handleez_t *)event;
-	RUNTIME_CHECK(empty_zone_handle_conflicts(dns_rootname, pevent->ev_arg)
+	RUNTIME_CHECK(empty_zone_handle_conflicts(dns_rootname, pevent->ev_arg,
+						  pevent->warn_only)
 		      == ISC_R_SUCCESS);
 
 	isc_event_free(&event);
diff --git a/src/empty_zones.h b/src/empty_zones.h
index 3f805672173656f16ff373b1ef2c831d15babf88..513f95d87c97d5db975d8686d48c7efecf3a0c16 100644
--- a/src/empty_zones.h
+++ b/src/empty_zones.h
@@ -18,14 +18,16 @@ isc_result_t
 empty_zone_search_init(empty_zone_search_t *iter, dns_name_t *qname) ATTR_NONNULLS ATTR_CHECKRESULT;
 
 isc_result_t
-empty_zone_handle_conflicts(dns_name_t *name, dns_zt_t *zonetable) ATTR_NONNULLS ATTR_CHECKRESULT;
+empty_zone_handle_conflicts(dns_name_t *name, dns_zt_t *zonetable,
+			    isc_boolean_t warn_only) ATTR_NONNULLS ATTR_CHECKRESULT;
 
 /* Trigger to execute empty_zone_handle_conflicts() for dns_rootname. */
 #define LDAPDB_EVENT_GLOBALFWD_HANDLEEZ	(LDAPDB_EVENTCLASS + 5)
 
 typedef struct ldap_globalfwd_handleez ldap_globalfwd_handleez_t;
 struct ldap_globalfwd_handleez {
 	ISC_EVENT_COMMON(ldap_globalfwd_handleez_t);
+	isc_boolean_t warn_only;
 };
 
 void
diff --git a/src/ldap_helper.c b/src/ldap_helper.c
index 5338ab114b7426a6ed30299c07e43bfb8cc2373d..46204865d4b1617356fcbb15aacd12569008538c 100644
--- a/src/ldap_helper.c
+++ b/src/ldap_helper.c
@@ -583,16 +583,22 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
 
 		/* Make sure we disable conflicting automatic empty zones.
 		 * This will be done in event to prevent the plugin from
-		 * interfering with BIND start-up. */
+		 * interfering with BIND start-up.
+		 *
+		 * Warn-only semantics is implemented in BIND RT#41441,
+		 * this code can be removed when we rebase to BIND 9.11. */
 		CHECK(sync_task_add(ldap_inst->sctx, task));
 		gfwdevent = (ldap_globalfwd_handleez_t *)isc_event_allocate(
 					ldap_inst->mctx, ldap_inst,
 					LDAPDB_EVENT_GLOBALFWD_HANDLEEZ,
 					empty_zone_handle_globalfwd_ev,
 					ldap_inst->view->zonetable,
 					sizeof(ldap_globalfwd_handleez_t));
 		if (gfwdevent == NULL)
 			CLEANUP_WITH(ISC_R_NOMEMORY);
+		/* policy == first does not override automatic empty zones */
+		gfwdevent->warn_only = (orig_global_forwarders->fwdpolicy
+					== dns_fwdpolicy_first);
 
 		isc_task_send(task, (isc_event_t **)&gfwdevent);
 
@@ -1585,7 +1591,8 @@ configure_zone_forwarders(ldap_entry_t *entry, ldap_instance_t *inst,
 		if (fwdpolicy != dns_fwdpolicy_none) {
 			/* Handle collisions with automatic empty zones. */
 			CHECK(empty_zone_handle_conflicts(name,
-							  inst->view->zonetable));
+							  inst->view->zonetable,
+							  (fwdpolicy == dns_fwdpolicy_first)));
 		}
 
 		/* Something was changed - set forward table up. */
-- 
2.5.5

-- 
Manage your subscription for the Freeipa-devel mailing list:
https://www.redhat.com/mailman/listinfo/freeipa-devel
Contribute to FreeIPA: http://www.freeipa.org/page/Contribute/Code

Reply via email to