[bind-dyndb-ldap] Update to the latest git
Adam Tkac
atkac at fedoraproject.org
Thu Aug 2 13:06:23 UTC 2012
commit b60b696e829fff1366139d54b428cf2ee9f0dd41
Author: Adam Tkac <vonsch at gmail.com>
Date: Thu Aug 2 15:05:34 2012 +0200
Update to the latest git
- fix for CVE-2012-3429 has been merged
Signed-off-by: Adam Tkac <vonsch at gmail.com>
...den-DNS-to-LDAP-name-conversion.-Fixes-CV.patch | 91 -
bind-dyndb-ldap.spec | 8 +-
bind-dyndb-ldap110-master.patch | 2793 +++++++++++++++++++-
3 files changed, 2779 insertions(+), 113 deletions(-)
---
diff --git a/bind-dyndb-ldap.spec b/bind-dyndb-ldap.spec
index 5962306..a477463 100644
--- a/bind-dyndb-ldap.spec
+++ b/bind-dyndb-ldap.spec
@@ -6,7 +6,7 @@
Name: bind-dyndb-ldap
Version: 1.1.0
-Release: 0.14.%{PREVER}%{?dist}
+Release: 0.15.%{PREVER}%{?dist}
Summary: LDAP back-end plug-in for BIND
Group: System Environment/Libraries
@@ -22,7 +22,6 @@ BuildRequires: openldap-devel
Requires: bind >= 32:9.6.1-0.3.b1
Patch0: bind-dyndb-ldap110-master.patch
-Patch1: 0001-Fix-and-harden-DNS-to-LDAP-name-conversion.-Fixes-CV.patch
%description
This package provides an LDAP back-end plug-in for BIND. It features
@@ -34,7 +33,6 @@ off of your LDAP server.
%setup -q -n %{name}-%{VERSION}
%patch0 -p1 -b .master
-%patch1 -p1 -b .CVE-2012-3429
%build
export CFLAGS="`isc-config.sh --cflags dns` $RPM_OPT_FLAGS"
@@ -62,6 +60,10 @@ rm -rf %{buildroot}
%changelog
+* Thu Aug 03 2012 Adam Tkac <atkac redhat com> 1.1.0-0.15.rc1
+- update to the latest git
+ - fix for CVE-2012-3429 has been merged
+
* Thu Aug 02 2012 Adam Tkac <atkac redhat com> 1.1.0-0.14.rc1
- fix CVE-2012-3429
diff --git a/bind-dyndb-ldap110-master.patch b/bind-dyndb-ldap110-master.patch
index 2308974..38a38e1 100644
--- a/bind-dyndb-ldap110-master.patch
+++ b/bind-dyndb-ldap110-master.patch
@@ -1,12 +1,12 @@
From f0d49c0eb816c958e4fa6bf4a073eb6ac592efad Mon Sep 17 00:00:00 2001
From: Adam Tkac <atkac at redhat.com>
Date: Thu, 26 Apr 2012 13:48:21 +0200
-Subject: [PATCH 1/5] Link ldap.so with relro, now and noexecstack linker
+Subject: [PATCH 01/27] Link ldap.so with relro, now and noexecstack linker
parameters.
Signed-off-by: Adam Tkac <atkac at redhat.com>
---
- src/Makefile.am | 2 +-
+ src/Makefile.am | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/Makefile.am b/src/Makefile.am
@@ -20,19 +20,19 @@ index 84c774b..b7b4240 100644
-ldap_la_LDFLAGS = -module -avoid-version
+ldap_la_LDFLAGS = -module -avoid-version -Wl,-z,relro,-z,now,-z,noexecstack
--
-1.7.10.2
+1.7.11.2
From 481e350f5848cf01da6743f259a6f12419fc4177 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspacek at redhat.com>
Date: Tue, 24 Apr 2012 15:09:32 +0200
-Subject: [PATCH 2/5] Add simple semaphore deadlock detection logic.
+Subject: [PATCH 02/27] Add simple semaphore deadlock detection logic.
Signed-off-by: Petr Spacek <pspacek at redhat.com>
---
- src/ldap_helper.c | 78 ++++++++++++++++++++++++++++++++---------------------
- src/semaphore.c | 26 +++++++++++++++---
- src/semaphore.h | 6 ++++-
+ src/ldap_helper.c | 78 +++++++++++++++++++++++++++++++++----------------------
+ src/semaphore.c | 26 ++++++++++++++++---
+ src/semaphore.h | 6 ++++-
3 files changed, 74 insertions(+), 36 deletions(-)
diff --git a/src/ldap_helper.c b/src/ldap_helper.c
@@ -382,19 +382,19 @@ index 4ca4f65..1367747 100644
#endif /* !_LD_SEMAPHORE_H_ */
--
-1.7.10.2
+1.7.11.2
From 3d43fd66aa68ef275855391a94e47e9d2f30309d Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspacek at redhat.com>
Date: Mon, 23 Apr 2012 11:38:43 +0200
-Subject: [PATCH 3/5] Add proper DN escaping before LDAP library calls.
+Subject: [PATCH 03/27] Add proper DN escaping before LDAP library calls.
Signed-off-by: Petr Spacek <pspacek at redhat.com>
---
- src/ldap_convert.c | 105 ++++++++++++++++++++++++++++++++++++++++++++++++---
- src/zone_register.c | 7 ++++
- src/zone_register.h | 3 ++
+ src/ldap_convert.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++++---
+ src/zone_register.c | 7 ++++
+ src/zone_register.h | 3 ++
3 files changed, 110 insertions(+), 5 deletions(-)
diff --git a/src/ldap_convert.c b/src/ldap_convert.c
@@ -594,20 +594,20 @@ index e2408cb..fa8ef25 100644
+
#endif /* !_LD_ZONE_REGISTER_H_ */
--
-1.7.10.2
+1.7.11.2
From 0744209bc4461bf2f4d83b0a8e3f7051132ddef3 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspacek at redhat.com>
Date: Thu, 7 Jun 2012 14:42:40 +0200
-Subject: [PATCH 4/5] Fix crash during BIND reload with persistent search
+Subject: [PATCH 04/27] Fix crash during BIND reload with persistent search
enabled.
https://fedorahosted.org/bind-dyndb-ldap/ticket/78
Signed-off-by: Petr Spacek <pspacek at redhat.com>
Signed-off-by: Adam Tkac <atkac at redhat.com>
---
- src/ldap_helper.c | 2 +-
+ src/ldap_helper.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/ldap_helper.c b/src/ldap_helper.c
@@ -624,19 +624,20 @@ index 5965d30..dc4fdf5 100644
int ret, cnt;
isc_result_t result;
--
-1.7.10.2
+1.7.11.2
From 0dccccec9cede75bd254f723bc9a49592c24a44b Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspacek at redhat.com>
Date: Thu, 7 Jun 2012 15:27:27 +0200
-Subject: [PATCH 5/5] Fix crash during zone unload when NS is not resolvable.
+Subject: [PATCH 05/27] Fix crash during zone unload when NS is not
+ resolvable.
https://fedorahosted.org/bind-dyndb-ldap/ticket/77
Signed-off-by: Petr Spacek <pspacek at redhat.com>
Signed-off-by: Adam Tkac <atkac at redhat.com>
---
- src/ldap_helper.c | 11 +++++++++--
+ src/ldap_helper.c | 11 +++++++++--
1 file changed, 9 insertions(+), 2 deletions(-)
diff --git a/src/ldap_helper.c b/src/ldap_helper.c
@@ -683,5 +684,2759 @@ index dc4fdf5..09c1f7d 100644
goto cleanup;
--
-1.7.10.2
+1.7.11.2
+
+
+From f06d4d5b524e9dd322574b617fe16a26a9e627ff Mon Sep 17 00:00:00 2001
+From: Adam Tkac <atkac at redhat.com>
+Date: Fri, 15 Jun 2012 14:05:25 +0200
+Subject: [PATCH 06/27] Check for Kerberos 5 development files in configure.
+
+Signed-off-by: Adam Tkac <atkac at redhat.com>
+---
+ configure.ac | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/configure.ac b/configure.ac
+index 37e986c..6686310 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -18,6 +18,8 @@ AC_CHECK_LIB([dns], [dns_name_init], [],
+ AC_MSG_ERROR([Install BIND9 development files]))
+ AC_CHECK_LIB([ldap], [ldap_initialize], [],
+ AC_MSG_ERROR([Install OpenLDAP development files]))
++AC_CHECK_LIB([krb5], [krb5_cc_initialize], [],
++ AC_MSG_ERROR([Install Kerberos 5 development files]))
+
+ # Checks for header files.
+ AC_CHECK_HEADERS([stddef.h stdlib.h string.h strings.h])
+--
+1.7.11.2
+
+
+From d52ad09a3942392995e73aa0ebc0daddc823ea75 Mon Sep 17 00:00:00 2001
+From: Adam Tkac <atkac at redhat.com>
+Date: Mon, 18 Jun 2012 15:30:19 +0200
+Subject: [PATCH 07/27] Use SIGUSR1 to wake-up and terminate psearch_watcher.
+
+The previously SIGTERM interfered with BIND9 SIGTERM handler.
+
+Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=832015
+
+Signed-off-by: Adam Tkac <atkac at redhat.com>
+---
+ src/ldap_helper.c | 84 +++++++++++++++++++++++++++++++++++++++++++++++++------
+ 1 file changed, 75 insertions(+), 9 deletions(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index 09c1f7d..f3f2106 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -544,8 +544,11 @@ destroy_ldap_instance(ldap_instance_t **ldap_instp)
+ * Wake up the watcher thread. This might look like a hack
+ * but isc_thread_t is actually pthread_t and libisc don't
+ * have any isc_thread_kill() func.
++ *
++ * We use SIGUSR1 to not to interfere with any signal
++ * used by BIND itself.
+ */
+- REQUIRE(pthread_kill(ldap_inst->watcher, SIGTERM) == 0);
++ REQUIRE(pthread_kill(ldap_inst->watcher, SIGUSR1) == 0);
+ RUNTIME_CHECK(isc_thread_join(ldap_inst->watcher, NULL)
+ == ISC_R_SUCCESS);
+ ldap_inst->watcher = 0;
+@@ -3081,6 +3084,65 @@ cleanup:
+ }
+ }
+
++#define CHECK_EXIT \
++ do { \
++ if (inst->exiting) \
++ goto cleanup; \
++ } while (0)
++
++/*
++ * This "sane" sleep allows us to end if signal set the "exiting" variable.
++ *
++ * Returns ISC_FALSE if we should terminate, ISC_TRUE otherwise.
++ */
++static inline isc_boolean_t
++sane_sleep(const ldap_instance_t *inst, unsigned int timeout)
++{
++ unsigned int remains = timeout;
++
++ while (remains && !inst->exiting)
++ remains = sleep(remains);
++
++ if (remains)
++ log_debug(99, "sane_sleep: interrupted");
++
++ return inst->exiting ? ISC_FALSE : ISC_TRUE;
++}
++
++/* No-op signal handler for SIGUSR1 */
++static void
++noop_handler(int signal)
++{
++ UNUSED(signal);
++}
++
++static inline void
++install_usr1handler(void)
++{
++ struct sigaction sa;
++ struct sigaction oldsa;
++ int ret;
++ static isc_boolean_t once = ISC_FALSE;
++
++ if (once)
++ return;
++
++ memset(&sa, 0, sizeof(sa));
++ sa.sa_handler = &noop_handler;
++
++ ret = sigaction(SIGUSR1, &sa, &oldsa);
++ RUNTIME_CHECK(ret == 0); /* If sigaction fails, it's a bug */
++
++ /* Don't attempt to replace already existing handler */
++ RUNTIME_CHECK(oldsa.sa_handler == NULL);
++
++ once = ISC_TRUE;
++}
++
++/*
++ * NOTE:
++ * Every blocking call in psearch_watcher thread must be preemptible.
++ */
+ static isc_threadresult_t
+ ldap_psearch_watcher(isc_threadarg_t arg)
+ {
+@@ -3093,14 +3155,16 @@ ldap_psearch_watcher(isc_threadarg_t arg)
+
+ log_debug(1, "Entering ldap_psearch_watcher");
+
++ install_usr1handler();
++
+ /*
+ * By default, BIND sets threads to accept signals only via
+- * sigwait(). However we need to use SIGTERM to interrupt
++ * sigwait(). However we need to use SIGUSR1 to interrupt
+ * watcher from waiting inside ldap_result so enable
+- * asynchronous delivering of SIGTERM.
++ * asynchronous delivering of SIGUSR1.
+ */
+ sigemptyset(&sigset);
+- sigaddset(&sigset, SIGTERM);
++ sigaddset(&sigset, SIGUSR1);
+ ret = pthread_sigmask(SIG_UNBLOCK, &sigset, NULL);
+ /* pthread_sigmask fails only due invalid args */
+ RUNTIME_CHECK(ret == 0);
+@@ -3114,12 +3178,12 @@ ldap_psearch_watcher(isc_threadarg_t arg)
+
+ /* Try to connect. */
+ while (conn->handle == NULL) {
+- if (inst->exiting)
+- goto cleanup;
++ CHECK_EXIT;
+
+ log_error("ldap_psearch_watcher handle is NULL. "
+ "Next try in %ds", inst->reconnect_interval);
+- sleep(inst->reconnect_interval);
++ if (!sane_sleep(inst, inst->reconnect_interval))
++ goto cleanup;
+ ldap_connect(inst, conn, ISC_TRUE);
+ }
+
+@@ -3150,14 +3214,16 @@ restart:
+ while (!inst->exiting) {
+ ret = ldap_result(conn->handle, conn->msgid, 0, &tv,
+ &conn->result);
+-
+ if (ret <= 0) {
++ /* Don't reconnect if signaled to exit */
++ CHECK_EXIT;
+ while (handle_connection_error(inst, conn, ISC_TRUE)
+ != ISC_R_SUCCESS) {
+ log_error("ldap_psearch_watcher failed to handle "
+ "LDAP connection error. Reconnection "
+ "in %ds", inst->reconnect_interval);
+- sleep(inst->reconnect_interval);
++ if (!sane_sleep(inst, inst->reconnect_interval))
++ goto cleanup;
+ }
+ goto restart;
+ }
+--
+1.7.11.2
+
+
+From a7cd8ae747b3a81a02ab9e5dbefe1c595aa24ff6 Mon Sep 17 00:00:00 2001
+From: Adam Tkac <atkac at redhat.com>
+Date: Mon, 18 Jun 2012 15:54:18 +0200
+Subject: [PATCH 08/27] ldap_query can incorrectly return ISC_R_SUCCESS even
+ when failed
+
+Signed-off-by: Adam Tkac <atkac at redhat.com>
+---
+ src/ldap_helper.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index f3f2106..7f0a6f4 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -1670,7 +1670,7 @@ retry:
+ goto retry;
+ }
+
+- return result;
++ return ISC_R_FAILURE;
+ }
+
+ static void
+--
+1.7.11.2
+
+
+From 88dcade344af6e71503b85c4d2630343dbf7d7c0 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Mon, 7 May 2012 12:51:09 +0200
+Subject: [PATCH 09/27] Separate LDAP result from LDAP connection and fix
+ deadlock. This affects operation without persistent
+ search with connections count == 1.
+
+https://fedorahosted.org/bind-dyndb-ldap/ticket/66
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 234 +++++++++++++++++++++++++++++++++---------------------
+ 1 file changed, 143 insertions(+), 91 deletions(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index 7f0a6f4..aa7f976 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -109,6 +109,7 @@
+ * must acquire the semaphore and the lock.
+ */
+
++typedef struct ldap_qresult ldap_qresult_t;
+ typedef struct ldap_connection ldap_connection_t;
+ typedef struct ldap_pool ldap_pool_t;
+ typedef struct ldap_auth_pair ldap_auth_pair_t;
+@@ -186,10 +187,8 @@ struct ldap_pool {
+ struct ldap_connection {
+ isc_mem_t *mctx;
+ isc_mutex_t lock;
+- ld_string_t *query_string;
+
+ LDAP *handle;
+- LDAPMessage *result;
+ LDAPControl *serverctrls[2]; /* psearch/NULL or NULL/NULL */
+ int msgid;
+
+@@ -198,19 +197,19 @@ struct ldap_connection {
+ isc_buffer_t rdata_target;
+ unsigned char *rdata_target_mem;
+
+- /* Cache. */
+- ldap_entrylist_t ldap_entries;
+-
+ /* For reconnection logic. */
+ isc_time_t next_reconnect;
+ unsigned int tries;
++};
+
+- /* Temporary stuff. */
+- LDAPMessage *entry;
+- BerElement *ber;
+- char *attribute;
+- char **values;
+- char *dn;
++/**
++ * Result from single LDAP query.
++ */
++struct ldap_qresult {
++ isc_mem_t *mctx;
++ ld_string_t *query_string;
++ LDAPMessage *result;
++ ldap_entrylist_t ldap_entries;
+ };
+
+ /*
+@@ -271,9 +270,10 @@ static isc_result_t ldap_reconnect(ldap_instance_t *ldap_inst,
+ static isc_result_t handle_connection_error(ldap_instance_t *ldap_inst,
+ ldap_connection_t *ldap_conn, isc_boolean_t force);
+ static isc_result_t ldap_query(ldap_instance_t *ldap_inst, ldap_connection_t *ldap_conn,
+- const char *base,
+- int scope, char **attrs, int attrsonly, const char *filter, ...);
+-static void ldap_query_free(ldap_connection_t *ldap_conn);
++ ldap_qresult_t **ldap_qresultp, const char *base, int scope, char **attrs,
++ int attrsonly, const char *filter, ...);
++static isc_result_t ldap_query_create(isc_mem_t *mctx, ldap_qresult_t **ldap_qresultp);
++static void ldap_query_free(isc_boolean_t prepare_reuse, ldap_qresult_t **ldap_qresultp);
+
+ /* Functions for writing to LDAP. */
+ static isc_result_t ldap_modify_do(ldap_connection_t *ldap_conn, const char *dn,
+@@ -608,8 +608,6 @@ new_ldap_connection(ldap_pool_t *pool, ldap_connection_t **ldap_connp)
+
+ isc_mem_attach(pool->mctx, &ldap_conn->mctx);
+
+- CHECK(str_new(ldap_conn->mctx, &ldap_conn->query_string));
+-
+ CHECK(isc_lex_create(ldap_conn->mctx, TOKENSIZ, &ldap_conn->lex));
+ CHECKED_MEM_GET(ldap_conn->mctx, ldap_conn->rdata_target_mem, MINTSIZ);
+ CHECK(ldap_pscontrol_create(ldap_conn->mctx,
+@@ -637,8 +635,6 @@ destroy_ldap_connection(ldap_pool_t *pool, ldap_connection_t **ldap_connp)
+ if (ldap_conn->handle != NULL)
+ ldap_unbind_ext_s(ldap_conn->handle, NULL, NULL);
+
+- str_destroy(&ldap_conn->query_string);
+-
+ if (ldap_conn->lex != NULL)
+ isc_lex_destroy(&ldap_conn->lex);
+ if (ldap_conn->rdata_target_mem != NULL) {
+@@ -1103,6 +1099,8 @@ refresh_zones_from_ldap(ldap_instance_t *ldap_inst)
+ {
+ isc_result_t result = ISC_R_SUCCESS;
+ ldap_connection_t *ldap_conn = NULL;
++ ldap_qresult_t *ldap_config_qresult = NULL;
++ ldap_qresult_t *ldap_zones_qresult = NULL;
+ int zone_count = 0;
+ ldap_entry_t *entry;
+ dns_rbt_t *rbt = NULL;
+@@ -1127,31 +1125,31 @@ refresh_zones_from_ldap(ldap_instance_t *ldap_inst)
+
+ log_debug(2, "refreshing list of zones for %s", ldap_inst->db_name);
+
++ /* Query for configuration and zones from LDAP and release LDAP connection
++ * before processing them. It prevents deadlock in situations where
++ * ldap_parse_zoneentry() requests another connection. */
+ CHECK(ldap_pool_getconnection(ldap_inst->pool, &ldap_conn));
+-
+- CHECK(ldap_query(ldap_inst, ldap_conn, str_buf(ldap_inst->base),
++ CHECK(ldap_query(ldap_inst, ldap_conn, &ldap_config_qresult, str_buf(ldap_inst->base),
+ LDAP_SCOPE_SUBTREE, config_attrs, 0,
+ "(objectClass=idnsConfigObject)"));
++ CHECK(ldap_query(ldap_inst, ldap_conn, &ldap_zones_qresult, str_buf(ldap_inst->base),
++ LDAP_SCOPE_SUBTREE, zone_attrs, 0,
++ "(&(objectClass=idnsZone)(idnsZoneActive=TRUE))"));
++ ldap_pool_putconnection(ldap_inst->pool, &ldap_conn);
+
+- for (entry = HEAD(ldap_conn->ldap_entries);
++ for (entry = HEAD(ldap_config_qresult->ldap_entries);
+ entry != NULL;
+ entry = NEXT(entry, link)) {
+ CHECK(ldap_parse_configentry(entry, ldap_inst));
+ }
+
+- ldap_query_free(ldap_conn);
+-
+- CHECK(ldap_query(ldap_inst, ldap_conn, str_buf(ldap_inst->base),
+- LDAP_SCOPE_SUBTREE, zone_attrs, 0,
+- "(&(objectClass=idnsZone)(idnsZoneActive=TRUE))"));
+-
+ /*
+ * Create RB-tree with all zones stored in LDAP for cross check
+ * with registered zones in plugin.
+ */
+ CHECK(dns_rbt_create(ldap_inst->mctx, NULL, NULL, &rbt));
+
+- for (entry = HEAD(ldap_conn->ldap_entries);
++ for (entry = HEAD(ldap_zones_qresult->ldap_entries);
+ entry != NULL;
+ entry = NEXT(entry, link)) {
+
+@@ -1240,6 +1238,8 @@ cleanup:
+ if (invalidate_nodechain)
+ dns_rbtnodechain_invalidate(&chain);
+
++ ldap_query_free(ISC_FALSE, &ldap_config_qresult);
++ ldap_query_free(ISC_FALSE, &ldap_zones_qresult);
+ ldap_pool_putconnection(ldap_inst->pool, &ldap_conn);
+
+ log_debug(2, "finished refreshing list of zones");
+@@ -1395,6 +1395,7 @@ ldapdb_nodelist_get(isc_mem_t *mctx, ldap_instance_t *ldap_inst, dns_name_t *nam
+ {
+ isc_result_t result;
+ ldap_connection_t *ldap_conn = NULL;
++ ldap_qresult_t *ldap_qresult = NULL;
+ ldap_entry_t *entry;
+ ld_string_t *string = NULL;
+ ldapdb_node_t *node;
+@@ -1411,15 +1412,15 @@ ldapdb_nodelist_get(isc_mem_t *mctx, ldap_instance_t *ldap_inst, dns_name_t *nam
+ CHECK(str_new(mctx, &string));
+ CHECK(dnsname_to_dn(ldap_inst->zone_register, name, string));
+
+- CHECK(ldap_query(ldap_inst, ldap_conn, str_buf(string),
++ CHECK(ldap_query(ldap_inst, ldap_conn, &ldap_qresult, str_buf(string),
+ LDAP_SCOPE_SUBTREE, NULL, 0, "(objectClass=idnsRecord)"));
+
+- if (EMPTY(ldap_conn->ldap_entries)) {
++ if (EMPTY(ldap_qresult->ldap_entries)) {
+ result = ISC_R_NOTFOUND;
+ goto cleanup;
+ }
+
+- for (entry = HEAD(ldap_conn->ldap_entries);
++ for (entry = HEAD(ldap_qresult->ldap_entries);
+ entry != NULL;
+ entry = NEXT(entry, link)) {
+ node = NULL;
+@@ -1452,6 +1453,7 @@ ldapdb_nodelist_get(isc_mem_t *mctx, ldap_instance_t *ldap_inst, dns_name_t *nam
+ result = ISC_R_SUCCESS;
+
+ cleanup:
++ ldap_query_free(ISC_FALSE, &ldap_qresult);
+ ldap_pool_putconnection(ldap_inst->pool, &ldap_conn);
+ str_destroy(&string);
+
+@@ -1464,6 +1466,7 @@ ldapdb_rdatalist_get(isc_mem_t *mctx, ldap_instance_t *ldap_inst, dns_name_t *na
+ {
+ isc_result_t result;
+ ldap_connection_t *ldap_conn = NULL;
++ ldap_qresult_t *ldap_qresult = NULL;
+ ldap_entry_t *entry;
+ ld_string_t *string = NULL;
+ ldap_cache_t *cache;
+@@ -1486,15 +1489,15 @@ ldapdb_rdatalist_get(isc_mem_t *mctx, ldap_instance_t *ldap_inst, dns_name_t *na
+ CHECK(dnsname_to_dn(ldap_inst->zone_register, name, string));
+
+ CHECK(ldap_pool_getconnection(ldap_inst->pool, &ldap_conn));
+- CHECK(ldap_query(ldap_inst, ldap_conn, str_buf(string),
++ CHECK(ldap_query(ldap_inst, ldap_conn, &ldap_qresult, str_buf(string),
+ LDAP_SCOPE_BASE, NULL, 0, "(objectClass=idnsRecord)"));
+
+- if (EMPTY(ldap_conn->ldap_entries)) {
++ if (EMPTY(ldap_qresult->ldap_entries)) {
+ result = ISC_R_NOTFOUND;
+ goto cleanup;
+ }
+
+- for (entry = HEAD(ldap_conn->ldap_entries);
++ for (entry = HEAD(ldap_qresult->ldap_entries);
+ entry != NULL;
+ entry = NEXT(entry, link)) {
+ if (ldap_parse_rrentry(mctx, entry, ldap_conn,
+@@ -1512,6 +1515,7 @@ ldapdb_rdatalist_get(isc_mem_t *mctx, ldap_instance_t *ldap_inst, dns_name_t *na
+ result = ISC_R_NOTFOUND;
+
+ cleanup:
++ ldap_query_free(ISC_FALSE, &ldap_qresult);
+ ldap_pool_putconnection(ldap_inst->pool, &ldap_conn);
+ str_destroy(&string);
+
+@@ -1609,55 +1613,65 @@ cleanup:
+ return result;
+ }
+
++/**
++ * @param ldap_conn A LDAP connection structure obtained via ldap_get_connection().
++ * @param ldap_qresult New ldap_qresult structure will be allocated and pointer
++ * to it will be returned through this parameter. The result
++ * has to be freed by caller via ldap_query_free().
++ */
+ static isc_result_t
+ ldap_query(ldap_instance_t *ldap_inst, ldap_connection_t *ldap_conn,
+- const char *base, int scope, char **attrs,
++ ldap_qresult_t **ldap_qresultp, const char *base, int scope, char **attrs,
+ int attrsonly, const char *filter, ...)
+ {
+ va_list ap;
+ isc_result_t result;
++ ldap_qresult_t *ldap_qresult = NULL;
+ int cnt;
+ int ret;
+ int once = 0;
+
+ REQUIRE(ldap_conn != NULL);
++ REQUIRE(ldap_qresultp != NULL && *ldap_qresultp == NULL);
++
++ CHECK(ldap_query_create(ldap_inst->mctx, &ldap_qresult));
+
+ va_start(ap, filter);
+- str_vsprintf(ldap_conn->query_string, filter, ap);
++ str_vsprintf(ldap_qresult->query_string, filter, ap);
+ va_end(ap);
+
+ log_debug(2, "querying '%s' with '%s'", base,
+- str_buf(ldap_conn->query_string));
++ str_buf(ldap_qresult->query_string));
+
+ if (ldap_conn->handle == NULL) {
+ /*
+ * handle can be NULL when the first connection to LDAP wasn't
+ * successful
++ * TODO: handle this case inside ldap_pool_getconnection()?
+ */
+- result = ldap_connect(ldap_inst, ldap_conn, ISC_FALSE);
+- if (result != ISC_R_SUCCESS)
+- return result;
++ CHECK(ldap_connect(ldap_inst, ldap_conn, ISC_FALSE));
+ }
+
+ retry:
+ ret = ldap_search_ext_s(ldap_conn->handle, base, scope,
+- str_buf(ldap_conn->query_string),
++ str_buf(ldap_qresult->query_string),
+ attrs, attrsonly, NULL, NULL, NULL,
+- LDAP_NO_LIMIT, &ldap_conn->result);
++ LDAP_NO_LIMIT, &ldap_qresult->result);
+ if (ret == 0) {
+ ldap_conn->tries = 0;
+- cnt = ldap_count_entries(ldap_conn->handle, ldap_conn->result);
++ cnt = ldap_count_entries(ldap_conn->handle, ldap_qresult->result);
+ log_debug(2, "entry count: %d", cnt);
+
+ result = ldap_entrylist_create(ldap_conn->mctx,
+ ldap_conn->handle,
+- ldap_conn->result,
+- &ldap_conn->ldap_entries);
++ ldap_qresult->result,
++ &ldap_qresult->ldap_entries);
+ if (result != ISC_R_SUCCESS) {
+ log_error("failed to save LDAP query results");
+ return result;
+ }
+
++ *ldap_qresultp = ldap_qresult;
+ return ISC_R_SUCCESS;
+ }
+
+@@ -1669,38 +1683,70 @@ retry:
+ if (result == ISC_R_SUCCESS)
+ goto retry;
+ }
+-
++cleanup:
++ ldap_query_free(ISC_FALSE, &ldap_qresult);
+ return ISC_R_FAILURE;
+ }
+
++/**
++ * Allocate and initialize new ldap_qresult structure.
++ * @param[out] ldap_qresultp Newly allocated ldap_qresult structure.
++ * @return ISC_R_SUCCESS or ISC_R_NOMEMORY (from CHECKED_MEM_GET_PTR)
++ */
++static isc_result_t
++ldap_query_create(isc_mem_t *mctx, ldap_qresult_t **ldap_qresultp) {
++ ldap_qresult_t *ldap_qresult = NULL;
++ isc_result_t result;
++
++ CHECKED_MEM_GET_PTR(mctx, ldap_qresult);
++ ldap_qresult->mctx = mctx;
++ ldap_qresult->result = NULL;
++ ldap_qresult->query_string = NULL;
++ INIT_LIST(ldap_qresult->ldap_entries);
++ CHECK(str_new(mctx, &ldap_qresult->query_string));
++
++ *ldap_qresultp = ldap_qresult;
++ return ISC_R_SUCCESS;
++
++cleanup:
++ SAFE_MEM_PUT_PTR(mctx, ldap_qresult);
++ return result;
++}
++
++/**
++ * Free LDAP query result. Can free the whole structure or internal parts only.
++ * Freeing internal parts is suitable before reusing the structure.
++ * @param[in] prepare_reuse ISC_TRUE implies freeing internal parts,
++ * but not the whole structure.
++ * @param[in,out] ldap_qresultp Pointer to freed query. Will be set to NULL
++ * if prepare_reuse == ISC_FALSE.
++ */
+ static void
+-ldap_query_free(ldap_connection_t *ldap_conn)
++ldap_query_free(isc_boolean_t prepare_reuse, ldap_qresult_t **ldap_qresultp)
+ {
+- if (ldap_conn == NULL)
++ ldap_qresult_t *qresult;
++ REQUIRE(ldap_qresultp != NULL);
++
++ qresult = *ldap_qresultp;
++
++ if (qresult == NULL)
+ return;
+
+- if (ldap_conn->dn) {
+- ldap_memfree(ldap_conn->dn);
+- ldap_conn->dn = NULL;
+- }
+- if (ldap_conn->values) {
+- ldap_value_free(ldap_conn->values);
+- ldap_conn->values = NULL;
+- }
+- if (ldap_conn->attribute) {
+- ldap_memfree(ldap_conn->attribute);
+- ldap_conn->attribute = NULL;
+- }
+- if (ldap_conn->ber) {
+- ber_free(ldap_conn->ber, 0);
+- ldap_conn->ber = NULL;
+- }
+- if (ldap_conn->result) {
+- ldap_msgfree(ldap_conn->result);
+- ldap_conn->result = NULL;
++ if (qresult->result) {
++ ldap_msgfree(qresult->result);
++ qresult->result = NULL;
+ }
+
+- ldap_entrylist_destroy(ldap_conn->mctx, &ldap_conn->ldap_entries);
++ ldap_entrylist_destroy(qresult->mctx, &qresult->ldap_entries);
++
++ if (prepare_reuse) {
++ str_clear(qresult->query_string);
++ INIT_LIST(qresult->ldap_entries);
++ } else { /* free the whole structure */
++ str_destroy(&qresult->query_string);
++ SAFE_MEM_PUT_PTR(qresult->mctx, qresult);
++ *ldap_qresultp = NULL;
++ }
+ }
+
+ /* FIXME: Tested with SASL/GSSAPI/KRB5 only */
+@@ -2246,6 +2292,7 @@ modify_ldap_common(dns_name_t *owner, ldap_instance_t *ldap_inst,
+ isc_result_t result;
+ isc_mem_t *mctx = ldap_inst->mctx;
+ ldap_connection_t *ldap_conn = NULL;
++ ldap_qresult_t *ldap_qresult = NULL;
+ ld_string_t *owner_dn = NULL;
+ LDAPMod *change[3] = { NULL };
+ LDAPMod *change_ptr = NULL;
+@@ -2272,12 +2319,12 @@ modify_ldap_common(dns_name_t *owner, ldap_instance_t *ldap_inst,
+ }
+
+ CHECK(ldap_pool_getconnection(ldap_inst->pool, &ldap_conn));
+- CHECK(ldap_query(ldap_inst, ldap_conn, zone_dn,
++ CHECK(ldap_query(ldap_inst, ldap_conn, &ldap_qresult, zone_dn,
+ LDAP_SCOPE_BASE, attrs, 0,
+ "(&(objectClass=idnsZone)(idnsZoneActive=TRUE))"));
+
+ /* only 0 or 1 active zone can be returned from query */
+- entry = HEAD(ldap_conn->ldap_entries);
++ entry = HEAD(ldap_qresult->ldap_entries);
+ if (entry == NULL) {
+ log_debug(3, "Active zone %s not found", zone_dn);
+ result = ISC_R_NOTFOUND;
+@@ -2409,14 +2456,14 @@ modify_ldap_common(dns_name_t *owner, ldap_instance_t *ldap_inst,
+ char *owner_zone_dn_ptr = strstr(str_buf(owner_dn_ptr),", ") + 1;
+
+ /* Get attribute "idnsAllowDynUpdate" for reverse zone or use default. */
+- ldap_query_free(ldap_conn);
++ ldap_query_free(ISC_FALSE, &ldap_qresult);
+ zone_dyn_update = ldap_inst->dyn_update;
+- CHECK(ldap_query(ldap_inst, ldap_conn, owner_zone_dn_ptr,
++ CHECK(ldap_query(ldap_inst, ldap_conn, &ldap_qresult, owner_zone_dn_ptr,
+ LDAP_SCOPE_BASE, attrs, 0,
+ "(&(objectClass=idnsZone)(idnsZoneActive=TRUE))"));
+
+ /* Only 0 or 1 active zone can be returned from query. */
+- entry = HEAD(ldap_conn->ldap_entries);
++ entry = HEAD(ldap_qresult->ldap_entries);
+ if (entry == NULL) {
+ log_debug(3, "Active zone %s not found", zone_dn);
+ result = ISC_R_NOTFOUND;
+@@ -2502,6 +2549,7 @@ modify_ldap_common(dns_name_t *owner, ldap_instance_t *ldap_inst,
+ }
+
+ cleanup:
++ ldap_query_free(ISC_FALSE, &ldap_qresult);
+ ldap_pool_putconnection(ldap_inst->pool, &ldap_conn);
+ str_destroy(&owner_dn_ptr);
+ str_destroy(&owner_dn);
+@@ -2604,7 +2652,6 @@ ldap_pool_getconnection(ldap_pool_t *pool, ldap_connection_t ** conn)
+
+ RUNTIME_CHECK(ldap_conn != NULL);
+
+- INIT_LIST(ldap_conn->ldap_entries);
+ *conn = ldap_conn;
+
+ cleanup:
+@@ -2624,7 +2671,6 @@ ldap_pool_putconnection(ldap_pool_t *pool, ldap_connection_t **conn)
+ if (ldap_conn == NULL)
+ return;
+
+- ldap_query_free(ldap_conn);
+ UNLOCK(&ldap_conn->lock);
+ semaphore_signal(&pool->conn_semaphore);
+
+@@ -2756,6 +2802,7 @@ update_action(isc_task_t *task, isc_event_t *event)
+ isc_result_t result ;
+ ldap_instance_t *inst = NULL;
+ ldap_connection_t *conn = NULL;
++ ldap_qresult_t *ldap_qresult = NULL;
+ ldap_entry_t *entry;
+ isc_boolean_t delete = ISC_TRUE;
+ isc_mem_t *mctx;
+@@ -2775,11 +2822,11 @@ update_action(isc_task_t *task, isc_event_t *event)
+
+ CHECK(ldap_pool_getconnection(inst->pool, &conn));
+
+- CHECK(ldap_query(inst, conn, pevent->dn,
++ CHECK(ldap_query(inst, conn, &ldap_qresult, pevent->dn,
+ LDAP_SCOPE_BASE, attrs, 0,
+ "(&(objectClass=idnsZone)(idnsZoneActive=TRUE))"));
+
+- for (entry = HEAD(conn->ldap_entries);
++ for (entry = HEAD(ldap_qresult->ldap_entries);
+ entry != NULL;
+ entry = NEXT(entry, link)) {
+ delete = ISC_FALSE;
+@@ -2797,6 +2844,7 @@ cleanup:
+ "Zones can be outdated, run `rndc reload`",
+ pevent->dn);
+
++ ldap_query_free(ISC_FALSE, &ldap_qresult);
+ ldap_pool_putconnection(inst->pool, &conn);
+ isc_mem_free(mctx, pevent->dbname);
+ isc_mem_free(mctx, pevent->dn);
+@@ -2811,6 +2859,7 @@ update_config(isc_task_t *task, isc_event_t *event)
+ isc_result_t result ;
+ ldap_instance_t *inst = NULL;
+ ldap_connection_t *conn = NULL;
++ ldap_qresult_t *ldap_qresult = NULL;
+ ldap_entry_t *entry;
+ isc_mem_t *mctx;
+ char *attrs[] = {
+@@ -2828,14 +2877,14 @@ update_config(isc_task_t *task, isc_event_t *event)
+
+ CHECK(ldap_pool_getconnection(inst->pool, &conn));
+
+- CHECK(ldap_query(inst, conn, pevent->dn,
++ CHECK(ldap_query(inst, conn, &ldap_qresult, pevent->dn,
+ LDAP_SCOPE_BASE, attrs, 0,
+ "(objectClass=idnsConfigObject)"));
+
+- if (EMPTY(conn->ldap_entries))
+- log_error("Config object can not be empty");
++ if (EMPTY(ldap_qresult->ldap_entries))
++ log_error("Config object can not be empty"); /* TODO: WHY? */
+
+- for (entry = HEAD(conn->ldap_entries);
++ for (entry = HEAD(ldap_qresult->ldap_entries);
+ entry != NULL;
+ entry = NEXT(entry, link)) {
+ result = ldap_parse_configentry(entry, inst);
+@@ -2850,6 +2899,7 @@ cleanup:
+ "Configuration can be outdated, run `rndc reload`",
+ pevent->dn);
+
++ ldap_query_free(ISC_FALSE, &ldap_qresult);
+ ldap_pool_putconnection(inst->pool, &conn);
+ isc_mem_free(mctx, pevent->dbname);
+ isc_mem_free(mctx, pevent->dn);
+@@ -3148,6 +3198,7 @@ ldap_psearch_watcher(isc_threadarg_t arg)
+ {
+ ldap_instance_t *inst = (ldap_instance_t *)arg;
+ ldap_connection_t *conn = NULL;
++ ldap_qresult_t *ldap_qresult = NULL;
+ struct timeval tv;
+ int ret, cnt;
+ isc_result_t result;
+@@ -3187,6 +3238,8 @@ ldap_psearch_watcher(isc_threadarg_t arg)
+ ldap_connect(inst, conn, ISC_TRUE);
+ }
+
++ CHECK(ldap_query_create(conn->mctx, &ldap_qresult));
++
+ restart:
+ /* Perform initial lookup */
+ if (inst->psearch) {
+@@ -3213,7 +3266,7 @@ restart:
+
+ while (!inst->exiting) {
+ ret = ldap_result(conn->handle, conn->msgid, 0, &tv,
+- &conn->result);
++ &ldap_qresult->result);
+ if (ret <= 0) {
+ /* Don't reconnect if signaled to exit */
+ CHECK_EXIT;
+@@ -3225,6 +3278,7 @@ restart:
+ if (!sane_sleep(inst, inst->reconnect_interval))
+ goto cleanup;
+ }
++ ldap_query_free(ISC_TRUE, &ldap_qresult);
+ goto restart;
+ }
+
+@@ -3237,14 +3291,14 @@ restart:
+ }
+
+ conn->tries = 0;
+- cnt = ldap_count_entries(conn->handle, conn->result);
+-
++ cnt = ldap_count_entries(conn->handle, ldap_qresult->result);
++
+ if (cnt > 0) {
+ log_debug(3, "Got psearch updates (%d)", cnt);
+ result = ldap_entrylist_append(conn->mctx,
+ conn->handle,
+- conn->result,
+- &conn->ldap_entries);
++ ldap_qresult->result,
++ &ldap_qresult->ldap_entries);
+ if (result != ISC_R_SUCCESS) {
+ /*
+ * Error means inconsistency of our zones
+@@ -3256,7 +3310,7 @@ restart:
+ }
+
+ ldap_entry_t *entry;
+- for (entry = HEAD(conn->ldap_entries);
++ for (entry = HEAD(ldap_qresult->ldap_entries);
+ entry != NULL;
+ entry = NEXT(entry, link)) {
+ LDAPControl **ctrls = NULL;
+@@ -3274,16 +3328,14 @@ restart:
+ psearch_update(inst, entry, ctrls);
+ }
+ soft_err:
+-
+- ldap_msgfree(conn->result);
+- ldap_entrylist_destroy(conn->mctx,
+- &conn->ldap_entries);
++ ldap_query_free(ISC_TRUE, &ldap_qresult);
+ }
+ }
+
+ log_debug(1, "Ending ldap_psearch_watcher");
+
+ cleanup:
++ ldap_query_free(ISC_FALSE, &ldap_qresult);
+ ldap_pool_putconnection(inst->pool, &conn);
+
+ return (isc_threadresult_t)0;
+--
+1.7.11.2
+
+
+From 3c382dd0296f6fe2931ddb0d18de220e6740011c Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Thu, 28 Jun 2012 13:52:38 +0200
+Subject: [PATCH 10/27] Add debug message to ldap_cache_getrdatalist()
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/cache.c | 9 +++++++++
+ 1 file changed, 9 insertions(+)
+
+diff --git a/src/cache.c b/src/cache.c
+index c8afb99..28f93c9 100644
+--- a/src/cache.c
++++ b/src/cache.c
+@@ -26,6 +26,7 @@
+
+ #include <dns/rbt.h>
+ #include <dns/result.h>
++#include <dns/log.h>
+
+ #include <string.h>
+
+@@ -207,6 +208,14 @@ ldap_cache_getrdatalist(isc_mem_t *mctx, ldap_cache_t *cache,
+
+ cleanup:
+ UNLOCK(&cache->mutex);
++
++ if (isc_log_getdebuglevel(dns_lctx) >= 20) {
++ char dns_str[DNS_NAME_FORMATSIZE];
++ dns_name_format(name, dns_str, sizeof(dns_str));
++ log_debug(20, "cache search for '%s': %s", dns_str,
++ isc_result_totext(result));
++ }
++
+ return result;
+ }
+
+--
+1.7.11.2
+
+
+From 99663b6d65cf5dc166b3cb6f83be1878b8de3163 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Wed, 27 Jun 2012 10:36:26 +0200
+Subject: [PATCH 11/27] Increment SOA serial for each ordinary record received
+ through psearch
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 93 insertions(+), 1 deletion(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index aa7f976..0df1e03 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -34,6 +34,8 @@
+ #include <dns/zt.h>
+ #include <dns/byaddr.h>
+ #include <dns/forward.h>
++#include <dns/soa.h>
++#include <isc/serial.h>
+
+ #include <isc/buffer.h>
+ #include <isc/lex.h>
+@@ -173,6 +175,7 @@ struct ldap_instance {
+ isc_boolean_t exiting;
+ isc_boolean_t sync_ptr;
+ isc_boolean_t dyn_update;
++ isc_boolean_t serial_autoincrement;
+ };
+
+ struct ldap_pool {
+@@ -343,6 +346,7 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
+ { "ldap_hostname", default_string("") },
+ { "sync_ptr", default_boolean(ISC_FALSE) },
+ { "dyn_update", default_boolean(ISC_FALSE) },
++ { "serial_autoincrement", default_boolean(ISC_FALSE) },
+ end_of_settings
+ };
+
+@@ -401,6 +405,7 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
+ ldap_settings[i++].target = ldap_inst->ldap_hostname;
+ ldap_settings[i++].target = &ldap_inst->sync_ptr;
+ ldap_settings[i++].target = &ldap_inst->dyn_update;
++ ldap_settings[i++].target = &ldap_inst->serial_autoincrement;
+ CHECK(set_settings(ldap_settings, argv));
+
+ /* Set timer for deadlock detection inside semaphore_wait_timed . */
+@@ -463,6 +468,13 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
+ "increasing limit");
+ ldap_inst->connections = 3;
+ }
++ if (ldap_inst->serial_autoincrement == ISC_TRUE
++ && ldap_inst->psearch != ISC_TRUE) {
++ log_error("SOA serial number auto-increment feature requires "
++ "persistent search");
++ result = ISC_R_FAILURE;
++ goto cleanup;
++ }
+
+ CHECK(new_ldap_cache(mctx, argv, &ldap_inst->cache, ldap_inst->psearch));
+ CHECK(ldap_pool_create(mctx, ldap_inst->connections, &ldap_inst->pool));
+@@ -2787,6 +2799,82 @@ ldap_pscontrol_destroy(isc_mem_t *mctx, LDAPControl **ctrlp)
+ *ctrlp = NULL;
+ }
+
++static inline isc_result_t
++soa_serial_get(ldap_instance_t *inst, dns_name_t *zone_name,
++ isc_uint32_t *serial) {
++ isc_result_t result;
++ dns_zone_t *zone = NULL;
++
++ CHECK(zr_get_zone_ptr(inst->zone_register, zone_name, &zone));
++ CHECK(dns_zone_getserial2(zone, serial));
++ dns_zone_detach(&zone);
++
++cleanup:
++ return result;
++}
++
++isc_result_t
++soa_serial_increment(isc_mem_t *mctx, ldap_instance_t *inst,
++ dns_name_t *zone_name) {
++ isc_result_t result = ISC_R_FAILURE;
++ ldap_connection_t * conn = NULL;
++ ld_string_t *zone_dn = NULL;
++ ldapdb_rdatalist_t rdatalist;
++ dns_rdatalist_t *rdlist = NULL;
++ dns_rdata_t *soa_rdata = NULL;
++ isc_uint32_t old_serial;
++ isc_uint32_t new_serial;
++ isc_time_t curr_time;
++
++ REQUIRE(inst != NULL);
++ REQUIRE(zone_name != NULL);
++
++ INIT_LIST(rdatalist);
++ CHECK(str_new(mctx, &zone_dn));
++ CHECK(dnsname_to_dn(inst->zone_register, zone_name, zone_dn));
++ log_debug(5, "incrementing SOA serial number in zone '%s'",
++ str_buf(zone_dn));
++
++ /* get original SOA rdata and serial value */
++ CHECK(ldapdb_rdatalist_get(mctx, inst, zone_name, zone_name, &rdatalist));
++ CHECK(ldapdb_rdatalist_findrdatatype(&rdatalist, dns_rdatatype_soa, &rdlist));
++ soa_rdata = ISC_LIST_HEAD(rdlist->rdata);
++ CHECK(soa_serial_get(inst, zone_name, &old_serial));
++
++ /* Compute the new SOA serial - use actual timestamp.
++ * If timestamp <= oldSOAserial then increment old serial by one. */
++ isc_time_now(&curr_time);
++ new_serial = isc_time_seconds(&curr_time) & 0xFFFFFFFF;
++ if (!isc_serial_gt(new_serial, old_serial)) {
++ /* increment by one, RFC1982, from bind-9.8.2/bin/named/update.c */
++ new_serial = (old_serial + 1) & 0xFFFFFFFF;
++ }
++ if (new_serial == 0)
++ new_serial = 1;
++ log_debug(5,"zone '%s': old serial %u, new serial %u",
++ str_buf(zone_dn), old_serial, new_serial);
++ dns_soa_setserial(new_serial, soa_rdata);
++
++ /* write the new serial back to DB */
++ CHECK(ldap_pool_getconnection(inst->pool, &conn));
++ CHECK(modify_soa_record(conn, str_buf(zone_dn), soa_rdata));
++ CHECK(discard_from_cache(ldap_instance_getcache(inst), zone_name));
++
++ /* put the new SOA to inst->cache and compare old and new serials */
++ CHECK(soa_serial_get(inst, zone_name, &new_serial));
++ INSIST(isc_serial_gt(new_serial, old_serial) == ISC_TRUE);
++
++cleanup:
++ if (result != ISC_R_SUCCESS)
++ log_error("SOA serial number incrementation failed in zone '%s'",
++ str_buf(zone_dn));
++
++ ldap_pool_putconnection(inst->pool, &conn);
++ str_destroy(&zone_dn);
++ ldapdb_rdatalist_destroy(mctx, &rdatalist);
++ return result;
++}
++
+ /*
+ * update_action routine is processed asynchronously so it cannot assume
+ * anything about state of ldap_inst from where it was sent. The ldap_inst
+@@ -2942,7 +3030,7 @@ update_record(isc_task_t *task, isc_event_t *event)
+ if (PSEARCH_DEL(pevent->chgtype)) {
+ log_debug(5, "psearch_update: Removing item from cache (%s)",
+ pevent->dn);
+- }
++ }
+
+ /* Get cache instance & clean old record */
+ cache = ldap_instance_getcache(inst);
+@@ -2966,6 +3054,10 @@ update_record(isc_task_t *task, isc_event_t *event)
+ /* Destroy rdatalist, it is now in the cache. */
+ ldapdb_rdatalist_destroy(mctx, &rdatalist);
+ }
++
++ if (inst->serial_autoincrement) {
++ CHECK(soa_serial_increment(mctx, inst, &origin));
++ }
+ cleanup:
+ if (result != ISC_R_SUCCESS)
+ log_error("update_record (psearch) failed for %s. "
+--
+1.7.11.2
+
+
+From cd37fba03c5c86a766d1a9f893036ac3540e8b7c Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Mon, 2 Jul 2012 11:01:58 +0200
+Subject: [PATCH 12/27] Do not bump serial for each record during initial
+ database dump.
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 18 ++++++++++++++----
+ 1 file changed, 14 insertions(+), 4 deletions(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index 0df1e03..7eb18cb 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -2723,6 +2723,7 @@ cleanup:
+ #define LDAP_CONTROL_PERSISTENTSEARCH "2.16.840.1.113730.3.4.3"
+ #define LDAP_CONTROL_ENTRYCHANGE "2.16.840.1.113730.3.4.7"
+
++#define LDAP_ENTRYCHANGE_NONE 0 /* entry change control is not present */
+ #define LDAP_ENTRYCHANGE_ADD 1
+ #define LDAP_ENTRYCHANGE_DEL 2
+ #define LDAP_ENTRYCHANGE_MOD 4
+@@ -2733,6 +2734,7 @@ cleanup:
+ #define PSEARCH_DEL(chgtype) ((chgtype & LDAP_ENTRYCHANGE_DEL) != 0)
+ #define PSEARCH_MOD(chgtype) ((chgtype & LDAP_ENTRYCHANGE_MOD) != 0)
+ #define PSEARCH_MODDN(chgtype) ((chgtype & LDAP_ENTRYCHANGE_MODDN) != 0)
++#define PSEARCH_ANY(chgtype) ((chgtype & LDAP_ENTRYCHANGE_ALL) != 0)
+ /*
+ * Creates persistent search (aka psearch,
+ * http://tools.ietf.org/id/draft-ietf-ldapext-psearch-03.txt) control.
+@@ -3036,9 +3038,11 @@ update_record(isc_task_t *task, isc_event_t *event)
+ cache = ldap_instance_getcache(inst);
+ CHECK(discard_from_cache(cache, &name));
+
+- if (PSEARCH_ADD(pevent->chgtype) || PSEARCH_MOD(pevent->chgtype)) {
++ if (PSEARCH_ADD(pevent->chgtype) || PSEARCH_MOD(pevent->chgtype) ||
++ !PSEARCH_ANY(pevent->chgtype)) {
+ /*
+- * Find new data in LDAP.
++ * Find new data in LDAP. !PSEARCH_ANY indicates unchanged entry
++ * found during initial lookup (i.e. database dump).
+ *
+ * @todo Change this to convert ldap_entry_t to ldapdb_rdatalist_t.
+ */
+@@ -3055,7 +3059,13 @@ update_record(isc_task_t *task, isc_event_t *event)
+ ldapdb_rdatalist_destroy(mctx, &rdatalist);
+ }
+
+- if (inst->serial_autoincrement) {
++ log_debug(20,"psearch change type: none%d, add%d, del%d, mod%d, moddn%d",
++ !PSEARCH_ANY(pevent->chgtype), PSEARCH_ADD(pevent->chgtype),
++ PSEARCH_DEL(pevent->chgtype), PSEARCH_MOD(pevent->chgtype),
++ PSEARCH_MODDN(pevent->chgtype));
++
++ /* Do not bump serial during initial database dump. */
++ if (inst->serial_autoincrement && PSEARCH_ANY(pevent->chgtype)) {
+ CHECK(soa_serial_increment(mctx, inst, &origin));
+ }
+ cleanup:
+@@ -3138,7 +3148,7 @@ psearch_update(ldap_instance_t *inst, ldap_entry_t *entry, LDAPControl **ctrls)
+ ldap_entryclass_t class;
+ isc_result_t result = ISC_R_SUCCESS;
+ ldap_psearchevent_t *pevent = NULL;
+- int chgtype = LDAP_ENTRYCHANGE_ADD;
++ int chgtype = LDAP_ENTRYCHANGE_NONE;
+ char *moddn = NULL;
+ char *dn = NULL;
+ char *dbname = NULL;
+--
+1.7.11.2
+
+
+From 9a3f29c12db99597222ffa2bf0713d0b00cb4699 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Mon, 2 Jul 2012 16:40:23 +0200
+Subject: [PATCH 13/27] Maintain SOA serial for zone record changes also. Bump
+ serial after each BIND startup. Manual changes to
+ zone serial are allowed.
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 67 ++++++++++++++++++++++++++++++++++++++---------------
+ src/zone_register.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++
+ src/zone_register.h | 6 +++++
+ 3 files changed, 113 insertions(+), 19 deletions(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index 7eb18cb..a50674b 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -265,6 +265,8 @@ static isc_result_t ldap_parse_rrentry(isc_mem_t *mctx, ldap_entry_t *entry,
+ ldap_connection_t *conn, dns_name_t *origin,
+ const ld_string_t *fake_mname, ld_string_t *buf,
+ ldapdb_rdatalist_t *rdatalist);
++static inline isc_result_t ldap_get_zone_serial(ldap_instance_t *inst,
++ dns_name_t *zone_name, isc_uint32_t *serial);
+
+ static isc_result_t ldap_connect(ldap_instance_t *ldap_inst,
+ ldap_connection_t *ldap_conn, isc_boolean_t force);
+@@ -291,6 +293,8 @@ static isc_result_t ldap_rdata_to_char_array(isc_mem_t *mctx,
+ static void free_char_array(isc_mem_t *mctx, char ***valsp);
+ static isc_result_t modify_ldap_common(dns_name_t *owner, ldap_instance_t *ldap_inst,
+ dns_rdatalist_t *rdlist, int mod_op, isc_boolean_t delete_node);
++static isc_result_t soa_serial_increment(isc_mem_t *mctx, ldap_instance_t *inst,
++ dns_name_t *zone_name);
+
+ static isc_result_t
+ ldap_delete_zone2(ldap_instance_t *inst, dns_name_t *name, isc_boolean_t lock);
+@@ -996,8 +1000,9 @@ ldap_parse_zoneentry(ldap_entry_t *entry, ldap_instance_t *inst)
+ isc_result_t result;
+ isc_boolean_t unlock = ISC_FALSE;
+ isc_boolean_t publish = ISC_FALSE;
+- isc_boolean_t load = ISC_FALSE;
+ isc_task_t *task = inst->task;
++ isc_uint32_t ldap_serial;
++ isc_uint32_t zr_serial;
+
+ dns_name_init(&name, NULL);
+
+@@ -1016,11 +1021,11 @@ ldap_parse_zoneentry(ldap_entry_t *entry, ldap_instance_t *inst)
+ * Fetch forwarders.
+ * Forwarding has top priority hence when the forwarders are properly
+ * set up all others attributes are ignored.
+- */
++ */
+ result = ldap_entry_getvalues(entry, "idnsForwarders", &values);
+ if (result == ISC_R_SUCCESS) {
+ log_debug(2, "Setting forwarders for %s", dn);
+- CHECK(configure_zone_forwarders(entry, inst, &name, &values));
++ CHECK(configure_zone_forwarders(entry, inst, &name, &values));
+ /* DO NOT CHANGE ANYTHING ELSE after forwarders are set up! */
+ goto cleanup;
+ } else {
+@@ -1076,17 +1081,41 @@ ldap_parse_zoneentry(ldap_entry_t *entry, ldap_instance_t *inst)
+ /* Everything is set correctly, publish zone */
+ CHECK(publish_zone(inst, zone));
+ }
+- load = ISC_TRUE;
+
+-cleanup:
+- if (load) {
+- /*
+- * Don't bother if load fails, server will return
+- * SERVFAIL for queries beneath this zone. This is
+- * admin's problem.
+- */
+- (void) dns_zone_load(zone);
++ /*
++ * Don't bother if load fails, server will return
++ * SERVFAIL for queries beneath this zone. This is
++ * admin's problem.
++ */
++ result = dns_zone_load(zone);
++ if (result != ISC_R_SUCCESS && result != DNS_R_UPTODATE
++ && result != DNS_R_DYNAMIC && result != DNS_R_CONTINUE)
++ goto cleanup;
++
++ result = ISC_R_SUCCESS;
++
++ /* initialize serial in zone register and always increment serial
++ * for a new zone (typically after BIND start)
++ * - the zone was possibly changed in meanwhile */
++ if (publish) {
++ CHECK(ldap_get_zone_serial(inst, &name, &ldap_serial));
++ CHECK(zr_set_zone_serial(inst->zone_register, &name, ldap_serial));
++ }
++
++ /* SOA serial autoincrement feature for SOA record:
++ * 1) remember old SOA serial from zone register
++ * 2) compare old and new SOA serial from LDAP DB
++ * 3) if (old == new) then do autoincrement, remember new serial otherwise */
++ if (inst->serial_autoincrement) {
++ CHECK(ldap_get_zone_serial(inst, &name, &ldap_serial));
++ CHECK(zr_get_zone_serial(inst->zone_register, &name, &zr_serial));
++ if (ldap_serial == zr_serial)
++ CHECK(soa_serial_increment(inst->mctx, inst, &name));
++ else
++ CHECK(zr_set_zone_serial(inst->zone_register, &name, ldap_serial));
+ }
++
++cleanup:
+ if (unlock)
+ isc_task_endexclusive(task);
+ if (dns_name_dynamic(&name))
+@@ -1094,7 +1123,7 @@ cleanup:
+ if (zone != NULL)
+ dns_zone_detach(&zone);
+
+- return ISC_R_SUCCESS;
++ return result;
+ }
+
+ /*
+@@ -2802,7 +2831,7 @@ ldap_pscontrol_destroy(isc_mem_t *mctx, LDAPControl **ctrlp)
+ }
+
+ static inline isc_result_t
+-soa_serial_get(ldap_instance_t *inst, dns_name_t *zone_name,
++ldap_get_zone_serial(ldap_instance_t *inst, dns_name_t *zone_name,
+ isc_uint32_t *serial) {
+ isc_result_t result;
+ dns_zone_t *zone = NULL;
+@@ -2815,7 +2844,7 @@ cleanup:
+ return result;
+ }
+
+-isc_result_t
++static isc_result_t
+ soa_serial_increment(isc_mem_t *mctx, ldap_instance_t *inst,
+ dns_name_t *zone_name) {
+ isc_result_t result = ISC_R_FAILURE;
+@@ -2841,7 +2870,7 @@ soa_serial_increment(isc_mem_t *mctx, ldap_instance_t *inst,
+ CHECK(ldapdb_rdatalist_get(mctx, inst, zone_name, zone_name, &rdatalist));
+ CHECK(ldapdb_rdatalist_findrdatatype(&rdatalist, dns_rdatatype_soa, &rdlist));
+ soa_rdata = ISC_LIST_HEAD(rdlist->rdata);
+- CHECK(soa_serial_get(inst, zone_name, &old_serial));
++ CHECK(ldap_get_zone_serial(inst, zone_name, &old_serial));
+
+ /* Compute the new SOA serial - use actual timestamp.
+ * If timestamp <= oldSOAserial then increment old serial by one. */
+@@ -2863,7 +2892,7 @@ soa_serial_increment(isc_mem_t *mctx, ldap_instance_t *inst,
+ CHECK(discard_from_cache(ldap_instance_getcache(inst), zone_name));
+
+ /* put the new SOA to inst->cache and compare old and new serials */
+- CHECK(soa_serial_get(inst, zone_name, &new_serial));
++ CHECK(ldap_get_zone_serial(inst, zone_name, &new_serial));
+ INSIST(isc_serial_gt(new_serial, old_serial) == ISC_TRUE);
+
+ cleanup:
+@@ -2930,9 +2959,9 @@ update_action(isc_task_t *task, isc_event_t *event)
+
+ cleanup:
+ if (result != ISC_R_SUCCESS)
+- log_error("update_action (psearch) failed for %s. "
++ log_error("update_action (psearch) failed for '%s': %s. "
+ "Zones can be outdated, run `rndc reload`",
+- pevent->dn);
++ pevent->dn, isc_result_totext(result));
+
+ ldap_query_free(ISC_FALSE, &ldap_qresult);
+ ldap_pool_putconnection(inst->pool, &conn);
+diff --git a/src/zone_register.c b/src/zone_register.c
+index 81d208f..1de6bf1 100644
+--- a/src/zone_register.c
++++ b/src/zone_register.c
+@@ -50,6 +50,7 @@ struct zone_register {
+ typedef struct {
+ dns_zone_t *zone;
+ char *dn;
++ isc_uint32_t serial; /* last value processed by plugin (!= value in DB) */
+ } zone_info_t;
+
+ /* Callback for dns_rbt_create(). */
+@@ -136,6 +137,7 @@ create_zone_info(isc_mem_t *mctx, dns_zone_t *zone, const char *dn,
+
+ CHECKED_MEM_GET_PTR(mctx, zinfo);
+ CHECKED_MEM_STRDUP(mctx, dn, zinfo->dn);
++ zinfo->serial = 0;
+ zinfo->zone = NULL;
+ dns_zone_attach(zone, &zinfo->zone);
+
+@@ -312,3 +314,60 @@ zr_get_zone_ptr(zone_register_t *zr, dns_name_t *name, dns_zone_t **zonep)
+
+ return result;
+ }
++
++/**
++ * Return last SOA serial value processed by autoincrement feature.
++ */
++isc_result_t
++zr_get_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t *serialp)
++{
++ isc_result_t result;
++ void *zinfo = NULL;
++
++ REQUIRE(zr != NULL);
++ REQUIRE(name != NULL);
++ REQUIRE(serialp != NULL);
++
++ if (!dns_name_isabsolute(name)) {
++ log_bug("trying to find zone with a relative name");
++ return ISC_R_FAILURE;
++ }
++
++ RWLOCK(&zr->rwlock, isc_rwlocktype_read);
++
++ result = dns_rbt_findname(zr->rbt, name, 0, NULL, &zinfo);
++ if (result == ISC_R_SUCCESS)
++ *serialp = ((zone_info_t *)zinfo)->serial;
++
++ RWUNLOCK(&zr->rwlock, isc_rwlocktype_read);
++
++ return result;
++}
++
++/**
++ * Set last SOA serial value processed by autoincrement feature.
++ */
++isc_result_t
++zr_set_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t serial)
++{
++ isc_result_t result;
++ void *zinfo = NULL;
++
++ REQUIRE(zr != NULL);
++ REQUIRE(name != NULL);
++
++ if (!dns_name_isabsolute(name)) {
++ log_bug("trying to find zone with a relative name");
++ return ISC_R_FAILURE;
++ }
++
++ RWLOCK(&zr->rwlock, isc_rwlocktype_read);
++
++ result = dns_rbt_findname(zr->rbt, name, 0, NULL, &zinfo);
++ if (result == ISC_R_SUCCESS)
++ ((zone_info_t *)zinfo)->serial = serial;
++
++ RWUNLOCK(&zr->rwlock, isc_rwlocktype_read);
++
++ return result;
++}
+diff --git a/src/zone_register.h b/src/zone_register.h
+index fa8ef25..6ac3a92 100644
+--- a/src/zone_register.h
++++ b/src/zone_register.h
+@@ -48,4 +48,10 @@ zr_get_rbt(zone_register_t *zr);
+ isc_mem_t *
+ zr_get_mctx(zone_register_t *zr);
+
++isc_result_t
++zr_set_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t serial);
++
++isc_result_t
++zr_get_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t *serialp);
++
+ #endif /* !_LD_ZONE_REGISTER_H_ */
+--
+1.7.11.2
+
+
+From c379d81508fbfa00ecb5da727ff7b097ebb29a3d Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Tue, 10 Jul 2012 14:23:46 +0200
+Subject: [PATCH 14/27] Add support for replicated environments to SOA serial
+ autoincrement feature. 389 DS sends entry change
+ notification even if modifyTimestamp was modified
+ because of replication from another DS. This code
+ computes digest from resource records in zone object
+ and compares these digests for each received entry
+ change notification. False entry change notifications
+ are revealed this way and no incrementation is done
+ in that case.
+
+http://fedorahosted.org/bind-dyndb-ldap/ticket/67
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_driver.c | 16 +------
+ src/ldap_helper.c | 47 ++++++++++++++-----
+ src/rdlist.c | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++-
+ src/rdlist.h | 13 ++++++
+ src/zone_register.c | 35 ++++++++++-----
+ src/zone_register.h | 6 ++-
+ 6 files changed, 204 insertions(+), 40 deletions(-)
+
+diff --git a/src/ldap_driver.c b/src/ldap_driver.c
+index a87db18..cae45d4 100644
+--- a/src/ldap_driver.c
++++ b/src/ldap_driver.c
+@@ -110,7 +110,6 @@ static void *ldapdb_version = &dummy;
+
+ static void free_ldapdb(ldapdb_t *ldapdb);
+ static void detachnode(dns_db_t *db, dns_dbnode_t **targetp);
+-static unsigned int rdatalist_length(const dns_rdatalist_t *rdlist);
+ static isc_result_t clone_rdatalist_to_rdataset(isc_mem_t *mctx,
+ dns_rdatalist_t *rdlist,
+ dns_rdataset_t *rdataset);
+@@ -545,6 +544,7 @@ found:
+ goto skipfind;
+
+ result = ldapdb_rdatalist_findrdatatype(&rdatalist, type, &rdlist);
++
+ if (result != ISC_R_SUCCESS) {
+ /* No exact rdtype match. Check CNAME */
+
+@@ -1006,20 +1006,6 @@ cleanup:
+ return result;
+ }
+
+-static unsigned int
+-rdatalist_length(const dns_rdatalist_t *rdlist)
+-{
+- dns_rdata_t *ptr = HEAD(rdlist->rdata);
+- unsigned int length = 0;
+-
+- while (ptr != NULL) {
+- length++;
+- ptr = NEXT(ptr, link);
+- }
+-
+- return length;
+-}
+-
+ static isc_result_t
+ subtractrdataset(dns_db_t *db, dns_dbnode_t *node, dns_dbversion_t *version,
+ dns_rdataset_t *rdataset, unsigned int options,
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index a50674b..0b1ed73 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -71,6 +71,7 @@
+ #include "ldap_entry.h"
+ #include "ldap_helper.h"
+ #include "log.h"
++#include "rdlist.h"
+ #include "semaphore.h"
+ #include "settings.h"
+ #include "str.h"
+@@ -1002,9 +1003,16 @@ ldap_parse_zoneentry(ldap_entry_t *entry, ldap_instance_t *inst)
+ isc_boolean_t publish = ISC_FALSE;
+ isc_task_t *task = inst->task;
+ isc_uint32_t ldap_serial;
+- isc_uint32_t zr_serial;
++ isc_uint32_t zr_serial; /* SOA serial value from in-memory zone register */
++ unsigned char ldap_digest[RDLIST_DIGESTLENGTH];
++ unsigned char *zr_digest = NULL;
++ ldapdb_rdatalist_t rdatalist;
++
++ REQUIRE(entry != NULL);
++ REQUIRE(inst != NULL);
+
+ dns_name_init(&name, NULL);
++ INIT_LIST(rdatalist);
+
+ /* Derive the dns name of the zone from the DN. */
+ dn = entry->dn;
+@@ -1098,21 +1106,39 @@ ldap_parse_zoneentry(ldap_entry_t *entry, ldap_instance_t *inst)
+ * for a new zone (typically after BIND start)
+ * - the zone was possibly changed in meanwhile */
+ if (publish) {
++ memset(ldap_digest, 0, RDLIST_DIGESTLENGTH);
+ CHECK(ldap_get_zone_serial(inst, &name, &ldap_serial));
+- CHECK(zr_set_zone_serial(inst->zone_register, &name, ldap_serial));
++ CHECK(zr_set_zone_serial_digest(inst->zone_register, &name, ldap_serial,
++ ldap_digest));
+ }
+
+ /* SOA serial autoincrement feature for SOA record:
+- * 1) remember old SOA serial from zone register
+- * 2) compare old and new SOA serial from LDAP DB
+- * 3) if (old == new) then do autoincrement, remember new serial otherwise */
++ * 1) Remember old (already processed) SOA serial and digest computed from
++ * zone root records in zone register.
++ * 2) After each change notification compare the old and new SOA serials
++ * and recomputed digests. If:
++ * 3a) Nothing was changed (false change notification received) - do nothing
++ * 3b) Serial was changed - remember the new serial and recompute digest,
++ * do not autoincrement (respect external change).
++ * 3c) The old and new serials are same: autoincrement only if something
++ * else was changed.
++ */
+ if (inst->serial_autoincrement) {
+ CHECK(ldap_get_zone_serial(inst, &name, &ldap_serial));
+- CHECK(zr_get_zone_serial(inst->zone_register, &name, &zr_serial));
+- if (ldap_serial == zr_serial)
+- CHECK(soa_serial_increment(inst->mctx, inst, &name));
+- else
+- CHECK(zr_set_zone_serial(inst->zone_register, &name, ldap_serial));
++ CHECK(ldapdb_rdatalist_get(inst->mctx, inst, &name,
++ &name, &rdatalist));
++ CHECK(rdatalist_digest(inst->mctx, &rdatalist, ldap_digest));
++
++ CHECK(zr_get_zone_serial_digest(inst->zone_register, &name, &zr_serial,
++ &zr_digest));
++ if (ldap_serial == zr_serial) {
++ /* serials are same - increment only if something was changed */
++ if (memcmp(zr_digest, ldap_digest, RDLIST_DIGESTLENGTH) != 0)
++ CHECK(soa_serial_increment(inst->mctx, inst, &name));
++ } else { /* serial in LDAP was changed - update zone register */
++ CHECK(zr_set_zone_serial_digest(inst->zone_register, &name,
++ ldap_serial, ldap_digest));
++ }
+ }
+
+ cleanup:
+@@ -1122,6 +1148,7 @@ cleanup:
+ dns_name_free(&name, inst->mctx);
+ if (zone != NULL)
+ dns_zone_detach(&zone);
++ ldapdb_rdatalist_destroy(inst->mctx, &rdatalist);
+
+ return result;
+ }
+diff --git a/src/rdlist.c b/src/rdlist.c
+index 903c948..a16de45 100644
+--- a/src/rdlist.c
++++ b/src/rdlist.c
+@@ -2,7 +2,7 @@
+ * Authors: Adam Tkac <atkac at redhat.com>
+ * Martin Nagy <mnagy at redhat.com>
+ *
+- * Copyright (C) 2009 Red Hat
++ * Copyright (C) 2009-2012 Red Hat
+ * see file 'COPYING' for use and warranty information
+ *
+ * This program is free software; you can redistribute it and/or
+@@ -22,17 +22,27 @@
+ #include <isc/mem.h>
+ #include <isc/result.h>
+ #include <isc/util.h>
++#include <isc/buffer.h>
++#include <isc/md5.h>
+
+ #include <dns/rdata.h>
+ #include <dns/rdatalist.h>
+
+ #include <string.h>
++#include <stdlib.h>
+
+ #include "ldap_helper.h" /* TODO: Move things from ldap_helper here? */
+ #include "rdlist.h"
+ #include "util.h"
+
+
++/* useful only for RR sorting purposes */
++typedef struct rr_sort rr_sort_t;
++struct rr_sort {
++ dns_rdatalist_t *rdatalist; /* contains RR class, type, TTL */
++ isc_region_t rdatareg; /* handle to binary area with RR data */
++};
++
+ static isc_result_t
+ rdata_clone(isc_mem_t *mctx, dns_rdata_t *source, dns_rdata_t **targetp)
+ {
+@@ -106,6 +116,121 @@ cleanup:
+ return result;
+ }
+
++unsigned int
++rdatalist_length(const dns_rdatalist_t *rdlist)
++{
++ dns_rdata_t *ptr = HEAD(rdlist->rdata);
++ unsigned int length = 0;
++
++ while (ptr != NULL) {
++ length++;
++ ptr = NEXT(ptr, link);
++ }
++
++ return length;
++}
++
++static int
++rr_sort_compare(const void *rdl1, const void *rdl2) {
++ const rr_sort_t *r1 = rdl1;
++ const rr_sort_t *r2 = rdl2;
++ int res;
++
++ res = r1->rdatalist->rdclass - r2->rdatalist->rdclass;
++ if (res != 0)
++ return res;
++
++ res = r1->rdatalist->type - r2->rdatalist->type;
++ if (res != 0)
++ return res;
++
++ res = r1->rdatalist->ttl - r2->rdatalist->ttl;
++ if (res != 0)
++ return res;
++
++ res = isc_region_compare((isc_region_t *)&r1->rdatareg,
++ (isc_region_t *)&r2->rdatareg);
++
++ return res;
++}
++
++/**
++ * Compute MD5 digest from all resource records in input rrdatalist.
++ * All RRs are sorted by class, type, ttl and data respectively. For this reason
++ * digest should be unambigous.
++ *
++ * @param rdlist[in] List of RRsets. Each RRset contains a list of individual RR
++ * @param digest[out] Pointer to unsigned char[RDLIST_DIGESTLENGTH] array
++ * @return ISC_R_SUCCESS and MD5 digest in unsigned char array "digest"
++ * In case of any error the array will stay untouched.
++ */
++isc_result_t
++rdatalist_digest(isc_mem_t *mctx, ldapdb_rdatalist_t *rdlist,
++ unsigned char *digest) {
++ isc_result_t result;
++ isc_buffer_t *rrs = NULL; /* array of all resource records from input rdlist */
++ unsigned int rrs_len = 0;
++ isc_md5_t md5ctx;
++
++ REQUIRE(rdlist != NULL);
++ REQUIRE(digest != NULL);
++
++ /* Compute count of RRs to avoid dynamic reallocations.
++ * The count is expected to be small number (< 20). */
++ for (dns_rdatalist_t *rrset = HEAD(*rdlist);
++ rrset != NULL;
++ rrset = NEXT(rrset, link)) {
++
++ rrs_len += rdatalist_length(rrset);
++ }
++ CHECK(isc_buffer_allocate(mctx, &rrs, rrs_len*sizeof(rr_sort_t)));
++
++ /* Fill each rr_sort structure in array rrs with pointer to RRset
++ * and coresponding data region from each RR. rrs array will be sorted. */
++ for (dns_rdatalist_t *rrset = HEAD(*rdlist);
++ rrset != NULL;
++ rrset = NEXT(rrset, link)) {
++
++ for (dns_rdata_t *rr = HEAD(rrset->rdata);
++ rr != NULL;
++ rr = NEXT(rr, link)) {
++
++ rr_sort_t rr_sort_rec;
++ rr_sort_rec.rdatalist = rrset;
++ dns_rdata_toregion(rr, &rr_sort_rec.rdatareg);
++
++ isc_buffer_putmem(rrs, (const unsigned char *)(&rr_sort_rec),
++ sizeof(rr_sort_t));
++ }
++ }
++ qsort(isc_buffer_base(rrs), rrs_len, sizeof(rr_sort_t), rr_sort_compare);
++
++ isc_md5_init(&md5ctx);
++ for (unsigned int i = 0; i < rrs_len; i++ ) {
++ rr_sort_t *rr_rec = (rr_sort_t *)isc_buffer_base(rrs) + i;
++ isc_md5_update(&md5ctx,
++ (const unsigned char *)&rr_rec->rdatalist->rdclass,
++ sizeof(rr_rec->rdatalist->rdclass));
++ isc_md5_update(&md5ctx,
++ (const unsigned char *)&rr_rec->rdatalist->type,
++ sizeof(rr_rec->rdatalist->type));
++ isc_md5_update(&md5ctx,
++ (const unsigned char *)&rr_rec->rdatalist->ttl,
++ sizeof(rr_rec->rdatalist->ttl));
++ isc_md5_update(&md5ctx,
++ (const unsigned char *)(rr_rec->rdatareg.base),
++ rr_rec->rdatareg.length);
++ }
++ isc_md5_final(&md5ctx, digest);
++ isc_md5_invalidate(&md5ctx);
++
++cleanup:
++ if (rrs != NULL)
++ isc_buffer_free(&rrs);
++
++ return result;
++}
++
+ isc_result_t
+ ldap_rdatalist_copy(isc_mem_t *mctx, ldapdb_rdatalist_t source,
+ ldapdb_rdatalist_t *target)
+diff --git a/src/rdlist.h b/src/rdlist.h
+index 04b9915..465197a 100644
+--- a/src/rdlist.h
++++ b/src/rdlist.h
+@@ -22,6 +22,12 @@
+ #ifndef _LD_RDLIST_H_
+ #define _LD_RDLIST_H_
+
++#include <isc/md5.h>
++
++#include "types.h"
++
++#define RDLIST_DIGESTLENGTH ISC_MD5_DIGESTLENGTH
++
+ isc_result_t
+ rdatalist_clone(isc_mem_t *mctx, dns_rdatalist_t *source,
+ dns_rdatalist_t **targetp);
+@@ -30,4 +36,11 @@ isc_result_t
+ ldap_rdatalist_copy(isc_mem_t *mctx, ldapdb_rdatalist_t source,
+ ldapdb_rdatalist_t *target);
+
++unsigned int
++rdatalist_length(const dns_rdatalist_t *rdlist);
++
++isc_result_t
++rdatalist_digest(isc_mem_t *mctx, ldapdb_rdatalist_t *rdlist,
++ unsigned char *digest);
++
+ #endif /* !_LD_RDLIST_H_ */
+diff --git a/src/zone_register.c b/src/zone_register.c
+index 1de6bf1..b2b938f 100644
+--- a/src/zone_register.c
++++ b/src/zone_register.c
+@@ -21,6 +21,7 @@
+ #include <isc/mem.h>
+ #include <isc/rwlock.h>
+ #include <isc/util.h>
++#include <isc/md5.h>
+
+ #include <dns/rbt.h>
+ #include <dns/result.h>
+@@ -31,6 +32,7 @@
+ #include "log.h"
+ #include "util.h"
+ #include "zone_register.h"
++#include "rdlist.h"
+
+ /*
+ * The zone register is a red-black tree that maps a dns name of a zone to the
+@@ -51,6 +53,7 @@ typedef struct {
+ dns_zone_t *zone;
+ char *dn;
+ isc_uint32_t serial; /* last value processed by plugin (!= value in DB) */
++ unsigned char digest[RDLIST_DIGESTLENGTH]; /* MD5 digest from all RRs in zone record */
+ } zone_info_t;
+
+ /* Callback for dns_rbt_create(). */
+@@ -316,17 +319,19 @@ zr_get_zone_ptr(zone_register_t *zr, dns_name_t *name, dns_zone_t **zonep)
+ }
+
+ /**
+- * Return last SOA serial value processed by autoincrement feature.
++ * Return last values processed by autoincrement feature.
+ */
+ isc_result_t
+-zr_get_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t *serialp)
++zr_get_zone_serial_digest(zone_register_t *zr, dns_name_t *name,
++ isc_uint32_t *serialp, unsigned char ** digestp)
+ {
+ isc_result_t result;
+- void *zinfo = NULL;
++ zone_info_t *zinfo = NULL;
+
+ REQUIRE(zr != NULL);
+ REQUIRE(name != NULL);
+ REQUIRE(serialp != NULL);
++ REQUIRE(digestp != NULL && *digestp == NULL);
+
+ if (!dns_name_isabsolute(name)) {
+ log_bug("trying to find zone with a relative name");
+@@ -335,9 +340,11 @@ zr_get_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t *serialp)
+
+ RWLOCK(&zr->rwlock, isc_rwlocktype_read);
+
+- result = dns_rbt_findname(zr->rbt, name, 0, NULL, &zinfo);
+- if (result == ISC_R_SUCCESS)
+- *serialp = ((zone_info_t *)zinfo)->serial;
++ result = dns_rbt_findname(zr->rbt, name, 0, NULL, (void *)&zinfo);
++ if (result == ISC_R_SUCCESS) {
++ *serialp = zinfo->serial;
++ *digestp = zinfo->digest;
++ }
+
+ RWUNLOCK(&zr->rwlock, isc_rwlocktype_read);
+
+@@ -345,16 +352,18 @@ zr_get_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t *serialp)
+ }
+
+ /**
+- * Set last SOA serial value processed by autoincrement feature.
++ * Set last SOA serial and digest from RRs processed by autoincrement feature.
+ */
+ isc_result_t
+-zr_set_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t serial)
++zr_set_zone_serial_digest(zone_register_t *zr, dns_name_t *name,
++ isc_uint32_t serial, unsigned char *digest)
+ {
+ isc_result_t result;
+- void *zinfo = NULL;
++ zone_info_t *zinfo = NULL;
+
+ REQUIRE(zr != NULL);
+ REQUIRE(name != NULL);
++ REQUIRE(digest != NULL);
+
+ if (!dns_name_isabsolute(name)) {
+ log_bug("trying to find zone with a relative name");
+@@ -363,9 +372,11 @@ zr_set_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t serial)
+
+ RWLOCK(&zr->rwlock, isc_rwlocktype_read);
+
+- result = dns_rbt_findname(zr->rbt, name, 0, NULL, &zinfo);
+- if (result == ISC_R_SUCCESS)
+- ((zone_info_t *)zinfo)->serial = serial;
++ result = dns_rbt_findname(zr->rbt, name, 0, NULL, (void *)&zinfo);
++ if (result == ISC_R_SUCCESS) {
++ zinfo->serial = serial;
++ memcpy(zinfo->digest, digest, RDLIST_DIGESTLENGTH);
++ }
+
+ RWUNLOCK(&zr->rwlock, isc_rwlocktype_read);
+
+diff --git a/src/zone_register.h b/src/zone_register.h
+index 6ac3a92..dea2c9d 100644
+--- a/src/zone_register.h
++++ b/src/zone_register.h
+@@ -49,9 +49,11 @@ isc_mem_t *
+ zr_get_mctx(zone_register_t *zr);
+
+ isc_result_t
+-zr_set_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t serial);
++zr_set_zone_serial_digest(zone_register_t *zr, dns_name_t *name,
++ isc_uint32_t serial, unsigned char *digest);
+
+ isc_result_t
+-zr_get_zone_serial(zone_register_t *zr, dns_name_t *name, isc_uint32_t *serialp);
++zr_get_zone_serial_digest(zone_register_t *zr, dns_name_t *name,
++ isc_uint32_t *serialp, unsigned char ** digestp);
+
+ #endif /* !_LD_ZONE_REGISTER_H_ */
+--
+1.7.11.2
+
+
+From 93ae7491a80ba8c4789f8770e14c053b67176de4 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Wed, 11 Jul 2012 15:04:50 +0200
+Subject: [PATCH 15/27] Add documention for serial_autoincrement feature.
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ README | 18 ++++++++++++++++++
+ 1 file changed, 18 insertions(+)
+
+diff --git a/README b/README
+index 08badc5..7539e76 100644
+--- a/README
++++ b/README
+@@ -221,6 +221,24 @@ ldap_hostname (default "")
+ is used and named service has Kerberos principal different from
+ /bin/hostname output.
+
++serial_autoincrement (default no)
++ Automatically increment SOA serial number after each change in LDAP.
++ External changes done by other LDAP clients are detected with
++ persistent search.
++
++ If serial number is lower than current UNIX timestamp, then
++ it is set to the timestamp value. If SOA serial is greater or equal
++ to current timestamp, then the serial is incremented by one.
++
++ In multi-master LDAP environments it is recommended to make
++ idnsSOAserial attribute non-replicated (locally significant).
++ It is recommended to not use multiple masters for single slave zone
++ if SOA serial is locally significant. Serial numbers between masters
++ aren't synchronized. It will cause problems with zone transfers from
++ multiple masters.
++
++ This function requires persistent search and 4 connections at least.
++
+ sync_ptr (default no)
+ Set this option to "yes" if you would like to keep PTR record
+ synchronized with coresponding A/AAAA record for all zones.
+--
+1.7.11.2
+
+
+From d673f5b54132a14798ec8a355be6cf4911fe10d1 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Wed, 11 Jul 2012 12:10:16 +0200
+Subject: [PATCH 16/27] Prevent doubled LDAP queries during nonexistent DNS
+ name lookups. This problem was introduced in commit
+ cd33194c5a61e98cba53212458cce02b849077ba
+ (CVE-2012-2134 fix).
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 7 ++++++-
+ 1 file changed, 6 insertions(+), 1 deletion(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index 0b1ed73..9ae3c80 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -1697,6 +1697,7 @@ ldap_query(ldap_instance_t *ldap_inst, ldap_connection_t *ldap_conn,
+ ldap_qresult_t *ldap_qresult = NULL;
+ int cnt;
+ int ret;
++ int ldap_err_code;
+ int once = 0;
+
+ REQUIRE(ldap_conn != NULL);
+@@ -1743,8 +1744,12 @@ retry:
+ return ISC_R_SUCCESS;
+ }
+
++ ret = ldap_get_option(ldap_conn->handle, LDAP_OPT_RESULT_CODE,
++ (void *)&ldap_err_code);
++ if (ret == LDAP_OPT_SUCCESS && ldap_err_code == LDAP_NO_SUCH_OBJECT)
++ return ISC_R_NOTFOUND;
+ /* some error happened during ldap_search, try to recover */
+- if (!once) {
++ else if (!once) {
+ once++;
+ result = handle_connection_error(ldap_inst, ldap_conn,
+ ISC_FALSE);
+--
+1.7.11.2
+
+
+From e44ce4d9c42ad9b1226cea5b62e9040f2d7e4df2 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Thu, 12 Jul 2012 17:10:58 +0200
+Subject: [PATCH 17/27] Prevent crashes in ldap_pool_*() function family.
+
+https://fedorahosted.org/bind-dyndb-ldap/ticket/84
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 18 +++++++++++-------
+ 1 file changed, 11 insertions(+), 7 deletions(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index 9ae3c80..8015db7 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -555,6 +555,7 @@ destroy_ldap_instance(ldap_instance_t **ldap_instp)
+
+ dns_rbtnodechain_invalidate(&chain);
+
++ /* TODO: Terminate psearch watcher sooner? */
+ if (ldap_inst->psearch && ldap_inst->watcher != 0) {
+ ldap_inst->exiting = ISC_TRUE;
+ /*
+@@ -645,9 +646,12 @@ destroy_ldap_connection(ldap_pool_t *pool, ldap_connection_t **ldap_connp)
+ {
+ ldap_connection_t *ldap_conn;
+
+- REQUIRE(ldap_connp != NULL && *ldap_connp != NULL);
++ REQUIRE(ldap_connp != NULL);
+
+ ldap_conn = *ldap_connp;
++ if (ldap_conn == NULL)
++ return;
++
+ DESTROYLOCK(&ldap_conn->lock);
+ if (ldap_conn->handle != NULL)
+ ldap_unbind_ext_s(ldap_conn->handle, NULL, NULL);
+@@ -2676,8 +2680,7 @@ ldap_pool_create(isc_mem_t *mctx, unsigned int connections, ldap_pool_t **poolp)
+ return ISC_R_SUCCESS;
+
+ cleanup:
+- if (pool != NULL)
+- ldap_pool_destroy(&pool);
++ ldap_pool_destroy(&pool);
+ return result;
+ }
+ static void
+@@ -2687,9 +2690,11 @@ ldap_pool_destroy(ldap_pool_t **poolp)
+ ldap_connection_t *ldap_conn;
+ unsigned int i;
+
+- REQUIRE(poolp != NULL && *poolp != NULL);
++ REQUIRE(poolp != NULL);
+
+ pool = *poolp;
++ if (pool == NULL)
++ return;
+
+ for (i = 0; i < pool->connections; i++) {
+ ldap_conn = pool->conns[i];
+@@ -2703,6 +2708,7 @@ ldap_pool_destroy(ldap_pool_t **poolp)
+ semaphore_destroy(&pool->conn_semaphore);
+
+ MEM_PUT_AND_DETACH(pool);
++ *poolp = NULL;
+ }
+
+ static isc_result_t
+@@ -2774,9 +2780,7 @@ ldap_pool_connect(ldap_pool_t *pool, ldap_instance_t *ldap_inst)
+
+ cleanup:
+ for (i = 0; i < pool->connections; i++) {
+- ldap_conn = pool->conns[i];
+- if (ldap_conn != NULL)
+- destroy_ldap_connection(pool, &ldap_conn);
++ destroy_ldap_connection(pool, &pool->conns[i]);
+ }
+ return result;
+ }
+--
+1.7.11.2
+
+
+From 640511903fb2cde66dfd759a14f2fab69554f48e Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Wed, 18 Jul 2012 14:32:48 +0200
+Subject: [PATCH 18/27] Add missing return value check to new_ldap_instance().
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index 8015db7..4fd5fa2 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -454,7 +454,8 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ } else {
+- str_sprintf(ldap_inst->krb5_principal, "DNS/%s", hostname);
++ CHECK(str_sprintf(ldap_inst->krb5_principal,
++ "DNS/%s", hostname));
+ log_debug(2, "SASL mech GSSAPI defined but krb5_principal"
+ "and sasl_user are empty, using default %s",
+ str_buf(ldap_inst->krb5_principal));
+--
+1.7.11.2
+
+
+From 0f27c0743ca0dcb6f1f4e8d2bd3e0b6157296e59 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Wed, 18 Jul 2012 13:39:12 +0200
+Subject: [PATCH 19/27] Raise connection count automatically if
+ serial_autoincrement is enabled.
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 6 ++++++
+ 1 file changed, 6 insertions(+)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index 4fd5fa2..f21c449 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -481,6 +481,12 @@ new_ldap_instance(isc_mem_t *mctx, const char *db_name,
+ result = ISC_R_FAILURE;
+ goto cleanup;
+ }
++ if (ldap_inst->serial_autoincrement == ISC_TRUE
++ && ldap_inst->connections < 4) {
++ log_error("serial_autoincrement needs at least 4 connections, "
++ "increasing limit");
++ ldap_inst->connections = 4;
++ }
+
+ CHECK(new_ldap_cache(mctx, argv, &ldap_inst->cache, ldap_inst->psearch));
+ CHECK(ldap_pool_create(mctx, ldap_inst->connections, &ldap_inst->pool));
+--
+1.7.11.2
+
+
+From 2d9e71d47997cd75635412cd81349692a8cac1c2 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Wed, 18 Jul 2012 13:01:28 +0200
+Subject: [PATCH 20/27] Add support for modify DN operation to persistent
+ search.
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 108 ++++++++++++++++++++++++++++++++++++++++++++----------
+ 1 file changed, 89 insertions(+), 19 deletions(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index f21c449..baf26b2 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -239,6 +239,7 @@ struct ldap_psearchevent {
+ isc_mem_t *mctx;
+ char *dbname;
+ char *dn;
++ char *prevdn;
+ int chgtype;
+ };
+
+@@ -316,6 +317,9 @@ static isc_result_t ldap_pscontrol_create(isc_mem_t *mctx, LDAPControl **ctrlp);
+ static void ldap_pscontrol_destroy(isc_mem_t *mctx, LDAPControl **ctrlp);
+
+ static isc_threadresult_t ldap_psearch_watcher(isc_threadarg_t arg);
++static void psearch_update(ldap_instance_t *inst, ldap_entry_t *entry,
++ LDAPControl **ctrls);
++
+
+ /* Persistent updates watcher */
+ static isc_threadresult_t
+@@ -796,6 +800,7 @@ ldap_delete_zone2(ldap_instance_t *inst, dns_name_t *name, isc_boolean_t lock)
+ if (result == ISC_R_SUCCESS)
+ unlock = ISC_TRUE;
+
++ /* TODO: flush cache records belonging to deleted zone */
+ CHECK(discard_from_cache(inst->cache, name));
+ }
+
+@@ -2964,18 +2969,25 @@ update_action(isc_task_t *task, isc_event_t *event)
+ isc_result_t result ;
+ ldap_instance_t *inst = NULL;
+ ldap_connection_t *conn = NULL;
+- ldap_qresult_t *ldap_qresult = NULL;
+- ldap_entry_t *entry;
++ ldap_qresult_t *ldap_qresult_zone = NULL;
++ ldap_qresult_t *ldap_qresult_record = NULL;
++ ldap_entry_t *entry_zone = NULL;
++ ldap_entry_t *entry_record = NULL;
+ isc_boolean_t delete = ISC_TRUE;
+ isc_mem_t *mctx;
+- char *attrs[] = {
++ dns_name_t prevname;
++ char *attrs_zone[] = {
+ "idnsName", "idnsUpdatePolicy", "idnsAllowQuery",
+ "idnsAllowTransfer", "idnsForwardPolicy", "idnsForwarders", NULL
+ };
++ char *attrs_record[] = {
++ "objectClass", "dn", NULL
++ };
+
+ UNUSED(task);
+
+ mctx = pevent->mctx;
++ dns_name_init(&prevname, NULL);
+
+ result = manager_get_ldap_instance(pevent->dbname, &inst);
+ /* TODO: Can it happen? */
+@@ -2983,18 +2995,43 @@ update_action(isc_task_t *task, isc_event_t *event)
+ goto cleanup;
+
+ CHECK(ldap_pool_getconnection(inst->pool, &conn));
+-
+- CHECK(ldap_query(inst, conn, &ldap_qresult, pevent->dn,
+- LDAP_SCOPE_BASE, attrs, 0,
++ CHECK(ldap_query(inst, conn, &ldap_qresult_zone, pevent->dn,
++ LDAP_SCOPE_BASE, attrs_zone, 0,
+ "(&(objectClass=idnsZone)(idnsZoneActive=TRUE))"));
+
+- for (entry = HEAD(ldap_qresult->ldap_entries);
+- entry != NULL;
+- entry = NEXT(entry, link)) {
++ for (entry_zone = HEAD(ldap_qresult_zone->ldap_entries);
++ entry_zone != NULL;
++ entry_zone = NEXT(entry_zone, link)) {
+ delete = ISC_FALSE;
+ result = ldap_parse_zoneentry(entry, inst);
+ if (result != ISC_R_SUCCESS)
+ goto cleanup;
++
++ if (PSEARCH_MODDN(pevent->chgtype)) {
++ if (dn_to_dnsname(inst->mctx, pevent->prevdn, &prevname, NULL)
++ == ISC_R_SUCCESS) {
++ CHECK(ldap_delete_zone(inst, pevent->prevdn, ISC_TRUE));
++ } else {
++ log_debug(5, "update_action: old zone wasn't managed "
++ "by plugin, dn '%s'", pevent->prevdn);
++ }
++
++ /* fill the cache with records from renamed zone */
++ CHECK(ldap_query(inst, conn, &ldap_qresult_record, pevent->dn,
++ LDAP_SCOPE_ONELEVEL, attrs_record, 0,
++ "(objectClass=idnsRecord)"));
++
++ /* LDAP schema requires SOA record (at least) */
++ INSIST(HEAD(ldap_qresult_record->ldap_entries) != NULL);
++ for (entry_record = HEAD(ldap_qresult_record->ldap_entries);
++ entry_record != NULL;
++ entry_record = NEXT(entry_record, link)) {
++
++ psearch_update(inst, entry_record, NULL);
++ }
++ }
++
++ INSIST(NEXT(entry_zone, link) == NULL); /* no multiple zones with same DN */
+ }
+
+ if (delete)
+@@ -3006,9 +3043,14 @@ cleanup:
+ "Zones can be outdated, run `rndc reload`",
+ pevent->dn, isc_result_totext(result));
+
+- ldap_query_free(ISC_FALSE, &ldap_qresult);
++ ldap_query_free(ISC_FALSE, &ldap_qresult_zone);
++ ldap_query_free(ISC_FALSE, &ldap_qresult_record);
+ ldap_pool_putconnection(inst->pool, &conn);
++ if (dns_name_dynamic(&prevname))
++ dns_name_free(&prevname, inst->mctx);
+ isc_mem_free(mctx, pevent->dbname);
++ if (pevent->prevdn != NULL)
++ isc_mem_free(mctx, pevent->prevdn);
+ isc_mem_free(mctx, pevent->dn);
+ isc_mem_detach(&mctx);
+ isc_event_free(&event);
+@@ -3097,8 +3139,12 @@ update_record(isc_task_t *task, isc_event_t *event)
+ /* Convert domain name from text to struct dns_name_t. */
+ dns_name_t name;
+ dns_name_t origin;
++ dns_name_t prevname;
++ dns_name_t prevorigin;
+ dns_name_init(&name, NULL);
+ dns_name_init(&origin, NULL);
++ dns_name_init(&prevname, NULL);
++ dns_name_init(&prevorigin, NULL);
+ CHECK(dn_to_dnsname(mctx, pevent->dn, &name, &origin));
+
+ if (PSEARCH_DEL(pevent->chgtype)) {
+@@ -3110,8 +3156,21 @@ update_record(isc_task_t *task, isc_event_t *event)
+ cache = ldap_instance_getcache(inst);
+ CHECK(discard_from_cache(cache, &name));
+
++ if (PSEARCH_MODDN(pevent->chgtype)) {
++ /* remove previous name only if it was inside DNS subtree */
++ if(dn_to_dnsname(mctx, pevent->prevdn, &prevname, &prevorigin)
++ == ISC_R_SUCCESS) {
++ log_debug(5, "psearch_update: removing name from cache, dn: '%s'",
++ pevent->prevdn);
++ CHECK(discard_from_cache(cache, &prevname));
++ } else {
++ log_debug(5, "psearch_update: old name wasn't managed "
++ "by plugin, dn '%s'", pevent->prevdn);
++ }
++ }
++
+ if (PSEARCH_ADD(pevent->chgtype) || PSEARCH_MOD(pevent->chgtype) ||
+- !PSEARCH_ANY(pevent->chgtype)) {
++ PSEARCH_MODDN(pevent->chgtype) || !PSEARCH_ANY(pevent->chgtype)) {
+ /*
+ * Find new data in LDAP. !PSEARCH_ANY indicates unchanged entry
+ * found during initial lookup (i.e. database dump).
+@@ -3148,9 +3207,15 @@ cleanup:
+
+ if (dns_name_dynamic(&name))
+ dns_name_free(&name, inst->mctx);
++ if (dns_name_dynamic(&prevname))
++ dns_name_free(&prevname, inst->mctx);
+ if (dns_name_dynamic(&origin))
+ dns_name_free(&origin, inst->mctx);
++ if (dns_name_dynamic(&prevorigin))
++ dns_name_free(&prevorigin, inst->mctx);
+ isc_mem_free(mctx, pevent->dbname);
++ if (pevent->prevdn != NULL)
++ isc_mem_free(mctx, pevent->prevdn);
+ isc_mem_free(mctx, pevent->dn);
+ isc_mem_detach(&mctx);
+ isc_event_free(&event);
+@@ -3221,8 +3286,9 @@ psearch_update(ldap_instance_t *inst, ldap_entry_t *entry, LDAPControl **ctrls)
+ isc_result_t result = ISC_R_SUCCESS;
+ ldap_psearchevent_t *pevent = NULL;
+ int chgtype = LDAP_ENTRYCHANGE_NONE;
+- char *moddn = NULL;
+ char *dn = NULL;
++ char *prevdn_ldap = NULL;
++ char *prevdn = NULL;
+ char *dbname = NULL;
+ isc_mem_t *mctx = NULL;
+ isc_taskaction_t action = NULL;
+@@ -3235,7 +3301,7 @@ psearch_update(ldap_instance_t *inst, ldap_entry_t *entry, LDAPControl **ctrls)
+ }
+
+ if (ctrls != NULL)
+- CHECK(ldap_parse_entrychangectrl(ctrls, &chgtype, &moddn));
++ CHECK(ldap_parse_entrychangectrl(ctrls, &chgtype, &prevdn_ldap));
+
+ isc_mem_attach(inst->mctx, &mctx);
+
+@@ -3250,11 +3316,12 @@ psearch_update(ldap_instance_t *inst, ldap_entry_t *entry, LDAPControl **ctrls)
+ goto cleanup;
+ }
+
+- /* TODO: Handle moddn case. */
+ if (PSEARCH_MODDN(chgtype)) {
+- log_error("psearch moddn change is not implemented");
+- result = ISC_R_FAILURE;
+- goto cleanup;
++ prevdn = isc_mem_strdup(mctx, prevdn_ldap);
++ if (prevdn == NULL) {
++ result = ISC_R_NOMEMORY;
++ goto cleanup;
++ }
+ }
+
+ /*
+@@ -3288,6 +3355,7 @@ psearch_update(ldap_instance_t *inst, ldap_entry_t *entry, LDAPControl **ctrls)
+ pevent->mctx = mctx;
+ pevent->dbname = dbname;
+ pevent->dn = dn;
++ pevent->prevdn = prevdn;
+ pevent->chgtype = chgtype;
+ isc_task_send(inst->task, (isc_event_t **)&pevent);
+
+@@ -3297,10 +3365,12 @@ cleanup:
+ isc_mem_free(mctx, dbname);
+ if (dn != NULL)
+ isc_mem_free(mctx, dn);
++ if (prevdn != NULL)
++ isc_mem_free(mctx, prevdn);
+ if (mctx != NULL)
+ isc_mem_detach(&mctx);
+- if (moddn != NULL)
+- ldap_memfree(moddn);
++ if (prevdn_ldap != NULL)
++ ldap_memfree(prevdn);
+
+ log_error("psearch_update failed for %s zone. "
+ "Zone can be outdated, run `rndc reload`",
+--
+1.7.11.2
+
+
+From 16c402e39e467731422b27a6247e0e222e36586c Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Wed, 18 Jul 2012 13:04:10 +0200
+Subject: [PATCH 21/27] Rename persistent search update_action() to
+ update_zone().
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 4 ++--
+ 1 file changed, 2 insertions(+), 2 deletions(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index baf26b2..c00869f 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -2963,7 +2963,7 @@ cleanup:
+ * operation but zones don't change often.
+ */
+ static void
+-update_action(isc_task_t *task, isc_event_t *event)
++update_zone(isc_task_t *task, isc_event_t *event)
+ {
+ ldap_psearchevent_t *pevent = (ldap_psearchevent_t *)event;
+ isc_result_t result ;
+@@ -3334,7 +3334,7 @@ psearch_update(ldap_instance_t *inst, ldap_entry_t *entry, LDAPControl **ctrls)
+ if ((class & LDAP_ENTRYCLASS_CONFIG) != 0)
+ action = update_config;
+ else if ((class & LDAP_ENTRYCLASS_ZONE) != 0)
+- action = update_action;
++ action = update_zone;
+ else if ((class & LDAP_ENTRYCLASS_RR) != 0)
+ action = update_record;
+ else {
+--
+1.7.11.2
+
+
+From 4083460acbdce1760aa347ec68abd27d25e1059a Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Wed, 18 Jul 2012 13:05:59 +0200
+Subject: [PATCH 22/27] Minor code cleanup in persistent search error
+ handling.
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 10 ++--------
+ 1 file changed, 2 insertions(+), 8 deletions(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index c00869f..5cfa1e1 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -2989,11 +2989,7 @@ update_zone(isc_task_t *task, isc_event_t *event)
+ mctx = pevent->mctx;
+ dns_name_init(&prevname, NULL);
+
+- result = manager_get_ldap_instance(pevent->dbname, &inst);
+- /* TODO: Can it happen? */
+- if (result != ISC_R_SUCCESS)
+- goto cleanup;
+-
++ CHECK(manager_get_ldap_instance(pevent->dbname, &inst));
+ CHECK(ldap_pool_getconnection(inst->pool, &conn));
+ CHECK(ldap_query(inst, conn, &ldap_qresult_zone, pevent->dn,
+ LDAP_SCOPE_BASE, attrs_zone, 0,
+@@ -3003,9 +2999,7 @@ update_zone(isc_task_t *task, isc_event_t *event)
+ entry_zone != NULL;
+ entry_zone = NEXT(entry_zone, link)) {
+ delete = ISC_FALSE;
+- result = ldap_parse_zoneentry(entry, inst);
+- if (result != ISC_R_SUCCESS)
+- goto cleanup;
++ CHECK(ldap_parse_zoneentry(entry_zone, inst));
+
+ if (PSEARCH_MODDN(pevent->chgtype)) {
+ if (dn_to_dnsname(inst->mctx, pevent->prevdn, &prevname, NULL)
+--
+1.7.11.2
+
+
+From 6f7fd9c9ed9b9c78c1034972f903e8d41de902a8 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Wed, 18 Jul 2012 13:27:16 +0200
+Subject: [PATCH 23/27] Minor persistent search logging cleanup.
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 21 +++++++++++----------
+ 1 file changed, 11 insertions(+), 10 deletions(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index 5cfa1e1..6ac76fa 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -3141,8 +3141,8 @@ update_record(isc_task_t *task, isc_event_t *event)
+ dns_name_init(&prevorigin, NULL);
+ CHECK(dn_to_dnsname(mctx, pevent->dn, &name, &origin));
+
+- if (PSEARCH_DEL(pevent->chgtype)) {
+- log_debug(5, "psearch_update: Removing item from cache (%s)",
++ if (PSEARCH_DEL(pevent->chgtype) || PSEARCH_MODDN(pevent->chgtype)) {
++ log_debug(5, "psearch_update: removing name from cache, dn: '%s'",
+ pevent->dn);
+ }
+
+@@ -3171,7 +3171,7 @@ update_record(isc_task_t *task, isc_event_t *event)
+ *
+ * @todo Change this to convert ldap_entry_t to ldapdb_rdatalist_t.
+ */
+- log_debug(5, "psearch_update: Updating item in cache (%s)",
++ log_debug(5, "psearch_update: updating name in cache, dn: '%s'",
+ pevent->dn);
+ CHECK(ldapdb_rdatalist_get(mctx, inst, &name, &origin, &rdatalist));
+
+@@ -3184,18 +3184,13 @@ update_record(isc_task_t *task, isc_event_t *event)
+ ldapdb_rdatalist_destroy(mctx, &rdatalist);
+ }
+
+- log_debug(20,"psearch change type: none%d, add%d, del%d, mod%d, moddn%d",
+- !PSEARCH_ANY(pevent->chgtype), PSEARCH_ADD(pevent->chgtype),
+- PSEARCH_DEL(pevent->chgtype), PSEARCH_MOD(pevent->chgtype),
+- PSEARCH_MODDN(pevent->chgtype));
+-
+ /* Do not bump serial during initial database dump. */
+ if (inst->serial_autoincrement && PSEARCH_ANY(pevent->chgtype)) {
+ CHECK(soa_serial_increment(mctx, inst, &origin));
+ }
+ cleanup:
+ if (result != ISC_R_SUCCESS)
+- log_error("update_record (psearch) failed for %s. "
++ log_error("update_record (psearch) failed, dn '%s'. "
+ "Records can be outdated, run `rndc reload`",
+ pevent->dn);
+
+@@ -3289,7 +3284,7 @@ psearch_update(ldap_instance_t *inst, ldap_entry_t *entry, LDAPControl **ctrls)
+
+ class = ldap_entry_getclass(entry);
+ if (class == LDAP_ENTRYCLASS_NONE) {
+- log_error("psearch_update: ignoring unknown entry [dn %s]",
++ log_error("psearch_update: ignoring entry with unknown class, dn '%s'",
+ entry->dn);
+ return; /* ignore it, it's OK */
+ }
+@@ -3297,6 +3292,12 @@ psearch_update(ldap_instance_t *inst, ldap_entry_t *entry, LDAPControl **ctrls)
+ if (ctrls != NULL)
+ CHECK(ldap_parse_entrychangectrl(ctrls, &chgtype, &prevdn_ldap));
+
++
++ log_debug(20,"psearch change type: none%d, add%d, del%d, mod%d, moddn%d",
++ !PSEARCH_ANY(chgtype), PSEARCH_ADD(chgtype),
++ PSEARCH_DEL(chgtype), PSEARCH_MOD(chgtype),
++ PSEARCH_MODDN(chgtype));
++
+ isc_mem_attach(inst->mctx, &mctx);
+
+ dn = isc_mem_strdup(mctx, entry->dn);
+--
+1.7.11.2
+
+
+From 77c06ea1910a9737bf7e2d9f5c53eeb83827c332 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Fri, 20 Jul 2012 14:18:41 +0200
+Subject: [PATCH 24/27] Fix two memory leaks in ldap_query().
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 14 ++++++++------
+ 1 file changed, 8 insertions(+), 6 deletions(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index 6ac76fa..daffac7 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -1753,19 +1753,21 @@ retry:
+ &ldap_qresult->ldap_entries);
+ if (result != ISC_R_SUCCESS) {
+ log_error("failed to save LDAP query results");
+- return result;
++ goto cleanup;
+ }
+
+ *ldap_qresultp = ldap_qresult;
+ return ISC_R_SUCCESS;
++ } else {
++ result = ISC_R_FAILURE;
+ }
+
+ ret = ldap_get_option(ldap_conn->handle, LDAP_OPT_RESULT_CODE,
+ (void *)&ldap_err_code);
+- if (ret == LDAP_OPT_SUCCESS && ldap_err_code == LDAP_NO_SUCH_OBJECT)
+- return ISC_R_NOTFOUND;
+- /* some error happened during ldap_search, try to recover */
+- else if (!once) {
++ if (ret == LDAP_OPT_SUCCESS && ldap_err_code == LDAP_NO_SUCH_OBJECT) {
++ result = ISC_R_NOTFOUND;
++ } else if (!once) {
++ /* some error happened during ldap_search, try to recover */
+ once++;
+ result = handle_connection_error(ldap_inst, ldap_conn,
+ ISC_FALSE);
+@@ -1774,7 +1776,7 @@ retry:
+ }
+ cleanup:
+ ldap_query_free(ISC_FALSE, &ldap_qresult);
+- return ISC_R_FAILURE;
++ return result;
+ }
+
+ /**
+--
+1.7.11.2
+
+
+From 85763ded13a2c2a641da4a9bbf0950170a6aecf8 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Wed, 25 Jul 2012 10:07:20 +0200
+Subject: [PATCH 25/27] Handle incomplete/invalid zone unload in same way as
+ ns_server_del_zone().
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_helper.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/ldap_helper.c b/src/ldap_helper.c
+index daffac7..cc7003a 100644
+--- a/src/ldap_helper.c
++++ b/src/ldap_helper.c
+@@ -823,7 +823,7 @@ ldap_delete_zone2(ldap_instance_t *inst, dns_name_t *name, isc_boolean_t lock)
+
+ /* Do not unload partially loaded zones, they have incomplete structures. */
+ dns_db_t *dbp = NULL;
+- if (dns_zone_getdb(zone, &dbp) != DNS_R_NOTLOADED) {
++ if (dns_zone_getdb(zone, &dbp) == ISC_R_SUCCESS) {
+ dns_db_detach(&dbp); /* dns_zone_getdb() attaches DB implicitly */
+ dns_zone_unload(zone);
+ }
+--
+1.7.11.2
+
+
+From b04dfcbe328a8e713597921f7a43c9c8dd801e63 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Thu, 19 Jul 2012 14:13:12 +0200
+Subject: [PATCH 26/27] Cleanup in logging code.
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/log.c | 22 ++--------------------
+ src/log.h | 19 ++++++++++++++++---
+ 2 files changed, 18 insertions(+), 23 deletions(-)
+
+diff --git a/src/log.c b/src/log.c
+index b23e472..f731df7 100644
+--- a/src/log.c
++++ b/src/log.c
+@@ -28,31 +28,13 @@
+ #include "log.h"
+
+ void
+-log_debug(int level, const char *format, ...)
++log_write(int level, const char *format, ...)
+ {
+ va_list args;
+
+ va_start(args, format);
+-#ifdef LOG_AS_ERROR
+- UNUSED(level);
+ isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB,
+- ISC_LOG_ERROR, format, args);
+-#else
+- isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB,
+- ISC_LOG_DEBUG(level), format, args);
+-#endif
+-
+- va_end(args);
+-}
+-
+-void
+-log_error(const char *format, ...)
+-{
+- va_list args;
+-
+- va_start(args, format);
+- isc_log_vwrite(dns_lctx, DNS_LOGCATEGORY_DATABASE, DNS_LOGMODULE_DYNDB,
+- ISC_LOG_ERROR, format, args);
++ level, format, args);
+ va_end(args);
+
+ }
+diff --git a/src/log.h b/src/log.h
+index 0df4e25..898639b 100644
+--- a/src/log.h
++++ b/src/log.h
+@@ -22,6 +22,13 @@
+ #define _LD_LOG_H_
+
+ #include <isc/error.h>
++#include <dns/log.h>
++
++#ifdef LOG_AS_ERROR
++#define GET_LOG_LEVEL(level) ISC_LOG_ERROR
++#else
++#define GET_LOG_LEVEL(level) (level)
++#endif
+
+ #define fatal_error(...) \
+ isc_error_fatal(__FILE__, __LINE__, __VA_ARGS__)
+@@ -30,10 +37,16 @@
+ log_error("bug in %s(): " fmt, __func__,##__VA_ARGS__)
+
+ #define log_error_r(fmt, ...) \
+- log_error(fmt ": %s", ##__VA_ARGS__, isc_result_totext(result))
++ log_error(fmt ": %s", ##__VA_ARGS__, dns_result_totext(result))
+
+ /* Basic logging functions */
+-void log_debug(int level, const char *format, ...) ISC_FORMAT_PRINTF(2, 3);
+-void log_error(const char *format, ...) ISC_FORMAT_PRINTF(1, 2);
++#define log_error(format, ...) \
++ log_write(GET_LOG_LEVEL(ISC_LOG_ERROR), format, ##__VA_ARGS__)
++
++#define log_debug(level, format, ...) \
++ log_write(GET_LOG_LEVEL(level), format, ##__VA_ARGS__)
++
++void
++log_write(int level, const char *format, ...) ISC_FORMAT_PRINTF(2, 3);
+
+ #endif /* !_LD_LOG_H_ */
+--
+1.7.11.2
+
+
+From f345805c73c294db42452ae966c48fbc36c48006 Mon Sep 17 00:00:00 2001
+From: Petr Spacek <pspacek at redhat.com>
+Date: Fri, 20 Jul 2012 14:55:43 +0200
+Subject: [PATCH 27/27] Fix and harden DNS-to-LDAP name conversion. Fixes
+ CVE-2012-3429.
+
+Signed-off-by: Petr Spacek <pspacek at redhat.com>
+---
+ src/ldap_convert.c | 44 +++++++++++++++++++++++++++++++++-----------
+ 1 file changed, 33 insertions(+), 11 deletions(-)
+
+diff --git a/src/ldap_convert.c b/src/ldap_convert.c
+index 6b4e321..3352c57 100644
+--- a/src/ldap_convert.c
++++ b/src/ldap_convert.c
+@@ -192,16 +192,23 @@ cleanup:
+ }
+
+ /**
++ * WARNING! This function is used to mangle input from network
++ * and it is security sensitive.
++ *
+ * Convert a string from DNS escaping to LDAP escaping.
+ * The Input string dns_str is expected to be the result of dns_name_tostring().
+ * The DNS label can contain any binary data as described in
+ * http://tools.ietf.org/html/rfc2181#section-11 .
+ *
+- * DNS escaping uses form "\123" = ASCII value 123 (decimal)
++ * DNS escaping uses 2 forms: (see dns_name_totext2() in bind/lib/dns/name.c)
++ * form "\123" = ASCII value 123 (decimal)
++ * form "\$" = character '$' is escaped with '\'
++ * WARNING! Some characters are not escaped at all (e.g. ',').
++ *
+ * LDAP escaping users form "\7b" = ASCII value 7b (hexadecimal)
+ *
+- * Input (DNS escaped) example : _aaa,bbb\255\000ccc.555.ddd-eee
+- * Output (LDAP escaped) example: _aaa\2cbbb\ff\00ccc.555.ddd-eee
++ * Input (DNS escaped) example: \$.\255_aaa,bbb\127\000ccc.555.ddd-eee
++ * Output (LDAP escaped) example: \24.\ff_aaa\2cbbb\7f\00ccc.555.ddd-eee
+ *
+ * The DNS to text functions from ISC libraries do not convert certain
+ * characters (e.g. ","). This function converts \123 form to \7b form in all
+@@ -248,13 +255,23 @@ dns_to_ldap_dn_escape(isc_mem_t *mctx, const char const * dns_str, char ** ldap_
+ }
+ if (dns_str[dns_idx] != '\\') { /* not nice raw value, e.g. ',' */
+ ascii_val = dns_str[dns_idx];
+- } else { /* not nice value in DNS \123 decimal format */
+- /* check if input length <= expected size */
+- REQUIRE (dns_str_len > dns_idx + 3); /* this problem should never happen */
+- ascii_val = 100 * (dns_str[dns_idx + 1] - '0')
+- + 10 * (dns_str[dns_idx + 2] - '0')
+- + (dns_str[dns_idx + 3] - '0');
+- dns_idx += 3;
++ } else { /* DNS escaped value, it starts with '\' */
++ if (!(dns_idx + 1 < dns_str_len)) {
++ CHECK(DNS_R_BADESCAPE); /* this problem should never happen */
++ }
++ if (isdigit(dns_str[dns_idx + 1])) { /* \123 decimal format */
++ /* check if input length <= expected size */
++ if (!(dns_idx + 3 < dns_str_len)) {
++ CHECK(DNS_R_BADESCAPE); /* this problem should never happen */
++ }
++ ascii_val = 100 * (dns_str[dns_idx + 1] - '0')
++ + 10 * (dns_str[dns_idx + 2] - '0')
++ + (dns_str[dns_idx + 3] - '0');
++ dns_idx += 3;
++ } else { /* \$ single char format */
++ ascii_val = dns_str[dns_idx + 1];
++ dns_idx += 1;
++ }
+ }
+ /* LDAP uses \xy escaping. "xy" represent two hexadecimal digits.*/
+ /* TODO: optimize to bit mask & rotate & dec->hex table? */
+@@ -272,8 +289,13 @@ dns_to_ldap_dn_escape(isc_mem_t *mctx, const char const * dns_str, char ** ldap_
+ return ISC_R_SUCCESS;
+
+ cleanup:
+- if (*ldap_name)
++ if (result == DNS_R_BADESCAPE)
++ log_bug("improperly escaped DNS string: '%s'", dns_str);
++
++ if (*ldap_name) {
+ isc_mem_free(mctx, *ldap_name);
++ *ldap_name = NULL;
++ }
+ return result;
+ }
+
+--
+1.7.11.2
More information about the scm-commits
mailing list