Hello,

this patch set is preparation work for per-server config in LDAP, which is
required for DNS location in IPA.

This patch set should not cause any user-visible changes.

https://fedorahosted.org/bind-dyndb-ldap/ticket/162

-- 
Petr^2 Spacek
From 5a4e0b7026dc4f7f786d1d59a3a9ad33bfe89e30 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Wed, 4 May 2016 16:20:44 +0200
Subject: [PATCH] Fix in destroy_ldap_instance() caused by uninitialized
 MetaLDAP.

This happened only if an new_ldap_instance() failed with an error before
initializing MetaLDAP.
---
 src/mldap.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/mldap.c b/src/mldap.c
index 8cffe8a1fbf8eaa20aae79c28ad8d7a305494f19..143abce757f3d65e68356a2c0e660c475ed0ab58 100644
--- a/src/mldap.c
+++ b/src/mldap.c
@@ -79,7 +79,7 @@ void
 mldap_destroy(mldapdb_t **mldapp) {
 	mldapdb_t *mldap;
 
-	REQUIRE(mldapp != NULL && *mldapp != NULL);
+	REQUIRE(mldapp != NULL);
 
 	mldap = *mldapp;
 	if (mldap == NULL)
-- 
2.5.5

From 6c4cabd8be5d90502786a5f4356dbb4742ec7a70 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Thu, 2 Jun 2016 15:30:19 +0200
Subject: [PATCH] Separate BIND config utilities from ACL parsing.

In future we will use the code from multiple modules.
There should be no user-visible changes caused by this split.

https://fedorahosted.org/bind-dyndb-ldap/ticket/162
---
 src/Makefile.am    |   2 +
 src/acl.c          | 118 +++--------------------------------------------------
 src/bindcfg.c      | 116 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/bindcfg.h      |  24 +++++++++++
 src/zone_manager.c |   3 ++
 5 files changed, 151 insertions(+), 112 deletions(-)
 create mode 100644 src/bindcfg.c
 create mode 100644 src/bindcfg.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 58f73ec9d37471471036fb532ac239523512eb80..595fb4d95577f025e97dbce0770325a82a053ad8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -3,6 +3,7 @@ bindplugindir=$(libdir)/bind
 
 HDRS =				\
 	acl.h			\
+	bindcfg.h		\
 	compat.h		\
 	empty_zones.h		\
 	fs.h			\
@@ -31,6 +32,7 @@ HDRS =				\
 ldap_la_SOURCES =		\
 	$(HDRS)			\
 	acl.c			\
+	bindcfg.c		\
 	empty_zones.c		\
 	fwd_register.c		\
 	fs.c			\
diff --git a/src/acl.c b/src/acl.c
index e4b3f524876ac93a177613c7f325883398367af8..d03a34d8c4ba5016ae9b248dc86781952d92c8d4 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -6,13 +6,11 @@
 
 #include <isccfg/aclconf.h>
 #include <isccfg/cfg.h>
-#include <isccfg/namedconf.h>
 #include <isccfg/grammar.h>
 
 #include <isc/buffer.h>
 #include <isc/log.h>
 #include <isc/mem.h>
-#include <isc/once.h>
 #include <isc/result.h>
 #include <isc/types.h>
 #include <isc/util.h>
@@ -30,17 +28,12 @@
 #include <strings.h>
 
 #include "acl.h"
+#include "bindcfg.h"
 #include "str.h"
 #include "util.h"
 #include "log.h"
 #include "types.h"
 
-static isc_once_t once = ISC_ONCE_INIT;
-static cfg_type_t *update_policy;
-static cfg_type_t *allow_query;
-static cfg_type_t *allow_transfer;
-static cfg_type_t *forwarders;
-
 /* Following definitions are necessary for context ("map" configuration object)
  * required during ACL parsing. */
 static cfg_clausedef_t * empty_map_clausesets[] = {
@@ -60,105 +53,6 @@ const enum_txt_assoc_t acl_type_txts[] = {
 	{ -1,			NULL		} /* end marker */
 };
 
-static cfg_type_t * ATTR_NONNULLS ATTR_CHECKRESULT
-get_type_from_tuplefield(const cfg_type_t *cfg_type, const char *name)
-{
-	cfg_type_t *ret = NULL;
-	const cfg_tuplefielddef_t *field;
-
-	REQUIRE(cfg_type != NULL && cfg_type->of != NULL);
-	REQUIRE(name != NULL);
-
-	field = (cfg_tuplefielddef_t *)cfg_type->of;
-	for (int i = 0; field[i].name != NULL; i++) {
-		if (!strcmp(field[i].name, name)) {
-			ret = field[i].type;
-			break;
-		}
-	}
-
-	return ret;
-}
-
-static cfg_type_t * ATTR_NONNULLS ATTR_CHECKRESULT
-get_type_from_clause(const cfg_clausedef_t *clause, const char *name)
-{
-	cfg_type_t *ret = NULL;
-
-	REQUIRE(clause != NULL);
-	REQUIRE(name != NULL);
-
-	for (int i = 0; clause[i].name != NULL; i++) {
-		if (!strcmp(clause[i].name, name)) {
-			ret = clause[i].type;
-			break;
-		}
-	}
-
-	return ret;
-}
-
-static cfg_type_t * ATTR_NONNULLS ATTR_CHECKRESULT
-get_type_from_clause_array(const cfg_type_t *cfg_type, const char *name)
-{
-	cfg_type_t *ret = NULL;
-	const cfg_clausedef_t **clauses;
-
-	REQUIRE(cfg_type != NULL && cfg_type->of != NULL);
-	REQUIRE(name != NULL);
-
-	clauses = (const cfg_clausedef_t **)cfg_type->of;
-	for (int i = 0; clauses[i] != NULL; i++) {
-		ret = get_type_from_clause(clauses[i], name);
-		if (ret != NULL)
-			break;
-	}
-
-	return ret;
-}
-
-static void
-init_cfgtypes(void)
-{
-	cfg_type_t *zoneopts;
-
-	zoneopts = &cfg_type_namedconf;
-	zoneopts = get_type_from_clause_array(zoneopts, "zone");
-	zoneopts = get_type_from_tuplefield(zoneopts, "options");
-
-	update_policy = get_type_from_clause_array(zoneopts, "update-policy");
-	allow_query = get_type_from_clause_array(zoneopts, "allow-query");
-	allow_transfer = get_type_from_clause_array(zoneopts, "allow-transfer");
-	forwarders = get_type_from_clause_array(zoneopts, "forwarders");
-}
-
-static isc_result_t ATTR_NONNULLS ATTR_CHECKRESULT
-parse(cfg_parser_t *parser, const char *string, cfg_type_t **type,
-      cfg_obj_t **objp)
-{
-	isc_result_t result;
-	isc_buffer_t buffer;
-	size_t string_len;
-	cfg_obj_t *ret = NULL;
-
-	REQUIRE(parser != NULL);
-	REQUIRE(string != NULL);
-	REQUIRE(objp != NULL && *objp == NULL);
-
-	RUNTIME_CHECK(isc_once_do(&once, init_cfgtypes) == ISC_R_SUCCESS);
-
-	string_len = strlen(string);
-	isc_buffer_init(&buffer, (char *)string, string_len);
-	isc_buffer_add(&buffer, string_len);
-
-	result = cfg_parse_buffer(parser, &buffer, *type, &ret);
-
-	if (result == ISC_R_SUCCESS)
-		*objp = ret;
-
-	return result;
-}
-
 /*
  * The rest of the code in this file is either copied from, or based on code
  * from ISC BIND, file bin/named/config.c.
@@ -408,7 +302,7 @@ acl_configure_zone_ssutable(const char *policy_str, dns_zone_t *zone)
 	CHECK(bracket_str(mctx, policy_str, &new_policy_str));
 
 	CHECK(cfg_parser_create(mctx, dns_lctx, &parser));
-	result = parse(parser, str_buf(new_policy_str), &update_policy, &policy);
+	result = cfg_parse_strbuf(parser, str_buf(new_policy_str), &cfg_type_update_policy, &policy);
 
 	if (result != ISC_R_SUCCESS) {
 		dns_zone_log(zone, ISC_LOG_ERROR,
@@ -509,15 +403,15 @@ acl_from_ldap(isc_mem_t *mctx, const char *aclstr, acl_type_t type,
 
 	CHECK(cfg_parser_create(mctx, dns_lctx, &parser));
 	CHECK(cfg_parser_create(mctx, dns_lctx, &parser_empty));
-	CHECK(parse(parser_empty, "{}", &empty_map_p, &cctx));
+	CHECK(cfg_parse_strbuf(parser_empty, "{}", &empty_map_p, &cctx));
 
 	switch (type) {
 	case acl_type_query:
-		CHECK(parse(parser, str_buf(new_aclstr), &allow_query,
+		CHECK(cfg_parse_strbuf(parser, str_buf(new_aclstr), &cfg_type_allow_query,
 			    &aclobj));
 		break;
 	case acl_type_transfer:
-		CHECK(parse(parser, str_buf(new_aclstr), &allow_transfer,
+		CHECK(cfg_parse_strbuf(parser, str_buf(new_aclstr), &cfg_type_allow_transfer,
 			    &aclobj));
 		break;
 	default:
@@ -602,7 +496,7 @@ acl_parse_forwarder(const char *forwarder_str, isc_mem_t *mctx,
 		CHECK(bracket_str(mctx, forwarder_str, &new_forwarder_str));
 
 	CHECK(cfg_parser_create(mctx, dns_lctx, &parser));
-	CHECK(parse(parser, str_buf(new_forwarder_str), &forwarders, &forwarders_cfg));
+	CHECK(cfg_parse_strbuf(parser, str_buf(new_forwarder_str), &cfg_type_forwarders, &forwarders_cfg));
 
 	faddresses = cfg_tuple_get(forwarders_cfg, "addresses");
 	element = cfg_list_first(faddresses);
diff --git a/src/bindcfg.c b/src/bindcfg.c
new file mode 100644
index 0000000000000000000000000000000000000000..9b429ba31c8ca800062237c80481eb53a81122c9
--- /dev/null
+++ b/src/bindcfg.c
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2009-2016  bind-dyndb-ldap authors; see COPYING for license
+ *
+ * Utilities for BIND configuration parsers.
+ */
+
+#include "config.h"
+
+#include <isccfg/grammar.h>
+#include <isccfg/namedconf.h>
+
+#include <util.h>
+
+#include "bindcfg.h"
+
+cfg_type_t *cfg_type_update_policy;
+cfg_type_t *cfg_type_allow_query;
+cfg_type_t *cfg_type_allow_transfer;
+cfg_type_t *cfg_type_forwarders;
+
+static cfg_type_t * ATTR_NONNULLS ATTR_CHECKRESULT
+get_type_from_tuplefield(const cfg_type_t *cfg_type, const char *name)
+{
+	cfg_type_t *ret = NULL;
+	const cfg_tuplefielddef_t *field;
+
+	REQUIRE(cfg_type != NULL && cfg_type->of != NULL);
+	REQUIRE(name != NULL);
+
+	field = (cfg_tuplefielddef_t *)cfg_type->of;
+	for (int i = 0; field[i].name != NULL; i++) {
+		if (!strcmp(field[i].name, name)) {
+			ret = field[i].type;
+			break;
+		}
+	}
+
+	return ret;
+}
+
+static cfg_type_t * ATTR_NONNULLS ATTR_CHECKRESULT
+get_type_from_clause(const cfg_clausedef_t *clause, const char *name)
+{
+	cfg_type_t *ret = NULL;
+
+	REQUIRE(clause != NULL);
+	REQUIRE(name != NULL);
+
+	for (int i = 0; clause[i].name != NULL; i++) {
+		if (!strcmp(clause[i].name, name)) {
+			ret = clause[i].type;
+			break;
+		}
+	}
+
+	return ret;
+}
+
+static cfg_type_t * ATTR_NONNULLS ATTR_CHECKRESULT
+get_type_from_clause_array(const cfg_type_t *cfg_type, const char *name)
+{
+	cfg_type_t *ret = NULL;
+	const cfg_clausedef_t **clauses;
+
+	REQUIRE(cfg_type != NULL && cfg_type->of != NULL);
+	REQUIRE(name != NULL);
+
+	clauses = (const cfg_clausedef_t **)cfg_type->of;
+	for (int i = 0; clauses[i] != NULL; i++) {
+		ret = get_type_from_clause(clauses[i], name);
+		if (ret != NULL)
+			break;
+	}
+
+	return ret;
+}
+
+void
+cfg_init_types(void)
+{
+	cfg_type_t *zoneopts;
+
+	zoneopts = &cfg_type_namedconf;
+	zoneopts = get_type_from_clause_array(zoneopts, "zone");
+	zoneopts = get_type_from_tuplefield(zoneopts, "options");
+
+	cfg_type_update_policy = get_type_from_clause_array(zoneopts, "update-policy");
+	cfg_type_allow_query = get_type_from_clause_array(zoneopts, "allow-query");
+	cfg_type_allow_transfer = get_type_from_clause_array(zoneopts, "allow-transfer");
+	cfg_type_forwarders = get_type_from_clause_array(zoneopts, "forwarders");
+}
+
+isc_result_t
+cfg_parse_strbuf(cfg_parser_t *parser, const char *string, cfg_type_t **type,
+		 cfg_obj_t **objp)
+{
+	isc_result_t result;
+	isc_buffer_t buffer;
+	size_t string_len;
+	cfg_obj_t *ret = NULL;
+
+	REQUIRE(parser != NULL);
+	REQUIRE(string != NULL);
+	REQUIRE(objp != NULL && *objp == NULL);
+
+	string_len = strlen(string);
+	isc_buffer_init(&buffer, (char *)string, string_len);
+	isc_buffer_add(&buffer, string_len);
+
+	result = cfg_parse_buffer(parser, &buffer, *type, &ret);
+
+	if (result == ISC_R_SUCCESS)
+		*objp = ret;
+
+	return result;
+}
diff --git a/src/bindcfg.h b/src/bindcfg.h
new file mode 100644
index 0000000000000000000000000000000000000000..a7b882d6a0486ffa539374d8fd91478ec122aab8
--- /dev/null
+++ b/src/bindcfg.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2009-2016  bind-dyndb-ldap authors; see COPYING for license
+ */
+
+#ifndef _LD_BINDCFG_H_
+#define _LD_BINDCFG_H_
+
+#include <isccfg/cfg.h>
+
+#include "util.h"
+
+extern cfg_type_t *cfg_type_update_policy;
+extern cfg_type_t *cfg_type_allow_query;
+extern cfg_type_t *cfg_type_allow_transfer;
+extern cfg_type_t *cfg_type_forwarders;
+
+void
+cfg_init_types(void);
+
+isc_result_t ATTR_NONNULLS ATTR_CHECKRESULT
+cfg_parse_strbuf(cfg_parser_t *parser, const char *string, cfg_type_t **type,
+		 cfg_obj_t **objp);
+
+#endif /* _LD_BINDCFG_H_ */
diff --git a/src/zone_manager.c b/src/zone_manager.c
index 93e3fe5675713876d02ce52d036aa2f8a90e288e..85e19fb21ad1167338fdd11dc86e8b62c9f4d39e 100644
--- a/src/zone_manager.c
+++ b/src/zone_manager.c
@@ -18,6 +18,8 @@
 #include <unistd.h>
 
 #include "config.h"
+
+#include "bindcfg.h"
 #include "ldap_convert.h"
 #include "ldap_helper.h"
 #include "log.h"
@@ -50,6 +52,7 @@ initialize_manager(void)
 	log_info("bind-dyndb-ldap version " VERSION
 		 " compiled at " __TIME__ " " __DATE__
 		 ", compiler " __VERSION__);
+	cfg_init_types();
 }
 
 void
-- 
2.5.5

From 758f19f84aa6ebbcfe17fc9e1a01091342f63631 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Thu, 2 Jun 2016 15:36:59 +0200
Subject: [PATCH] Settings: Support enum description->value mapping.

https://fedorahosted.org/bind-dyndb-ldap/ticket/162
---
 src/settings.c | 20 ++++++++++++++++++++
 src/settings.h |  4 ++++
 2 files changed, 24 insertions(+)

diff --git a/src/settings.c b/src/settings.c
index 5755f83c4c92125b19af00dd724d9927c4bdc89c..a54abfb42af2a7e426e489f83fd218b76a5c0138 100644
--- a/src/settings.c
+++ b/src/settings.c
@@ -659,3 +659,23 @@ get_enum_description(const enum_txt_assoc_t *map, int value, const char **desc)
 	}
 	return ISC_R_NOTFOUND;
 }
+
+isc_result_t
+get_enum_value(const enum_txt_assoc_t *map, const char *description,
+	       int *value) {
+	const enum_txt_assoc_t *record;
+
+	REQUIRE(map != NULL);
+	REQUIRE(description != NULL);
+	REQUIRE(value != NULL);
+
+	for (record = map;
+	     record->description != NULL && record->value != -1;
+	     record++) {
+		if (strcasecmp(record->description, description) == 0) {
+			*value = record->value;
+			return ISC_R_SUCCESS;
+		}
+	}
+	return ISC_R_NOTFOUND;
+}
diff --git a/src/settings.h b/src/settings.h
index eeae00add799e525241492b16e84ed14cf73c846..c2b9cd7174f43cdeae57b5c601e9f6ec5d646104 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -111,4 +111,8 @@ setting_update_from_ldap_entry(const char *name, settings_set_t *set,
 isc_result_t
 get_enum_description(const enum_txt_assoc_t *map, int value, const char **desc) ATTR_NONNULLS ATTR_CHECKRESULT;
 
+isc_result_t
+get_enum_value(const enum_txt_assoc_t *map, const char *description,
+	       int *value) ATTR_NONNULLS ATTR_CHECKRESULT;
+
 #endif /* !_LD_SETTINGS_H_ */
-- 
2.5.5

From df82481bb2e2edcadc9159e1244b0289950f9def Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Thu, 2 Jun 2016 15:37:49 +0200
Subject: [PATCH] Settings: Expose API for setting_unset and setting_find to
 other modules.

https://fedorahosted.org/bind-dyndb-ldap/ticket/162
---
 src/settings.c | 2 +-
 src/settings.h | 9 +++++++++
 2 files changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/settings.c b/src/settings.c
index a54abfb42af2a7e426e489f83fd218b76a5c0138..24004248ac706a5594ec803d79d99b2fa9335dc4 100644
--- a/src/settings.c
+++ b/src/settings.c
@@ -354,7 +354,7 @@ cleanup:
  * @retval ISC_R_NOTFOUND Required setting was not found
  *                        in given set of settings.
  */
-static isc_result_t
+isc_result_t
 setting_unset(const char *const name, const settings_set_t *set)
 {
 	isc_result_t result;
diff --git a/src/settings.h b/src/settings.h
index c2b9cd7174f43cdeae57b5c601e9f6ec5d646104..e1d46f80392a2f7bcd42b01e3997ac4ff0c67a45 100644
--- a/src/settings.h
+++ b/src/settings.h
@@ -88,6 +88,11 @@ isc_boolean_t
 settings_set_isfilled(settings_set_t *set) ATTR_NONNULLS ATTR_CHECKRESULT;
 
 isc_result_t
+setting_find(const char *name, const settings_set_t *set,
+	     isc_boolean_t recursive, isc_boolean_t filled_only,
+	     setting_t **found) ATTR_CHECKRESULT;
+
+isc_result_t
 setting_get_uint(const char * const name, const settings_set_t * const set,
 		 isc_uint32_t * target) ATTR_NONNULLS ATTR_CHECKRESULT;
 
@@ -104,6 +109,10 @@ setting_set(const char *const name, const settings_set_t *set,
 	    const char *const value) ATTR_NONNULLS ATTR_CHECKRESULT;
 
 isc_result_t
+setting_unset(const char *const name, const settings_set_t *set)
+ATTR_NONNULLS ATTR_CHECKRESULT;
+
+isc_result_t
 setting_update_from_ldap_entry(const char *name, settings_set_t *set,
 			       const char *attr_name, ldap_entry_t *entry)
 			       ATTR_NONNULLS ATTR_CHECKRESULT;
-- 
2.5.5

From 8fc9d186f62ae2730045a928e9852264808c3c88 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Thu, 2 Jun 2016 17:08:24 +0200
Subject: [PATCH] Untangle forwarder configuration from LDAP code.

Previously code handling forwarders was so interleaved with LDAP code
that it was impossible to add another layer for per-server config.

This is preparatory work which untangles LDAP and forwarders to enable
further development.

As far as I can tell the user-visible behavior should not be changed by
this patch.

https://fedorahosted.org/bind-dyndb-ldap/ticket/162
---
 src/Makefile.am   |   2 +
 src/acl.c         |  99 ---------
 src/acl.h         |  12 +-
 src/fwd.c         | 585 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/fwd.h         |  37 ++++
 src/ldap_helper.c | 457 +++++++++---------------------------------
 src/ldap_helper.h |   7 +
 7 files changed, 729 insertions(+), 470 deletions(-)
 create mode 100644 src/fwd.c
 create mode 100644 src/fwd.h

diff --git a/src/Makefile.am b/src/Makefile.am
index 595fb4d95577f025e97dbce0770325a82a053ad8..238d8ef209fb320cd1a6c3fcdc35af411c7c8373 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -7,6 +7,7 @@ HDRS =				\
 	compat.h		\
 	empty_zones.h		\
 	fs.h			\
+	fwd.h			\
 	fwd_register.h		\
 	krb5_helper.h		\
 	ldap_convert.h		\
@@ -34,6 +35,7 @@ ldap_la_SOURCES =		\
 	acl.c			\
 	bindcfg.c		\
 	empty_zones.c		\
+	fwd.c			\
 	fwd_register.c		\
 	fs.c			\
 	krb5_helper.c		\
diff --git a/src/acl.c b/src/acl.c
index d03a34d8c4ba5016ae9b248dc86781952d92c8d4..0d475f08b42ed7bf83574924b7336cab7280abf0 100644
--- a/src/acl.c
+++ b/src/acl.c
@@ -263,24 +263,6 @@ cleanup:
 	return result;
 }
 
-static isc_result_t ATTR_NONNULLS ATTR_CHECKRESULT
-semicolon_bracket_str(isc_mem_t *mctx, const char *str, ld_string_t **bracket_strp)
-{
-	ld_string_t *tmp = NULL;
-	isc_result_t result;
-
-	CHECK(str_new(mctx, &tmp));
-	CHECK(str_sprintf(tmp, "{ %s; }", str));
-
-	*bracket_strp = tmp;
-
-	return ISC_R_SUCCESS;
-
-cleanup:
-	str_destroy(&tmp);
-	return result;
-}
-
 isc_result_t
 acl_configure_zone_ssutable(const char *policy_str, dns_zone_t *zone)
 {
@@ -445,84 +427,3 @@ cleanup:
 
 	return result;
 }
-
-
-/**
- * Parse nameserver IP address with or without port specified in BIND9 syntax.
- * IPv4 and IPv6 addresses are supported, see examples.
- *
- * @param[in] forwarder_str String with IP address and optionally port.
- * @param[in] mctx Memory for allocating temporary and sa structure.
- * @param[out] sa Socket address structure.
- *
- * @return Pointer to newly allocated isc_sockaddr_t structure.
- *
- * @example
- * "192.168.0.100" -> { address:192.168.0.100, port:53 }
- * "192.168.0.100 port 553" -> { address:192.168.0.100, port:553 }
- * "0102:0304:0506:0708:090A:0B0C:0D0E:0FAA" ->
- * 		{ address:0102:0304:0506:0708:090A:0B0C:0D0E:0FAA, port:53 }
- * "0102:0304:0506:0708:090A:0B0C:0D0E:0FAA port 553" ->
- * 		{ address:0102:0304:0506:0708:090A:0B0C:0D0E:0FAA, port:553 }
- *
- */
-isc_result_t
-acl_parse_forwarder(const char *forwarder_str, isc_mem_t *mctx,
-#if LIBDNS_VERSION_MAJOR < 140
-		isc_sockaddr_t **fw)
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-		dns_forwarder_t **fw)
-#endif
-{
-	isc_result_t result = ISC_R_SUCCESS;
-	cfg_parser_t *parser = NULL;
-
-	cfg_obj_t *forwarders_cfg = NULL;
-	ld_string_t *new_forwarder_str = NULL;
-	const cfg_obj_t *faddresses;
-	const cfg_listelt_t *element;
-	const cfg_obj_t *forwarder;
-	isc_sockaddr_t addr;
-
-	in_port_t port = 53;
-
-	REQUIRE(forwarder_str != NULL);
-	REQUIRE(fw != NULL && *fw == NULL);
-
-	/* add semicolon and brackets as necessary for parser */
-	if (!index(forwarder_str, ';'))
-		CHECK(semicolon_bracket_str(mctx, forwarder_str, &new_forwarder_str));
-	else
-		CHECK(bracket_str(mctx, forwarder_str, &new_forwarder_str));
-
-	CHECK(cfg_parser_create(mctx, dns_lctx, &parser));
-	CHECK(cfg_parse_strbuf(parser, str_buf(new_forwarder_str), &cfg_type_forwarders, &forwarders_cfg));
-
-	faddresses = cfg_tuple_get(forwarders_cfg, "addresses");
-	element = cfg_list_first(faddresses);
-	if (!element) {
-		result = ISC_R_FAILURE;
-		goto cleanup;
-	}
-
-	forwarder = cfg_listelt_value(element);
-	CHECKED_MEM_GET_PTR(mctx, *fw);
-	addr = *cfg_obj_assockaddr(forwarder);
-	if (isc_sockaddr_getport(&addr) == 0)
-		isc_sockaddr_setport(&addr, port);
-#if LIBDNS_VERSION_MAJOR < 140
-	**fw = addr;
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-	(*fw)->addr = addr;
-	(*fw)->dscp = cfg_obj_getdscp(forwarder);
-#endif
-
-
-cleanup:
-	if (forwarders_cfg != NULL)
-		cfg_obj_destroy(parser, &forwarders_cfg);
-	if (parser != NULL)
-		cfg_parser_destroy(&parser);
-	str_destroy(&new_forwarder_str);
-	return result;
-}
diff --git a/src/acl.h b/src/acl.h
index ef1fcb780df4af4c2d661ea2715f629614d168da..ce1210ec28185cc1ec347489b4fc04ae4b568ead 100644
--- a/src/acl.h
+++ b/src/acl.h
@@ -5,6 +5,9 @@
 #ifndef _LD_ACL_H_
 #define _LD_ACL_H_
 
+#include "config.h"
+
+#include "ldap_entry.h"
 #include "types.h"
 
 #include <dns/acl.h>
@@ -28,13 +31,4 @@ acl_from_ldap(isc_mem_t *mctx, const char *aclstr, acl_type_t type,
  * Please refer to BIND 9 ARM (Administrator Reference Manual) about ACLs.
  */
 
-isc_result_t
-acl_parse_forwarder(const char *forwarders_str, isc_mem_t *mctx,
-#if LIBDNS_VERSION_MAJOR < 140
-		isc_sockaddr_t **fw)
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-		dns_forwarder_t **fw)
-#endif
-ATTR_NONNULLS ATTR_CHECKRESULT;
-
 #endif /* !_LD_ACL_H_ */
diff --git a/src/fwd.c b/src/fwd.c
new file mode 100644
index 0000000000000000000000000000000000000000..e17b55a77cfc7f2813b891dbc9cccd3ad9c2ebc1
--- /dev/null
+++ b/src/fwd.c
@@ -0,0 +1,585 @@
+/**
+ * Copyright (C) 2016  bind-dyndb-ldap authors; see COPYING for license
+ *
+ * DNS forwarding helpers.
+ */
+
+#include "config.h"
+
+#include <isccfg/grammar.h>
+
+#include <dns/forward.h>
+#include <dns/fixedname.h>
+#include <dns/view.h>
+
+#include "bindcfg.h"
+#include "empty_zones.h"
+#include "fwd.h"
+#include "ldap_helper.h"
+#include "lock.h"
+#include "settings.h"
+
+const enum_txt_assoc_t forwarder_policy_txts[] = {
+	{ dns_fwdpolicy_none,	"none"	},
+	{ dns_fwdpolicy_first,	"first"	},
+	{ dns_fwdpolicy_only,	"only"	},
+	{ -1,			NULL	} /* end marker */
+};
+
+/**
+ * @pre closure points to a valid isc_buffer
+ * @pre isc_buffer has non-NULL mctx
+ * @pre isc_buffer has NULL buffer OR a buffer allocated from mctx
+ *
+ * @post closure contains \0 terminated string which is concatenation
+ *       of previous context and input text
+ */
+static void
+buffer_append_str(void *closure, const char *text, int textlen) {
+	isc_buffer_t *out_buf = closure;
+	isc_region_t new_space;
+	isc_region_t old_space;
+
+	REQUIRE(ISC_BUFFER_VALID(out_buf));
+	REQUIRE(out_buf->mctx != NULL);
+	REQUIRE(text != NULL);
+
+	/* Allocate sufficiently long output buffer. */
+	isc_buffer_region(out_buf, &old_space);
+	new_space.length = isc_buffer_length(out_buf) + textlen + 1;
+	new_space.base = isc_mem_get(out_buf->mctx, new_space.length);
+	RUNTIME_CHECK(new_space.base != NULL);
+	isc_buffer_reinit(out_buf, new_space.base, new_space.length);
+	if (old_space.base != NULL)
+		isc_mem_put(out_buf->mctx, old_space.base, old_space.length);
+
+	/* Append output text and \0-terminate it.
+	 * Overwrite \0 at the end if needed. */
+	if (isc_buffer_usedlength(out_buf) != 0)
+		/* Previous string is \0 terminated, chop \0. */
+		isc_buffer_subtract(out_buf, 1);
+	isc_buffer_putstr(out_buf, text);
+	isc_buffer_putuint8(out_buf, '\0');
+}
+
+static size_t
+fwd_list_len(dns_forwarders_t *fwdrs) {
+	size_t len = 0;
+
+	REQUIRE(fwdrs != NULL);
+
+#if LIBDNS_VERSION_MAJOR < 140
+	for (isc_sockaddr_t *fwdr = ISC_LIST_HEAD(fwdrs->addrs);
+#else /* LIBDNS_VERSION_MAJOR >= 140 */
+	for (dns_forwarder_t *fwdr = ISC_LIST_HEAD(fwdrs->fwdrs);
+#endif
+	     fwdr != NULL;
+	     fwdr = ISC_LIST_NEXT(fwdr, link)) {
+		len++;
+	}
+	return len;
+}
+
+/**
+ * Generate dummy string which looks like list of forwarders
+ * with list_len elements. This string might be fed into cfg parser.
+ *
+ * Caller has to deallocate resulting dummy_string.
+ */
+static isc_result_t
+fwd_list_gen_dummy_config_string(isc_mem_t *mctx, size_t list_len,
+				 isc_buffer_t **dummy_string) {
+	isc_result_t result;
+	const char prefix[] = "{ ";
+	const char suffix[] = "} // dummy string, please ignore";
+	const char fill[] = "127.0.0.1; ";
+	size_t target_size = sizeof(prefix) \
+			     + list_len*sizeof(fill)
+			     + sizeof(suffix)
+			     + 1; /* \0 */
+	isc_buffer_t *output = NULL;
+
+	REQUIRE(dummy_string != NULL && *dummy_string == NULL);
+
+	CHECK(isc_buffer_allocate(mctx, &output, target_size));
+	isc_buffer_putstr(output, prefix);
+	for (size_t i = 0; i < list_len; i++)
+		isc_buffer_putstr(output, fill);
+	isc_buffer_putstr(output, suffix);
+	isc_buffer_putuint8(output, '\0');
+	*dummy_string = output;
+
+cleanup:
+	if (result != ISC_R_SUCCESS && output != NULL)
+		isc_buffer_free(&output);
+
+	return result;
+}
+
+/**
+ * Generate list of all values as bracketed list.
+ * This string might be fed into cfg parser.
+ *
+ * Caller has to deallocate resulting output buffer.
+ */
+isc_result_t
+fwd_print_bracketed_values_buf(isc_mem_t *mctx, ldap_valuelist_t *values,
+			      isc_buffer_t **string) {
+	isc_result_t result;
+	ldap_value_t *value;
+	const char prefix[] = "{ ";
+	const char suffix[] = "}";
+	isc_buffer_t tmp_buf; /* hack: only the base buffer is allocated */
+
+	REQUIRE(string != NULL && *string == NULL);
+
+	isc_buffer_initnull(&tmp_buf);
+	tmp_buf.mctx = mctx;
+
+	buffer_append_str(&tmp_buf, prefix, 2);
+	for (value = HEAD(*values);
+	     value != NULL && value->value != NULL;
+	     value = NEXT(value, link)) {
+		buffer_append_str(&tmp_buf, value->value, strlen(value->value));
+		buffer_append_str(&tmp_buf, "; ", 2);
+	}
+	buffer_append_str(&tmp_buf, suffix, 2);
+
+	/* create and copy string from tmp to output buffer */
+	CHECK(isc_buffer_allocate(mctx, string, tmp_buf.used));
+	isc_buffer_putmem(*string, isc_buffer_base(&tmp_buf), tmp_buf.used);
+
+cleanup:
+	if (tmp_buf.base != NULL)
+		isc_mem_put(mctx, tmp_buf.base, tmp_buf.length);
+	return result;
+}
+
+isc_result_t
+fwd_print_list_buff(isc_mem_t *mctx, dns_forwarders_t *fwdrs,
+			 isc_buffer_t **out_buf) {
+	isc_result_t result;
+	size_t list_len;
+	isc_buffer_t *dummy_fwdr_buf = NULL; /* fully dynamic allocation */
+	isc_buffer_t tmp_buf; /* hack: only the base buffer is allocated */
+
+	cfg_parser_t *parser = NULL;
+	cfg_obj_t *forwarders_cfg = NULL;
+	const cfg_obj_t *faddresses;
+	const cfg_listelt_t *fwdr_cfg; /* config representation */
+	/* internal representation */
+#if LIBDNS_VERSION_MAJOR < 140
+	isc_sockaddr_t *fwdr_int;
+#else /* LIBDNS_VERSION_MAJOR >= 140 */
+	dns_forwarder_t *fwdr_int;
+#endif
+
+	isc_buffer_initnull(&tmp_buf);
+	tmp_buf.mctx = mctx;
+	CHECK(cfg_parser_create(mctx, dns_lctx, &parser));
+
+	/* Create dummy string with list of IP addresses of the same length
+	 * as the original list of forwarders. Parse this string to obtain
+	 * nested cfg structures which will be filled with data for actual
+	 * forwarders.
+	 *
+	 * This is nasty hack but it is easiest way to create list of cfg_objs
+	 * I found.
+	 */
+	list_len = fwd_list_len(fwdrs);
+	CHECK(fwd_list_gen_dummy_config_string(mctx,
+					       list_len, &dummy_fwdr_buf));
+	CHECK(cfg_parse_buffer(parser, dummy_fwdr_buf,
+			       cfg_type_forwarders, &forwarders_cfg));
+
+	/* Walk through internal representation and cfg representation and copy
+	 * data from the internal one to cfg data structures.*/
+	faddresses = cfg_tuple_get(forwarders_cfg, "addresses");
+	for (fwdr_int = ISC_LIST_HEAD(
+#if LIBDNS_VERSION_MAJOR < 140
+			fwdrs->addrs
+#else /* LIBDNS_VERSION_MAJOR >= 140 */
+			fwdrs->fwdrs
+#endif
+			), fwdr_cfg = cfg_list_first(faddresses);
+	     INSIST((fwdr_int == NULL) == (fwdr_cfg == NULL)), fwdr_int != NULL;
+	     fwdr_int = ISC_LIST_NEXT(fwdr_int, link), fwdr_cfg = cfg_list_next(fwdr_cfg)) {
+#if LIBDNS_VERSION_MAJOR < 140
+		fwdr_cfg->obj->value.sockaddr = *fwdr_int;
+#else /* LIBDNS_VERSION_MAJOR >= 140 */
+		fwdr_cfg->obj->value.sockaddrdscp.sockaddr = fwdr_int->addr;
+		fwdr_cfg->obj->value.sockaddrdscp.dscp = fwdr_int->dscp;
+#endif
+	}
+	cfg_print(faddresses, buffer_append_str, &tmp_buf);
+
+	/* create and copy string from tmp to output buffer */
+	CHECK(isc_buffer_allocate(mctx, out_buf, tmp_buf.used));
+	isc_buffer_putmem(*out_buf, isc_buffer_base(&tmp_buf),
+			  isc_buffer_usedlength(&tmp_buf));
+
+cleanup:
+	if (forwarders_cfg != NULL)
+		cfg_obj_destroy(parser, &forwarders_cfg);
+	if (parser != NULL)
+		cfg_parser_destroy(&parser);
+	if (dummy_fwdr_buf != NULL) {
+		if (tmp_buf.base != NULL)
+			isc_mem_put(mctx, tmp_buf.base, tmp_buf.length);
+		isc_buffer_free(&dummy_fwdr_buf);
+	}
+
+	return result;
+}
+
+/**
+ * Parse list of nameserver IP addresses with or without port specified
+ * in BIND9 syntax. IPv4 and IPv6 addresses are supported, see examples.
+ *
+ * @param[in]  forwarder_str String with IP addresses and optionally port.
+ * @param[in]  mctx	     Memory for allocating list of forwarders.
+ * @param[out] fwdrs	     Pointer to list of newly allocated forwarders.
+ *
+ * @return ISC_R_SUCCESS if parsing was successful
+ *
+ * @pre list of forwarders pointed to by fwdrs is empty
+ *
+ * @example
+ * "{ 192.168.0.100; }" -> { address:192.168.0.100, port:53 }
+ * "{ 192.168.0.100 port 553;} " -> { address:192.168.0.100, port:553 }
+ * "{ 0102:0304:0506:0708:090A:0B0C:0D0E:0FAA; }"
+ * 		-> { address:0102:0304:0506:0708:090A:0B0C:0D0E:0FAA, port:53 }
+ * "{ 0102:0304:0506:0708:090A:0B0C:0D0E:0FAA port 553; }" ->
+ * 		-> { address:0102:0304:0506:0708:090A:0B0C:0D0E:0FAA, port:553 }
+ * "{ 192.168.0.100; 0102:0304:0506:0708:090A:0B0C:0D0E:0FAA port 553; }"
+ * 		-> { address:192.168.0.100, port:53;
+ * 		     address:0102:0304:0506:0708:090A:0B0C:0D0E:0FAA, port:553 }
+ */
+
+static isc_result_t
+fwd_parse_str(const char *fwdrs_str, isc_mem_t *mctx,
+#if LIBDNS_VERSION_MAJOR < 140
+	isc_sockaddrlist_t *fwdrs
+#else /* LIBDNS_VERSION_MAJOR >= 140 */
+	dns_forwarderlist_t *fwdrs
+#endif
+	)
+{
+	isc_result_t result = ISC_R_SUCCESS;
+	cfg_parser_t *parser = NULL;
+
+	cfg_obj_t *fwdrs_cfg = NULL;
+	const cfg_obj_t *faddresses;
+	const cfg_listelt_t *listel;
+	const cfg_obj_t *fwdr_cfg;
+	isc_sockaddr_t addr;
+#if LIBDNS_VERSION_MAJOR < 140
+	isc_sockaddr_t *fwdr;
+#else /* LIBDNS_VERSION_MAJOR >= 140 */
+	dns_forwarder_t *fwdr;
+#endif
+
+	in_port_t port = 53;
+
+	REQUIRE(fwdrs_str != NULL);
+	REQUIRE(fwdrs != NULL);
+	REQUIRE(ISC_LIST_EMPTY(*fwdrs));
+
+	/* parse string like { ip; ip port dscp; } to list of cfg objects */
+	CHECK(cfg_parser_create(mctx, dns_lctx, &parser));
+	CHECK(cfg_parse_strbuf(parser, fwdrs_str,
+			       &cfg_type_forwarders, &fwdrs_cfg));
+	faddresses = cfg_tuple_get(fwdrs_cfg, "addresses");
+
+	/* transform list of cfg objects to linked list of forwarders */
+	for (listel = cfg_list_first(faddresses);
+	     listel != NULL;
+	     listel = cfg_list_next(listel)) {
+		fwdr_cfg = cfg_listelt_value(listel);
+		addr = *cfg_obj_assockaddr(fwdr_cfg);
+		if (isc_sockaddr_getport(&addr) == 0)
+			isc_sockaddr_setport(&addr, port);
+		CHECKED_MEM_GET_PTR(mctx, fwdr);
+#if LIBDNS_VERSION_MAJOR < 140
+		*fwdr = addr;
+#else /* LIBDNS_VERSION_MAJOR >= 140 */
+		fwdr->addr = addr;
+		fwdr->dscp = cfg_obj_getdscp(fwdr_cfg);
+#endif
+		ISC_LINK_INIT(fwdr, link);
+		ISC_LIST_APPEND(*fwdrs, fwdr, link);
+	}
+
+cleanup:
+	if (fwdrs_cfg != NULL)
+		cfg_obj_destroy(parser, &fwdrs_cfg);
+	if (parser != NULL)
+		cfg_parser_destroy(&parser);
+	return result;
+}
+
+static void
+fwdr_list_free(isc_mem_t *mctx,
+#if LIBDNS_VERSION_MAJOR < 140
+	isc_sockaddrlist_t *fwdrs
+#else /* LIBDNS_VERSION_MAJOR >= 140 */
+	dns_forwarderlist_t *fwdrs
+#endif
+	) {
+#if LIBDNS_VERSION_MAJOR < 140
+	isc_sockaddr_t *fwdr;
+#else /* LIBDNS_VERSION_MAJOR >= 140 */
+	dns_forwarder_t *fwdr;
+#endif
+	while (!ISC_LIST_EMPTY(*fwdrs)) {
+		fwdr = ISC_LIST_HEAD(*fwdrs);
+		ISC_LIST_UNLINK(*fwdrs, fwdr, link);
+		SAFE_MEM_PUT_PTR(mctx, fwdr);
+	}
+}
+
+/**
+ * Read forwarding policy (from idnsForwardingPolicy attribute) and
+ * list of forwarders (from idnsForwarders multi-value attribute)
+ * and update settings (forward_policy and forwarders) in given set of settings.
+ *
+ * This function does not change actual forwarding configuration.
+ * @see configure_zone_forwarders
+ *
+ * @post Forward_policy is always non-empty because default value is stored
+ *       if nothing is found in the LDAP entry.
+ *       Setting forwarders may be left unset if no forwarders are specified.
+ *
+ * @retval ISC_R_SUCCESS         Config was parsed and stored in settings
+ * @retval errors                Forwarding policy is invalid
+ *                               or specified forwarders are invalid.
+ */
+isc_result_t
+fwd_parse_ldap(ldap_entry_t *entry, settings_set_t *set) {
+	isc_result_t result;
+	isc_result_t first;
+	ldap_valuelist_t values;
+	ldap_value_t *value;
+	isc_buffer_t *tmp_buf = NULL; /* hack: only the base buffer is allocated */
+#if LIBDNS_VERSION_MAJOR < 140
+	isc_sockaddrlist_t fwdrs;
+#else /* LIBDNS_VERSION_MAJOR >= 140 */
+	dns_forwarderlist_t fwdrs;
+#endif
+	const char *setting_str = NULL;
+
+	/**
+	 * BIND forward policies are "first" (default) or "only".
+	 * We invented option "none" which disables forwarding for zone
+	 * regardless idnsForwarders attribute and global forwarders.
+	 */
+	dns_fwdpolicy_t fwdpolicy;
+
+	REQUIRE(entry != NULL);
+	REQUIRE(set != NULL);
+
+	ISC_LIST_INIT(fwdrs);
+
+	/* forward policy */
+	result = ldap_entry_getvalues(entry, "idnsForwardPolicy", &values);
+	/* validate LDAP entry before copying it into settings set */
+	if (result == ISC_R_SUCCESS
+	    && HEAD(values) != NULL
+	    && HEAD(values)->value != NULL) {
+		value = HEAD(values);
+		if (get_enum_value(forwarder_policy_txts, value->value,
+				   (int *)&fwdpolicy) != ISC_R_SUCCESS)
+		{
+			log_error("%s: invalid value '%s' in idnsForwardPolicy "
+				  "attribute; valid values: first, only, none",
+				  ldap_entry_logname(entry), value->value);
+			CLEANUP_WITH(ISC_R_UNEXPECTEDTOKEN);
+		}
+	}
+	result = setting_update_from_ldap_entry("forward_policy", set,
+						"idnsForwardPolicy",
+						entry);
+	first = result;
+	if (result != ISC_R_SUCCESS && result != ISC_R_IGNORE)
+		goto cleanup;
+	result = setting_find("forward_policy", set, ISC_FALSE, ISC_TRUE, NULL);
+	if (result == ISC_R_NOTFOUND) {
+		log_debug(2, "defaulting to forward policy 'first' for "
+			  "%s", ldap_entry_logname(entry));
+		CHECK(setting_set("forward_policy", set, "first"));
+	}
+
+	/* forwarders */
+	result = ldap_entry_getvalues(entry, "idnsForwarders", &values);
+	if (result == ISC_R_SUCCESS
+	    && HEAD(values) != NULL && HEAD(values)->value != NULL) {
+		/* Forwarders: concatenate IP addresses to one { string; } */
+		CHECK(fwd_print_bracketed_values_buf(entry->mctx, &values,
+						    &tmp_buf));
+		setting_str = isc_buffer_base(tmp_buf);
+		/* just sanity check, the result is unused */
+		CHECK(fwd_parse_str(setting_str, entry->mctx, &fwdrs));
+	}
+	if (!ISC_LIST_EMPTY(fwdrs)) {
+		result = setting_set("forwarders", set, setting_str);
+		if (result == ISC_R_SUCCESS)
+			log_debug(2, "setting 'forwarders' (idnsFowarders) was changed "
+				  "to '%s' in %s", setting_str, ldap_entry_logname(entry));
+		else if (result != ISC_R_IGNORE)
+				goto cleanup;
+	} else {
+		result = setting_unset("forwarders", set);
+	}
+
+	/* return "ignore" only if no change was done in any of the settings */
+	result = (result == ISC_R_IGNORE) ? first : result;
+
+cleanup:
+	if (result != ISC_R_SUCCESS && result != ISC_R_IGNORE
+	    && setting_str != NULL)
+		log_error_r("unable to parse forwarders '%s'", setting_str);
+	if (tmp_buf != NULL)
+		isc_buffer_free(&tmp_buf);
+	fwdr_list_free(entry->mctx, &fwdrs);
+	return result;
+}
+
+/**
+ * Read forwarding policy and list of forwarders from set of settings
+ * and update actual forwarding configuration.
+ *
+ * Enable forwarding if forwarders are specified and policy is not 'none'.
+ * Disable forwarding if forwarding policy is 'none' or list of forwarders
+ * is empty.
+ *
+ * Global forwarders will be used if all list of forwarders is not present
+ * at all. Global forwarders use configuration in following priority order:
+ * global LDAP config > named.conf
+ *
+ * @retval ISC_R_SUCCESS  Forwarding configuration was updated.
+ * @retval ISC_R_NOMEMORY
+ * @retval others	  Some RBT manipulation errors including ISC_R_FAILURE.
+ */
+isc_result_t
+fwd_configure_zone(settings_set_t *set, ldap_instance_t *inst,
+		  dns_name_t *name)
+{
+	isc_result_t result;
+	isc_mem_t *mctx = NULL;
+	dns_view_t *view = NULL;
+	isc_result_t lock_state = ISC_R_IGNORE;
+#if LIBDNS_VERSION_MAJOR < 140
+	isc_sockaddrlist_t fwdrs;
+#else /* LIBDNS_VERSION_MAJOR >= 140 */
+	dns_forwarderlist_t fwdrs;
+#endif
+	isc_boolean_t is_global_config;
+	dns_fixedname_t foundname;
+	const char *msg_use_global_fwds;
+	const char *msg_obj_type;
+	/**
+	 * BIND forward policies are "first" (default) or "only".
+	 * We invented option "none" which disables forwarding for zone
+	 * regardless idnsForwarders attribute and global forwarders.
+	 */
+	dns_fwdpolicy_t fwdpolicy;
+	const char *fwdpolicy_str = NULL;
+	const char *forwarders_str = NULL;
+
+	REQUIRE(inst != NULL && name != NULL);
+	ldap_instance_attachmem(inst, &mctx);
+	ldap_instance_attachview(inst, &view);
+
+	dns_fixedname_init(&foundname);
+	ISC_LIST_INIT(fwdrs);
+
+	if (dns_name_equal(name, dns_rootname)) {
+		is_global_config = ISC_TRUE;
+		msg_obj_type = "global configuration";
+		msg_use_global_fwds = "; global forwarders will be disabled";
+	} else {
+		is_global_config = ISC_FALSE;
+		msg_obj_type = "zone";
+		msg_use_global_fwds = "; global forwarders will be used "
+				      "(if they are configured)";
+	}
+
+	/* fetch forward policy */
+	CHECK(setting_get_str("forward_policy", set, &fwdpolicy_str));
+	result = get_enum_value(forwarder_policy_txts,
+				fwdpolicy_str, (int *)&fwdpolicy);
+	INSIST(result == ISC_R_SUCCESS);
+	log_debug(5, "%s %s: forward policy is '%s'", msg_obj_type,
+		  set->name, fwdpolicy_str);
+	if (fwdpolicy == dns_fwdpolicy_none)
+		log_debug(5, "%s %s: forwarding explicitly disabled "
+			  "(policy 'none', ignoring all forwarders)",
+			  msg_obj_type, set->name);
+
+	/* Fetch forwarders configured for particular zone.
+	 * Global config (root zone) is special case because it can be set in
+	 * named.conf, LDAP global object or root zone in LDAP so inheritance
+	 * is necessary.
+	 * For all other zones (non-root) zones *do not* use recursive getter
+	 * and let BIND to handle inheritance in fwdtable itself. */
+	if (fwdpolicy != dns_fwdpolicy_none
+	    && (setting_find("forwarders", set, ISC_FALSE, ISC_TRUE, NULL) == ISC_R_SUCCESS
+	    || is_global_config == ISC_TRUE)) {
+		CHECK(setting_get_str("forwarders", set, &forwarders_str));
+		CHECK(fwd_parse_str(forwarders_str, mctx, &fwdrs));
+	} else {
+		ISC_LIST_INIT(fwdrs);
+		log_debug(5, "list of forwarders in %s is empty%s",
+			  set->name, msg_use_global_fwds);
+	}
+
+	/* update forwarding table */
+	run_exclusive_enter(inst, &lock_state);
+	CHECK(fwd_delete_table(view, name, msg_obj_type, set->name));
+	if (fwdpolicy != dns_fwdpolicy_none && !ISC_LIST_EMPTY(fwdrs)) {
+#if LIBDNS_VERSION_MAJOR < 140
+		CHECK(dns_fwdtable_add(view->fwdtable, name, &fwdrs,
+				       fwdpolicy));
+#else /* LIBDNS_VERSION_MAJOR >= 140 */
+		CHECK(dns_fwdtable_addfwd(view->fwdtable, name, &fwdrs,
+					  fwdpolicy));
+#endif
+	}
+	dns_view_flushcache(view);
+	run_exclusive_exit(inst, lock_state);
+	lock_state = ISC_R_IGNORE; /* prevent double-unlock */
+	log_debug(5, "%s %s: forwarder table was updated: %s",
+		  msg_obj_type, set->name,
+		  dns_result_totext(result));
+
+	/* Handle collisions with automatic empty zones. */
+	if (fwdpolicy != dns_fwdpolicy_none && !ISC_LIST_EMPTY(fwdrs))
+		CHECK(empty_zone_handle_conflicts(name,
+						  view->zonetable,
+						  (fwdpolicy == dns_fwdpolicy_first)));
+
+cleanup:
+	run_exclusive_exit(inst, lock_state);
+	if (result != ISC_R_SUCCESS)
+		log_error_r("%s %s: forwarding table update failed",
+			    msg_obj_type, set->name);
+	fwdr_list_free(mctx, &fwdrs);
+	dns_view_detach(&view);
+	isc_mem_detach(&mctx);
+	return result;
+}
+
+isc_result_t
+fwd_delete_table(dns_view_t *view, dns_name_t *name,
+		 const char *msg_obj_type, const char *logname) {
+	isc_result_t result;
+
+	result = dns_fwdtable_delete(view->fwdtable, name);
+	if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
+		log_error_r("%s %s: failed to delete forwarders",
+			    msg_obj_type, logname);
+		return result;
+	} else {
+		return ISC_R_SUCCESS; /* ISC_R_NOTFOUND = nothing to delete */
+	}
+}
diff --git a/src/fwd.h b/src/fwd.h
new file mode 100644
index 0000000000000000000000000000000000000000..79bec61539cacd7d4537ee8e70122570d01741b7
--- /dev/null
+++ b/src/fwd.h
@@ -0,0 +1,37 @@
+/*
+ * DNS forwarding utilities.
+ */
+
+#ifndef _LD_FWD_H_
+#define _LD_FWD_H_
+
+#include "config.h"
+#include "ldap_entry.h"
+#include "util.h"
+
+extern const enum_txt_assoc_t forwarder_policy_txts[];
+
+isc_result_t
+fwd_print_list_buff(isc_mem_t *mctx, dns_forwarders_t *fwdrs,
+		    isc_buffer_t **out_buf)
+		    ATTR_NONNULLS ATTR_CHECKRESULT;
+
+isc_result_t
+fwd_print_bracketed_values_buf(isc_mem_t *mctx, ldap_valuelist_t *values,
+			       isc_buffer_t **string)
+			       ATTR_NONNULLS ATTR_CHECKRESULT;
+
+isc_result_t
+fwd_parse_ldap(ldap_entry_t *entry, settings_set_t *set)
+	       ATTR_NONNULLS ATTR_CHECKRESULT;
+
+isc_result_t
+fwd_configure_zone(settings_set_t *set, ldap_instance_t *inst, dns_name_t *name)
+		   ATTR_NONNULLS ATTR_CHECKRESULT;
+
+isc_result_t
+fwd_delete_table(dns_view_t *view, dns_name_t *name,
+		 const char *msg_obj_type, const char *logname)
+		 ATTR_NONNULLS ATTR_CHECKRESULT;
+
+#endif /* _LD_FWD_H_ */
diff --git a/src/ldap_helper.c b/src/ldap_helper.c
index 451ecdba20d6aa4c57ea438fe466a70426e9abfa..b13f17368e712102ceb0211172700c94c3213cbe 100644
--- a/src/ldap_helper.c
+++ b/src/ldap_helper.c
@@ -41,6 +41,8 @@
 #include <isc/serial.h>
 #include <isc/string.h>
 
+#include <isccfg/cfg.h>
+
 #include <alloca.h>
 #define LDAP_DEPRECATED 1
 #include <ldap.h>
@@ -57,6 +59,7 @@
 #include "acl.h"
 #include "empty_zones.h"
 #include "fs.h"
+#include "fwd.h"
 #include "krb5_helper.h"
 #include "ldap_convert.h"
 #include "ldap_driver.h"
@@ -78,13 +81,6 @@
 #include "rbt_helper.h"
 #include "fwd_register.h"
 
-const enum_txt_assoc_t forwarder_policy_txts[] = {
-	{ dns_fwdpolicy_none,	"none"	},
-	{ dns_fwdpolicy_first,	"first"	},
-	{ dns_fwdpolicy_only,	"only"	},
-	{ -1,			NULL	} /* end marker */
-};
-
 #define LDAP_OPT_CHECK(r, ...)						\
 	do {								\
 		if ((r) != LDAP_OPT_SUCCESS) {				\
@@ -131,13 +127,6 @@ struct ldap_auth_pair {
 	char *name;	/* String representation used in configuration file */
 };
 
-/* BIND 9.10 changed forwarder representation in struct dns_forwarders */
-#if LIBDNS_VERSION_MAJOR < 140
-	#define inst_fwdlist(inst) ((inst)->orig_global_forwarders.addrs)
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-	#define inst_fwdlist(inst) ((inst)->orig_global_forwarders.fwdrs)
-#endif
-
 /* These are typedefed in ldap_helper.h */
 struct ldap_instance {
 	isc_mem_t		*mctx;
@@ -166,7 +155,6 @@ struct ldap_instance {
 	/* Settings. */
 	settings_set_t		*local_settings;
 	settings_set_t		*global_settings;
-	dns_forwarders_t	orig_global_forwarders; /* from named.conf */
 
 	sync_ctx_t		*sctx;
 	mldapdb_t		*mldapdb;
@@ -250,13 +238,20 @@ static const setting_t settings_local_default[] = {
 	{ "verbose_checks",		no_default_boolean	},
 	{ "directory",			no_default_string	},
 	{ "nsec3param",			default_string("0 0 0 00")	}, /* NSEC only */
+	/* Defaults for forwarding here must be overridden by values from
+	 * from named.conf (i.e. copied to inst->local_settings)
+	 * during start up to allow settings_set_isfilled() to pass.*/
+	{ "forward_policy",		no_default_string	},
+	{ "forwarders",			no_default_string	},
 	end_of_settings
 };
 
 /** Global settings from idnsConfig object. */
 static setting_t settings_global_default[] = {
-	{ "dyn_update",		no_default_boolean	},
-	{ "sync_ptr",		no_default_boolean	},
+	{ "dyn_update",		no_default_boolean					},
+	{ "sync_ptr",		no_default_boolean					},
+	{ "forward_policy",	default_string("first")					},
+	{ "forwarders",		default_string("{ /* uninitialized global config */ }")	},
 	end_of_settings
 };
 
@@ -508,7 +503,9 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
 	isc_result_t result;
 	ldap_instance_t *ldap_inst;
 	dns_view_t *view = NULL;
-	dns_forwarders_t *orig_global_forwarders = NULL;
+	dns_forwarders_t *named_conf_forwarders = NULL;
+	isc_buffer_t *forwarders_list = NULL;
+	const char *forward_policy = NULL;
 	isc_uint32_t connections;
 	char settings_name[PRINT_BUFF_SIZE];
 	ldap_globalfwd_handleez_t *gfwdevent = NULL;
@@ -524,7 +521,6 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
 	view = dns_dyndb_get_view(dyndb_args);
 	dns_view_attach(view, &ldap_inst->view);
 	ldap_inst->zmgr = dns_dyndb_get_zonemgr(dyndb_args);
-	ISC_LIST_INIT(inst_fwdlist(ldap_inst));
 	ldap_inst->task = task;
 	ldap_inst->watcher = 0;
 	CHECK(sync_ctx_init(ldap_inst->mctx, ldap_inst, &ldap_inst->sctx));
@@ -544,42 +540,22 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
 	      ldap_inst->local_settings, &ldap_inst->global_settings));
 
 	CHECK(settings_set_fill(ldap_inst->local_settings, argv));
-	CHECK(validate_local_instance_settings(ldap_inst, ldap_inst->local_settings));
-	if (settings_set_isfilled(ldap_inst->global_settings) != ISC_TRUE)
-		CLEANUP_WITH(ISC_R_FAILURE);
-
-	CHECK(setting_get_uint("connections", ldap_inst->local_settings, &connections));
-
-	CHECK(zr_create(mctx, ldap_inst, ldap_inst->global_settings,
-			&ldap_inst->zone_register));
-	CHECK(fwdr_create(ldap_inst->mctx, &ldap_inst->fwd_register));
-	CHECK(mldap_new(mctx, &ldap_inst->mldapdb));
-
-	CHECK(isc_mutex_init(&ldap_inst->kinit_lock));
 
 	/* copy global forwarders setting for configuration roll back in
 	 * configure_zone_forwarders() */
 	result = dns_fwdtable_find(ldap_inst->view->fwdtable, dns_rootname,
-				   &orig_global_forwarders);
+				   &named_conf_forwarders);
 	if (result == ISC_R_SUCCESS) {
-#if LIBDNS_VERSION_MAJOR < 140
-		isc_sockaddr_t *fwdr;
-		isc_sockaddr_t *new_fwdr;
-		for (fwdr = ISC_LIST_HEAD(orig_global_forwarders->addrs);
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-		dns_forwarder_t *fwdr;
-		dns_forwarder_t *new_fwdr;
-		for (fwdr = ISC_LIST_HEAD(orig_global_forwarders->fwdrs);
-#endif
-		     fwdr != NULL;
-		     fwdr = ISC_LIST_NEXT(fwdr, link)) {
-			CHECKED_MEM_GET_PTR(mctx, new_fwdr);
-			*new_fwdr = *fwdr;
-			ISC_LINK_INIT(new_fwdr, link);
-			ISC_LIST_APPEND(inst_fwdlist(ldap_inst), new_fwdr, link);
-		}
-		ldap_inst->orig_global_forwarders.fwdpolicy =
-				orig_global_forwarders->fwdpolicy;
+		/* Copy forwarding config from named.conf into local_settings */
+		CHECK(fwd_print_list_buff(mctx, named_conf_forwarders,
+						  &forwarders_list));
+		CHECK(setting_set("forwarders", ldap_inst->local_settings,
+				  isc_buffer_base(forwarders_list)));
+		CHECK(get_enum_description(forwarder_policy_txts,
+					   named_conf_forwarders->fwdpolicy,
+					   &forward_policy));
+		CHECK(setting_set("forward_policy", ldap_inst->local_settings,
+				  forward_policy));
 
 		/* Make sure we disable conflicting automatic empty zones.
 		 * This will be done in event to prevent the plugin from
@@ -597,18 +573,35 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
 		if (gfwdevent == NULL)
 			CLEANUP_WITH(ISC_R_NOMEMORY);
 		/* policy == first does not override automatic empty zones */
-		gfwdevent->warn_only = (orig_global_forwarders->fwdpolicy
+		gfwdevent->warn_only = (named_conf_forwarders->fwdpolicy
 					== dns_fwdpolicy_first);
 
 		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;
+		CHECK(setting_set("forwarders", ldap_inst->local_settings,
+				  "{ /* empty list of forwarders */ }"));
+		CHECK(setting_set("forward_policy", ldap_inst->local_settings,
+				  "first"));
 	} else {
 		goto cleanup;
 	}
 
+	CHECK(validate_local_instance_settings(ldap_inst,
+					       ldap_inst->local_settings));
+	if (settings_set_isfilled(ldap_inst->global_settings) != ISC_TRUE)
+		CLEANUP_WITH(ISC_R_FAILURE);
+
+	CHECK(setting_get_uint("connections", ldap_inst->local_settings, &connections));
+
+	CHECK(zr_create(mctx, ldap_inst, ldap_inst->global_settings,
+			&ldap_inst->zone_register));
+	CHECK(fwdr_create(ldap_inst->mctx, &ldap_inst->fwd_register));
+	CHECK(mldap_new(mctx, &ldap_inst->mldapdb));
+
+	CHECK(isc_mutex_init(&ldap_inst->kinit_lock));
+
 	CHECK(ldap_pool_create(mctx, connections, &ldap_inst->pool));
 	CHECK(ldap_pool_connect(ldap_inst->pool, ldap_inst));
 
@@ -621,6 +614,8 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
 	}
 
 cleanup:
+	if (forwarders_list != NULL)
+		isc_buffer_free(&forwarders_list);
 	if (result != ISC_R_SUCCESS)
 		destroy_ldap_instance(&ldap_inst);
 	else
@@ -635,11 +630,6 @@ destroy_ldap_instance(ldap_instance_t **ldap_instp)
 {
 	ldap_instance_t *ldap_inst;
 	const char *db_name;
-#if LIBDNS_VERSION_MAJOR < 140
-	isc_sockaddr_t *fwdr;
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-	dns_forwarder_t *fwdr;
-#endif
 
 	REQUIRE(ldap_instp != NULL);
 
@@ -675,12 +665,6 @@ destroy_ldap_instance(ldap_instance_t **ldap_instp)
 
 	DESTROYLOCK(&ldap_inst->kinit_lock);
 
-	while (!ISC_LIST_EMPTY(inst_fwdlist(ldap_inst))) {
-		fwdr = ISC_LIST_HEAD(inst_fwdlist(ldap_inst));
-		ISC_LIST_UNLINK(inst_fwdlist(ldap_inst), fwdr, link);
-		SAFE_MEM_PUT_PTR(ldap_inst->mctx, fwdr);
-	}
-
 	settings_set_free(&ldap_inst->global_settings);
 	settings_set_free(&ldap_inst->local_settings);
 
@@ -1260,21 +1244,6 @@ configure_zone_ssutable(dns_zone_t *zone, const char *update_str)
 	return result;
 }
 
-static isc_result_t ATTR_NONNULLS ATTR_CHECKRESULT
-delete_forwarding_table(ldap_instance_t *inst, dns_name_t *name,
-			const char *msg_obj_type, const char *logname) {
-	isc_result_t result;
-
-	result = dns_fwdtable_delete(inst->view->fwdtable, name);
-	if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) {
-		log_error_r("%s %s: failed to delete forwarders",
-			    msg_obj_type, logname);
-		return result;
-	} else {
-		return ISC_R_SUCCESS; /* ISC_R_NOTFOUND = nothing to delete */
-	}
-}
-
 /* Delete zone by dns zone name */
 isc_result_t
 ldap_delete_zone2(ldap_instance_t *inst, dns_name_t *name, isc_boolean_t lock,
@@ -1295,8 +1264,8 @@ ldap_delete_zone2(ldap_instance_t *inst, dns_name_t *name, isc_boolean_t lock,
 		run_exclusive_enter(inst, &lock_state);
 
 	if (!preserve_forwarding) {
-		CHECK(delete_forwarding_table(inst, name, "zone",
-					      zone_name_char));
+		CHECK(fwd_delete_table(inst->view, name, "zone",
+				       zone_name_char));
 		isforward = fwdr_zone_ispresent(inst->fwd_register, name);
 		if (isforward == ISC_R_SUCCESS)
 			CHECK(fwdr_del_zone(inst->fwd_register, name));
@@ -1368,7 +1337,7 @@ unpublish_zone(ldap_instance_t *inst, dns_name_t *name, const char *logname) {
 	}
 	CHECK(dns_view_findzone(inst->view, name, &zone_in_view));
 	INSIST(zone_in_view == raw || zone_in_view == secure);
-	CHECK(delete_forwarding_table(inst, name, "zone", logname));
+	CHECK(fwd_delete_table(inst->view, name, "zone", logname));
 	CHECK(dns_zt_unmount(inst->view->zonetable, zone_in_view));
 
 cleanup:
@@ -1387,273 +1356,6 @@ cleanup:
 	return result;
 }
 
-/**
- * Read forwarding policy (from idnsForwardingPolicy attribute) and
- * list of forwarders (from idnsForwarders multi-value attribute)
- * and update forwarding settings for given zone.
- *
- * Enable forwarding if forwarders are specified and policy is not 'none'.
- * Disable forwarding if forwarding policy is 'none' or list of forwarders
- * is empty.
- *
- * Invalid forwarders are skipped, forwarding will be enabled if at least
- * one valid forwarder is defined. Global forwarders will be used if all
- * defined forwarders are invalid or list of forwarders is not present at all.
- *
- * @retval ISC_R_SUCCESS  Forwarding was enabled.
- * @retval ISC_R_DISABLED Forwarding was disabled.
- * @retval ISC_R_UNEXPECTEDTOKEN Forwarding policy is invalid
- *                               or all specified forwarders are invalid.
- * @retval ISC_R_NOMEMORY
- * @retval others	  Some RBT manipulation errors including ISC_R_FAILURE.
- */
-static isc_result_t ATTR_NONNULLS ATTR_CHECKRESULT
-configure_zone_forwarders(ldap_entry_t *entry, ldap_instance_t *inst,
-			  dns_name_t *name)
-{
-	isc_result_t result;
-	isc_result_t orig_result;
-	isc_result_t lock_state = ISC_R_IGNORE;
-	ldap_valuelist_t values;
-	ldap_value_t *value;
-#if LIBDNS_VERSION_MAJOR < 140
-	isc_sockaddrlist_t fwdrs;
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-	dns_forwarderlist_t fwdrs;
-#endif
-	isc_boolean_t is_global_config;
-	isc_boolean_t fwdtbl_deletion_requested = ISC_TRUE;
-	isc_boolean_t fwdtbl_update_requested = ISC_FALSE;
-	dns_forwarders_t *old_setting = NULL;
-	dns_fixedname_t foundname;
-	const char *msg_use_global_fwds;
-	const char *msg_obj_type;
-	const char *msg_forwarders_not_def;
-	const char *msg_forward_policy = NULL;
-	/**
-	 * BIND forward policies are "first" (default) or "only".
-	 * We invented option "none" which disables forwarding for zone
-	 * regardless idnsForwarders attribute and global forwarders.
-	 */
-	dns_fwdpolicy_t fwdpolicy = dns_fwdpolicy_first;
-
-	REQUIRE(entry != NULL && inst != NULL && name != NULL);
-	ISC_LIST_INIT(fwdrs);
-	dns_fixedname_init(&foundname);
-	if (dns_name_equal(name, dns_rootname)) {
-		is_global_config = ISC_TRUE;
-		msg_obj_type = "global configuration";
-		msg_use_global_fwds = "; global forwarders will be disabled";
-		msg_forwarders_not_def = "; global forwarders from "
-					 "configuration file will be used";
-	} else {
-		is_global_config = ISC_FALSE;
-		msg_obj_type = "zone";
-		msg_use_global_fwds = "; global forwarders will be used "
-				      "(if they are configured)";
-		msg_forwarders_not_def = msg_use_global_fwds;
-	}
-
-	/*
-	 * Fetch forward policy.
-	 */
-	result = ldap_entry_getvalues(entry, "idnsForwardPolicy", &values);
-	if (result == ISC_R_SUCCESS) {
-		value = HEAD(values);
-		if (value != NULL && value->value != NULL) {
-			if (strcasecmp(value->value, "only") == 0)
-				fwdpolicy = dns_fwdpolicy_only;
-			else if (strcasecmp(value->value, "first") == 0)
-				fwdpolicy = dns_fwdpolicy_first;
-			else if (strcasecmp(value->value, "none") == 0)
-				fwdpolicy = dns_fwdpolicy_none;
-			else {
-				log_error("%s %s: invalid value '%s' in "
-					  "idnsForwardPolicy attribute; "
-					  "valid values: first, only, none"
-					  "%s",
-					  msg_obj_type,
-					  ldap_entry_logname(entry),
-					  value->value, msg_use_global_fwds);
-				CLEANUP_WITH(ISC_R_UNEXPECTEDTOKEN);
-			}
-		}
-	}
-
-	if (fwdpolicy == dns_fwdpolicy_none) {
-		ISC_LIST_INIT(values); /* ignore idnsForwarders in LDAP */
-	} else {
-		result = ldap_entry_getvalues(entry, "idnsForwarders", &values);
-		if (result == ISC_R_NOTFOUND || EMPTY(values)) {
-			log_debug(5, "%s %s: idnsForwarders attribute is "
-				  "not present%s", msg_obj_type,
-				  ldap_entry_logname(entry),
-				  msg_forwarders_not_def);
-			if (is_global_config) {
-				ISC_LIST_INIT(values);
-				fwdrs = inst_fwdlist(inst);
-				fwdpolicy = inst->orig_global_forwarders.fwdpolicy;
-			} else {
-				CLEANUP_WITH(ISC_R_DISABLED);
-			}
-		}
-	}
-
-	CHECK(get_enum_description(forwarder_policy_txts, fwdpolicy,
-				   &msg_forward_policy));
-	log_debug(5, "%s %s: forward policy is '%s'", msg_obj_type,
-		  ldap_entry_logname(entry), msg_forward_policy);
-
-	for (value = HEAD(values); value != NULL; value = NEXT(value, link)) {
-#if LIBDNS_VERSION_MAJOR < 140
-		isc_sockaddr_t *fwdr = NULL;
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-		dns_forwarder_t *fwdr = NULL;
-#endif
-		char forwarder_txt[ISC_SOCKADDR_FORMATSIZE];
-
-		if (acl_parse_forwarder(value->value, inst->mctx, &fwdr)
-				!= ISC_R_SUCCESS) {
-			log_error("%s %s: could not parse forwarder '%s'",
-				  msg_obj_type, ldap_entry_logname(entry),
-				  value->value);
-			continue;
-		}
-
-		ISC_LINK_INIT(fwdr, link);
-		ISC_LIST_APPEND(fwdrs, fwdr, link);
-		isc_sockaddr_format(
-#if LIBDNS_VERSION_MAJOR < 140
-				fwdr,
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-				&fwdr->addr,
-#endif
-				forwarder_txt, ISC_SOCKADDR_FORMATSIZE);
-		log_debug(5, "%s %s: adding forwarder '%s'", msg_obj_type,
-			  ldap_entry_logname(entry), forwarder_txt);
-	}
-
-	if (fwdpolicy != dns_fwdpolicy_none && ISC_LIST_EMPTY(fwdrs)) {
-		log_debug(5, "%s %s: all idnsForwarders are invalid%s",
-			  msg_obj_type, ldap_entry_logname(entry),
-			  msg_use_global_fwds);
-		CLEANUP_WITH(ISC_R_UNEXPECTEDTOKEN);
-	} else if (fwdpolicy == dns_fwdpolicy_none) {
-		log_debug(5, "%s %s: forwarding explicitly disabled "
-			  "(policy 'none', ignoring global forwarders)",
-			  msg_obj_type, ldap_entry_logname(entry));
-	}
-
-	/* Check for old and new forwarding settings equality. */
-	result = dns_fwdtable_find2(inst->view->fwdtable, name,
-				    dns_fixedname_name(&foundname),
-				    &old_setting);
-	if (result == ISC_R_SUCCESS &&
-	   (dns_name_equal(name, dns_fixedname_name(&foundname)) == ISC_TRUE)) {
-#if LIBDNS_VERSION_MAJOR < 140
-		isc_sockaddr_t *s1, *s2;
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-		dns_forwarder_t *s1, *s2;
-#endif
-
-		if (fwdpolicy != old_setting->fwdpolicy)
-			fwdtbl_update_requested = ISC_TRUE;
-
-		/* Check address lists item by item. */
-#if LIBDNS_VERSION_MAJOR < 140
-		for (s1 = ISC_LIST_HEAD(fwdrs), s2 = ISC_LIST_HEAD(old_setting->addrs);
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-		for (s1 = ISC_LIST_HEAD(fwdrs), s2 = ISC_LIST_HEAD(old_setting->fwdrs);
-#endif
-		     s1 != NULL && s2 != NULL && !fwdtbl_update_requested;
-		     s1 = ISC_LIST_NEXT(s1, link), s2 = ISC_LIST_NEXT(s2, link))
-#if LIBDNS_VERSION_MAJOR < 140
-		if (!isc_sockaddr_equal(s1, s2)) {
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-		if (!isc_sockaddr_equal(&s1->addr, &s2->addr) ||
-		    s1->dscp != s2->dscp) {
-#endif
-			fwdtbl_update_requested = ISC_TRUE;
-		}
-
-		if (!fwdtbl_update_requested && ((s1 != NULL) || (s2 != NULL)))
-			fwdtbl_update_requested = ISC_TRUE;
-
-	} 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 (fwdtbl_update_requested) {
-		if (fwdpolicy != dns_fwdpolicy_none) {
-			/* Handle collisions with automatic empty zones. */
-			CHECK(empty_zone_handle_conflicts(name,
-							  inst->view->zonetable,
-							  (fwdpolicy == dns_fwdpolicy_first)));
-		}
-
-		/* Something was changed - set forward table up. */
-		CHECK(delete_forwarding_table(inst, name, msg_obj_type,
-					      ldap_entry_logname(entry)));
-#if LIBDNS_VERSION_MAJOR < 140
-		result = dns_fwdtable_add(inst->view->fwdtable, name, &fwdrs,
-					  fwdpolicy);
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-		result = dns_fwdtable_addfwd(inst->view->fwdtable, name, &fwdrs,
-					     fwdpolicy);
-#endif
-		if (result != ISC_R_SUCCESS)
-			log_error_r("%s %s: forwarding table update failed",
-				    msg_obj_type, ldap_entry_logname(entry));
-	} else {
-		result = ISC_R_SUCCESS;
-		log_debug(5, "%s %s: forwarding table unmodified",
-			  msg_obj_type, ldap_entry_logname(entry));
-	}
-	if (result == ISC_R_SUCCESS) {
-		fwdtbl_deletion_requested = ISC_FALSE;
-		if (fwdpolicy == dns_fwdpolicy_none)
-			result = ISC_R_DISABLED;
-	}
-
-cleanup:
-	if (ISC_LIST_HEAD(fwdrs) !=
-	    ISC_LIST_HEAD(inst_fwdlist(inst))) {
-		while(!ISC_LIST_EMPTY(fwdrs)) {
-#if LIBDNS_VERSION_MAJOR < 140
-			isc_sockaddr_t *fwdr = NULL;
-#else /* LIBDNS_VERSION_MAJOR >= 140 */
-			dns_forwarder_t *fwdr = NULL;
-#endif
-			fwdr = ISC_LIST_HEAD(fwdrs);
-			ISC_LIST_UNLINK(fwdrs, fwdr, link);
-			SAFE_MEM_PUT_PTR(inst->mctx, fwdr);
-		}
-	}
-	if (fwdtbl_deletion_requested) {
-		orig_result = result;
-		result = delete_forwarding_table(inst, name, msg_obj_type,
-						 ldap_entry_logname(entry));
-		if (result == ISC_R_SUCCESS)
-			result = orig_result;
-	}
-	if (fwdtbl_deletion_requested || fwdtbl_update_requested) {
-		log_debug(5, "%s %s: forwarder table was updated: %s",
-			  msg_obj_type, ldap_entry_logname(entry),
-			  dns_result_totext(result));
-		orig_result = result;
-		run_exclusive_enter(inst, &lock_state);
-		result = dns_view_flushcache(inst->view);
-		run_exclusive_exit(inst, lock_state);
-		if (result == ISC_R_SUCCESS)
-			result = orig_result;
-	}
-	return result;
-}
-
 /* Parse the config object entry */
 static isc_result_t ATTR_NONNULLS ATTR_CHECKRESULT
 ldap_parse_configentry(ldap_entry_t *entry, ldap_instance_t *inst)
@@ -1665,11 +1367,14 @@ ldap_parse_configentry(ldap_entry_t *entry, ldap_instance_t *inst)
 
 	log_debug(3, "Parsing configuration object");
 
-	/* idnsForwardPolicy change is handled by configure_zone_forwarders() */
-	result = configure_zone_forwarders(entry, inst, dns_rootname);
-	if (result != ISC_R_SUCCESS && result != ISC_R_DISABLED) {
-		log_error_r("global forwarder could not be set up");
-	}
+	result = fwd_parse_ldap(entry, inst->global_settings);
+	if (result == ISC_R_SUCCESS)
+		result = fwd_configure_zone(inst->global_settings, inst,
+					    dns_rootname);
+		if (result != ISC_R_SUCCESS)
+			log_error_r("global forwarder could not be set up");
+	else if (result != ISC_R_IGNORE)
+		goto cleanup;
 
 	result = setting_update_from_ldap_entry("dyn_update",
 						inst->global_settings,
@@ -1699,25 +1404,37 @@ ldap_parse_fwd_zoneentry(ldap_entry_t *entry, ldap_instance_t *inst)
 	char name_txt[DNS_NAME_FORMATSIZE];
 	isc_result_t result;
 
+	static const setting_t fwdz_defaults[] = {
+		{ "forward_policy",		no_default_string	},
+		{ "forwarders",			no_default_string	},
+		end_of_settings
+	};
+	settings_set_t *fwdz_settings = NULL;
+
 	REQUIRE(entry != NULL);
 	REQUIRE(inst != NULL);
 
+	/* Zone is active */
 	CHECK(ldap_entry_getvalues(entry, "idnsZoneActive", &values));
 	if (HEAD(values) != NULL &&
 	    strcasecmp(HEAD(values)->value, "TRUE") != 0) {
 		/* Zone is not active */
 		result = ldap_delete_zone2(inst, &entry->fqdn,
 					   ISC_TRUE, ISC_FALSE);
 		goto cleanup;
 	}
 
-	/* Zone is active */
-	result = configure_zone_forwarders(entry, inst, &entry->fqdn);
-	if (result != ISC_R_DISABLED && result != ISC_R_SUCCESS) {
-		log_error_r("%s: could not configure forwarding",
+	CHECK(settings_set_create(inst->mctx, fwdz_defaults, sizeof(fwdz_defaults),
+				  "fake fwdz settings", inst->global_settings,
+				  &fwdz_settings));
+	result = fwd_parse_ldap(entry, fwdz_settings);
+	if (result == ISC_R_IGNORE) {
+		log_error_r("%s: invalid object: either "
+			    "forwarding policy or forwarders must be set",
 			    ldap_entry_logname(entry));
 		goto cleanup;
 	}
+	CHECK(fwd_configure_zone(fwdz_settings, inst, &entry->fqdn));
 
 	result = fwdr_add_zone(inst->fwd_register, &entry->fqdn);
 	if (result != ISC_R_EXISTS && result != ISC_R_SUCCESS) {
@@ -1731,6 +1448,7 @@ ldap_parse_fwd_zoneentry(ldap_entry_t *entry, ldap_instance_t *inst)
 	log_info("forward zone '%s': loaded", name_txt);
 
 cleanup:
+	settings_set_free(&fwdz_settings);
 	return result;
 }
 
@@ -2258,10 +1976,6 @@ ldap_parse_master_zoneentry(ldap_entry_t * const entry, dns_db_t * const olddb,
 
 	run_exclusive_enter(inst, &lock_state);
 
-	result = configure_zone_forwarders(entry, inst, &entry->fqdn);
-	if (result != ISC_R_SUCCESS && result != ISC_R_DISABLED)
-		goto cleanup;
-
 	result = ldap_entry_getvalues(entry, "idnsSecInlineSigning", &values);
 	if (result == ISC_R_NOTFOUND || HEAD(values) == NULL)
 		want_secure = ISC_FALSE;
@@ -2296,7 +2010,14 @@ ldap_parse_master_zoneentry(ldap_entry_t * const entry, dns_db_t * const olddb,
 	CHECK(zr_get_zone_settings(inst->zone_register, &entry->fqdn,
 				   &zone_settings));
 	CHECK(zone_master_reconfigure(entry, zone_settings, raw, secure, task));
-
+	result = fwd_parse_ldap(entry, zone_settings);
+	if (result == ISC_R_SUCCESS) {
+		result = fwd_configure_zone(zone_settings, inst, &entry->fqdn);
+		if (result != ISC_R_SUCCESS)
+			log_error_r("%s: could not configure forwarding",
+				    ldap_entry_logname(entry));
+	} else if (result != ISC_R_IGNORE)
+		goto cleanup;
 	/* synchronize zone origin with LDAP */
 	CHECK(zr_get_zone_dbs(inst->zone_register, &entry->fqdn, &ldapdb, &rbtdb));
 	CHECK(dns_db_newversion(ldapdb, &version));
@@ -4624,6 +4345,18 @@ ldap_instance_gettask(ldap_instance_t *ldap_inst)
 	return ldap_inst->task;
 }
 
+void
+ldap_instance_attachview(ldap_instance_t *ldap_inst, dns_view_t **view)
+{
+	dns_view_attach(ldap_inst->view, view);
+}
+
+void
+ldap_instance_attachmem(ldap_instance_t *ldap_inst, isc_mem_t **mctx)
+{
+	isc_mem_attach(ldap_inst->mctx, mctx);
+}
+
 isc_boolean_t
 ldap_instance_isexiting(ldap_instance_t *ldap_inst)
 {
diff --git a/src/ldap_helper.h b/src/ldap_helper.h
index b4b1ee59edb3414b305888271dc425980a1fd3df..1d691a29a06db645acb3979a1425df9ecb8577d7 100644
--- a/src/ldap_helper.h
+++ b/src/ldap_helper.h
@@ -96,4 +96,11 @@ ldap_instance_untaint_start(ldap_instance_t *ldap_inst);
 isc_result_t
 ldap_instance_untaint_finish(ldap_instance_t *ldap_inst, unsigned int count);
 
+void
+ldap_instance_attachview(ldap_instance_t *ldap_inst, dns_view_t **view) ATTR_NONNULLS;
+
+void
+ldap_instance_attachmem(ldap_instance_t *ldap_inst, isc_mem_t **mctx)
+			ATTR_NONNULLS;
+
 #endif /* !_LD_LDAP_HELPER_H_ */
-- 
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