Hello,

these patches provides SOA serial auto-increment feature for external changes.
Related ticket: https://fedorahosted.org/bind-dyndb-ldap/ticket/67

It is necessary to set "psearch" AND "serial_autoincrement" to "yes" in /etc/named.conf to enable this feature.

In replicated environment idnsSOAserial attribute has to be declared as non-replicated. It is done by mkosek's patch 281 for 389 DS & FreeIPA.

For testing purposes it is enough to add "idnsSOAserial" to end of exclude list in nsDS5ReplicatedAttributeList attribute for each replication agreement located in cn=mapping tree,cn=config subtree.


My patch 28 contains "trick" necessary for replicated environments with 389 DS. 389 sends entry change notification (ECN) in cases when non-replicated attribute idnsSOAserial was changed on *other side*. In that case no change is visible in DNS attributes, but ECN is sent by 389. (Attribute modifyTimestamp is changed also.)

Patch 28 computes digest/hash from all resource records in idnsZone object and compares old and new digest after each received ECN. This approach eliminates "false changes".

Each patch depends on all preceding patches, but each patch implements visible (and testable) part of functionality.

Petr^2 Spacek
From 0ed1d3dd4910e1c94617b0209420b1c6598de68e Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Wed, 27 Jun 2012 10:36:26 +0200
Subject: [PATCH] Increment SOA serial for each ordinary record received
 through psearch

Signed-off-by: Petr Spacek <pspa...@redhat.com>
---
 src/ldap_helper.c |   98 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 files changed, 97 insertions(+), 1 deletions(-)

diff --git a/src/ldap_helper.c b/src/ldap_helper.c
index 7f0a6f4b37171a6fa4db79cd32fdd8bc62288e0f..5c509443dfb176607d8ee4714d196efaa4afa66b 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>
@@ -172,6 +174,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));
@@ -2741,6 +2753,86 @@ ldap_pscontrol_destroy(isc_mem_t *mctx, LDAPControl **ctrlp)
 	*ctrlp = NULL;
 }
 
+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;
+
+	REQUIRE(inst != NULL);
+	REQUIRE(zone_name != NULL);
+	REQUIRE(serial != 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);
+
+	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 */
+	INIT_LIST(rdatalist);
+	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
@@ -2892,7 +2984,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);
@@ -2916,6 +3008,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.7.6

From b82c951ad09c72e524be93ba06b0f46897c85f71 Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Mon, 2 Jul 2012 11:01:58 +0200
Subject: [PATCH] Do not bump serial for each record during initial database
 dump.

Signed-off-by: Petr Spacek <pspa...@redhat.com>
---
 src/ldap_helper.c |   18 ++++++++++++++----
 1 files changed, 14 insertions(+), 4 deletions(-)

diff --git a/src/ldap_helper.c b/src/ldap_helper.c
index 5c509443dfb176607d8ee4714d196efaa4afa66b..d744642364ba70919f52abd5ea31998f8e91ccd4 100644
--- a/src/ldap_helper.c
+++ b/src/ldap_helper.c
@@ -2677,6 +2677,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
@@ -2687,6 +2688,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.
@@ -2990,9 +2992,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.
 		 */
@@ -3009,7 +3013,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:
@@ -3092,7 +3102,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.7.6

From 9e85fd45baff2dd6fb696ce416af8ae8a8ecccbe Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Mon, 2 Jul 2012 16:40:23 +0200
Subject: [PATCH] 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 <pspa...@redhat.com>
---
 src/ldap_helper.c   |   61 ++++++++++++++++++++++++++++++++++++---------------
 src/ldap_helper.h   |    6 +++++
 src/zone_register.c |   59 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/zone_register.h |    6 +++++
 4 files changed, 114 insertions(+), 18 deletions(-)

diff --git a/src/ldap_helper.c b/src/ldap_helper.c
index d744642364ba70919f52abd5ea31998f8e91ccd4..da6f8562ef59dadc8882743ee87bf84305a1de82 100644
--- a/src/ldap_helper.c
+++ b/src/ldap_helper.c
@@ -1000,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);
 
@@ -1020,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 {
@@ -1080,25 +1081,49 @@ 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;
+
+	/*
+	 * 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 (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);
-	}
 	if (unlock)
 		isc_task_endexclusive(task);
 	if (dns_name_dynamic(&name))
 		dns_name_free(&name, inst->mctx);
 	if (zone != NULL)
 		dns_zone_detach(&zone);
 
-	return ISC_R_SUCCESS;
+	return result;
 }
 
 /*
@@ -2756,7 +2781,7 @@ ldap_pscontrol_destroy(isc_mem_t *mctx, LDAPControl **ctrlp)
 }
 
 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;
@@ -2799,7 +2824,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. */
@@ -2821,7 +2846,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:
@@ -2887,9 +2912,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_pool_putconnection(inst->pool, &conn);
 	isc_mem_free(mctx, pevent->dbname);
diff --git a/src/ldap_helper.h b/src/ldap_helper.h
index bc784106abefe15787841578687469539c8052c3..b6e46e307fa7e2f0af1b71600fcabdb0c13c5649 100644
--- a/src/ldap_helper.h
+++ b/src/ldap_helper.h
@@ -93,4 +93,10 @@ isc_result_t remove_from_ldap(dns_name_t *owner, ldap_instance_t *ldap_inst,
 /* Get cache associated with ldap_inst */
 ldap_cache_t *ldap_instance_getcache(ldap_instance_t *ldap_inst);
 
+isc_result_t ldap_get_zone_serial(ldap_instance_t *inst, dns_name_t *zone_name,
+		isc_uint32_t *serial);
+
+isc_result_t soa_serial_increment(isc_mem_t *mctx, ldap_instance_t *inst,
+		dns_name_t *zone_name);
+
 #endif /* !_LD_LDAP_HELPER_H_ */
diff --git a/src/zone_register.c b/src/zone_register.c
index 81d208fc6e3c0dba6efb72ae10db54a79c336eca..1de6bf1ac121dc02c54f187cd36f8b588ee14d18 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 fa8ef255ef9cf212bca04aaafba0e6450d40a5c6..6ac3a92fbe755fcff2d9af3e6e26b32ae83d4133 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.7.6

From 6918535ee7abd3121db7f31c0c7ecba6a5e847cf Mon Sep 17 00:00:00 2001
From: Petr Spacek <pspa...@redhat.com>
Date: Tue, 10 Jul 2012 14:23:46 +0200
Subject: [PATCH] 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 <pspa...@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 a87db180b0aa72c97aa51dd178ecdab7b90a0afd..cae45d4f6cc1f201c40ca3413d1f626e03a0318e 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 da6f8562ef59dadc8882743ee87bf84305a1de82..323ee81e70bd3e4e0ffd59a024f047bbcbde9d89 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,30 +1106,49 @@ 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:
 	if (unlock)
 		isc_task_endexclusive(task);
 	if (dns_name_dynamic(&name))
 		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 903c948f401a9bd82cbc0eb06ac55aa05452c976..a16de45bcc49d00a1eacf42b23c87f24be8d3b2f 100644
--- a/src/rdlist.c
+++ b/src/rdlist.c
@@ -2,7 +2,7 @@
  * Authors: Adam Tkac   <at...@redhat.com>
  *          Martin Nagy <mn...@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 04b991508e47ae320df32a60adeb5384e68951ae..465197a63933f2ec8fa1835bf0b34eeabf108d1b 100644
--- a/src/rdlist.h
+++ b/src/rdlist.h
@@ -22,12 +22,25 @@
 #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);
 
 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 1de6bf1ac121dc02c54f187cd36f8b588ee14d18..b2b938f3336b23a40d43c85062c9389a2190f3cb 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,56 +319,64 @@ 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");
 		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;
+	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);
 
 	return result;
 }
 
 /**
- * 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");
 		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;
+	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 6ac3a92fbe755fcff2d9af3e6e26b32ae83d4133..dea2c9dce054daf1764ba31154627419acada27d 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.7.6

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to