[PATCH] DNS scavenging in the AD DC

Andrew Bartlett abartlet at samba.org
Tue Jul 10 05:21:47 UTC 2018


Attached here is the latest iteration of Aaron's DNS scavenging patch
series, as cleaned up by Gary and myself.

While there is much more I would like to see done (dbcheck rules to
handle the existing records, command-line tools to change the
settings), at this time this is a useful improvement and finally
creates static and dynamic records correctly.

Aside from the WHATSNEW it is reviewed by Gary and myself, and the
tests have been run against Windows and the windows static record
behaviour has been clarified by Microsoft. 

https://gitlab.com/samba-team/samba/merge_requests/26

CI: https://gitlab.com/catalyst-samba/samba/pipelines/25444977

Please review and push!

Thanks,

Andrew Bartlett
-- 
Andrew Bartlett
https://samba.org/~abartlet/
Authentication Developer, Samba Team         https://samba.org
Samba Development and Support, Catalyst IT   
https://catalyst.net.nz/services/samba



-------------- next part --------------
From 1305f4d88c1fb85680b8a264c06bdc408025c393 Mon Sep 17 00:00:00 2001
From: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Date: Wed, 9 May 2018 18:02:28 +1200
Subject: [PATCH 01/18] dns: record aging tests

First basic DNS record aging tests.  These check that we can
turn aging on and off, and that timestamps are written on DNS
add and update calls, but not RPC calls.

Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/tests/dns.py           | 212 ++++++++++++++++++++++++++++++++++--
 python/samba/tests/dns_base.py      |  10 +-
 selftest/knownfail.d/dns            |  16 +++
 selftest/knownfail.d/dns-scavenging |  12 ++
 4 files changed, 236 insertions(+), 14 deletions(-)
 create mode 100644 selftest/knownfail.d/dns-scavenging

diff --git a/python/samba/tests/dns.py b/python/samba/tests/dns.py
index 4082b5375d1..083dc460afd 100644
--- a/python/samba/tests/dns.py
+++ b/python/samba/tests/dns.py
@@ -16,6 +16,12 @@
 #
 
 from __future__ import print_function
+
+from samba import dsdb
+from samba.ndr import ndr_unpack, ndr_pack
+from samba.samdb import SamDB
+from samba.auth import system_session
+import ldb
 import os
 import sys
 import struct
@@ -652,11 +658,9 @@ class TestComplexQueries(DNSTest):
         r.rr_class = dns.DNS_QCLASS_IN
         r.ttl = 900
         r.length = 0xffff
-        rdata = value
-        r.rdata = rdata
-        updates = [r]
+        r.rdata = value
         p.nscount = 1
-        p.nsrecs = updates
+        p.nsrecs = [r]
         (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
@@ -884,11 +888,22 @@ class TestZones(DNSTest):
         self.timeout = timeout
 
         self.zone = "test.lan"
-        self.rpc_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[sign]" % (self.server_ip),
+        self.rpc_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[sign]" %\
+                                            (self.server_ip),
                                             self.lp, self.creds)
 
+        self.samdb = SamDB(url="ldap://" + self.server_ip,
+                           lp = self.get_loadparm(),
+                           session_info=system_session(),
+                           credentials=self.creds)
+
+        self.zone_dn = "DC=" + self.zone +\
+                       ",CN=MicrosoftDNS,DC=DomainDNSZones," +\
+                       str(self.samdb.get_default_basedn())
+
     def tearDown(self):
         super(TestZones, self).tearDown()
+
         try:
             self.delete_zone(self.zone)
         except RuntimeError as e:
@@ -896,15 +911,18 @@ class TestZones(DNSTest):
             if num != werror.WERR_DNS_ERROR_ZONE_DOES_NOT_EXIST:
                 raise
 
-    def create_zone(self, zone):
+    def create_zone(self, zone, aging_enabled=False):
         zone_create = dnsserver.DNS_RPC_ZONE_CREATE_INFO_LONGHORN()
         zone_create.pszZoneName = zone
         zone_create.dwZoneType = dnsp.DNS_ZONE_TYPE_PRIMARY
-        zone_create.fAllowUpdate = dnsp.DNS_ZONE_UPDATE_SECURE
-        zone_create.fAging = 0
+        zone_create.fAging = int(aging_enabled)
         zone_create.dwDpFlags = dnsserver.DNS_DP_DOMAIN_DEFAULT
+        zone_create.fDsIntegrated = 1
+        zone_create.fLoadExisting = 1
+        zone_create.fAllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE
         try:
-            self.rpc_conn.DnssrvOperation2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+            client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
+            self.rpc_conn.DnssrvOperation2(client_version,
                                            0,
                                            self.server_ip,
                                            None,
@@ -915,6 +933,182 @@ class TestZones(DNSTest):
         except WERRORError as e:
             self.fail(str(e))
 
+    def set_params(self, **kwargs):
+        zone = kwargs.pop('zone', None)
+        for key,val in kwargs.items():
+            name_param = dnsserver.DNS_RPC_NAME_AND_PARAM()
+            name_param.dwParam = val
+            name_param.pszNodeName = key
+
+            client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
+            nap_type = dnsserver.DNSSRV_TYPEID_NAME_AND_PARAM
+            try:
+                self.rpc_conn.DnssrvOperation2(client_version, 0, self.server, zone,
+                                               0, 'ResetDwordProperty', nap_type,
+                                               name_param)
+            except WERRORError as e:
+                self.fail(str(e))
+
+    def ldap_modify_dnsrecs(self, name, func):
+        dn = 'DC={},{}'.format(name, self.zone_dn)
+        dns_recs = self.ldap_get_dns_records(name)
+        for rec in dns_recs:
+            func(rec)
+        update_dict = {'dn':dn, 'dnsRecord':[ndr_pack(r) for r in dns_recs]}
+        self.samdb.modify(ldb.Message.from_dict(self.samdb,
+                                                update_dict,
+                                                ldb.FLAG_MOD_REPLACE))
+
+    def dns_update_record(self, prefix, txt):
+        p = self.make_txt_update(prefix, txt, self.zone)
+        (code, response) = self.dns_transaction_udp(p, host=self.server_ip)
+        self.assert_dns_rcode_equals(code, dns.DNS_RCODE_OK)
+        recs = self.ldap_get_dns_records(prefix)
+        recs = [r for r in recs if r.data.str == txt]
+        self.assertEqual(len(recs), 1)
+        return recs[0]
+
+    def ldap_get_records(self, name):
+        dn = 'DC={},{}'.format(name, self.zone_dn)
+        expr = "(&(objectClass=dnsNode)(name={}))".format(name)
+        return self.samdb.search(base=dn, scope=ldb.SCOPE_SUBTREE,
+                                 expression=expr, attrs=["*"])
+
+    def ldap_get_dns_records(self, name):
+        records = self.ldap_get_records(name)
+        return [ndr_unpack(dnsp.DnssrvRpcRecord, r)
+                for r in records[0].get('dnsRecord')]
+
+    def ldap_get_zone_settings(self):
+        records = self.samdb.search(base=self.zone_dn, scope=ldb.SCOPE_BASE,
+                   expression="(&(objectClass=dnsZone)"+\
+                                "(name={}))".format(self.zone),
+                                    attrs=["dNSProperty"])
+        self.assertEqual(len(records), 1)
+        props = [ndr_unpack(dnsp.DnsProperty, r)
+                 for r in records[0].get('dNSProperty')]
+
+        #We have no choice but to repeat these here.
+        zone_prop_ids = {0x00: "EMPTY",
+                         0x01: "TYPE",
+                         0x02: "ALLOW_UPDATE",
+                         0x08: "SECURE_TIME",
+                         0x10: "NOREFRESH_INTERVAL",
+                         0x11: "SCAVENGING_SERVERS",
+                         0x12: "AGING_ENABLED_TIME",
+                         0x20: "REFRESH_INTERVAL",
+                         0x40: "AGING_STATE",
+                         0x80: "DELETED_FROM_HOSTNAME",
+                         0x81: "MASTER_SERVERS",
+                         0x82: "AUTO_NS_SERVERS",
+                         0x83: "DCPROMO_CONVERT",
+                         0x90: "SCAVENGING_SERVERS_DA",
+                         0x91: "MASTER_SERVERS_DA",
+                         0x92: "NS_SERVERS_DA",
+                         0x100: "NODE_DBFLAGS"}
+        return {zone_prop_ids[p.id].lower(): p.data for p in props}
+
+    def set_aging(self, enable=False):
+        self.create_zone(self.zone, aging_enabled=enable)
+        self.set_params(NoRefreshInterval=1, RefreshInterval=1,
+                        Aging=int(bool(enable)), zone=self.zone,
+                        AllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE)
+
+    def test_set_aging(self, enable=True, name='agingtest', txt=['test txt']):
+        self.set_aging(enable=True)
+        settings = self.ldap_get_zone_settings()
+        self.assertTrue(settings['aging_state'] is not None)
+        self.assertTrue(settings['aging_state'])
+
+        rec = self.dns_update_record('agingtest', ['test txt'])
+        self.assertNotEqual(rec.dwTimeStamp, 0)
+
+    def test_set_aging_disabled(self):
+        self.set_aging(enable=False)
+        settings = self.ldap_get_zone_settings()
+        self.assertTrue(settings['aging_state'] is not None)
+        self.assertFalse(settings['aging_state'])
+
+        rec = self.dns_update_record('agingtest', ['test txt'])
+        self.assertNotEqual(rec.dwTimeStamp, 0)
+
+    def test_aging_update(self, enable=True):
+        name, txt = 'agingtest', ['test txt']
+        self.set_aging(enable=True)
+        before_mod = self.dns_update_record(name, txt)
+        if not enable:
+            self.set_params(zone=self.zone, Aging=0)
+        dec = 2
+        def mod_ts(rec):
+            rec.dwTimeStamp -= dec
+        self.ldap_modify_dnsrecs(name, mod_ts)
+        after_mod = self.ldap_get_dns_records(name)
+        self.assertEqual(len(after_mod), 1)
+        after_mod = after_mod[0]
+        self.assertEqual(after_mod.dwTimeStamp,
+                         before_mod.dwTimeStamp - dec)
+        after_update = self.dns_update_record(name, txt)
+        after_should_equal = before_mod if enable else after_mod
+        self.assertEqual(after_should_equal.dwTimeStamp,
+                         after_update.dwTimeStamp)
+
+    def test_aging_update_disabled(self):
+        self.test_aging_update(enable=False)
+
+    def test_aging_refresh(self):
+        name,txt = 'agingtest', ['test txt']
+        self.create_zone(self.zone, aging_enabled=True)
+        interval = 10
+        self.set_params(NoRefreshInterval=interval, RefreshInterval=interval,
+                        Aging=1, zone=self.zone,
+                        AllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE)
+        before_mod = self.dns_update_record(name, txt)
+        def mod_ts(rec):
+            rec.dwTimeStamp -= interval/2
+        self.ldap_modify_dnsrecs(name, mod_ts)
+        update_during_norefresh = self.dns_update_record(name, txt)
+        def mod_ts(rec):
+            rec.dwTimeStamp -= interval + interval/2
+        self.ldap_modify_dnsrecs(name, mod_ts)
+        update_during_refresh = self.dns_update_record(name, txt)
+        self.assertEqual(update_during_norefresh.dwTimeStamp,
+                         before_mod.dwTimeStamp - interval/2)
+        self.assertEqual(update_during_refresh.dwTimeStamp,
+                         before_mod.dwTimeStamp)
+
+    def test_rpc_add_no_timestamp(self):
+        name,txt = 'agingtest', ['test txt']
+        self.set_aging(enable=True)
+        rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+        rec_buf.rec = TXTRecord(txt)
+        self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                                          0, self.server_ip,
+                                          self.zone, name, rec_buf, None)
+        recs = self.ldap_get_dns_records(name)
+        self.assertEqual(len(recs), 1)
+        self.assertEqual(recs[0].dwTimeStamp, 0)
+
+    def test_basic_scavenging(self):
+        self.create_zone(self.zone, aging_enabled=True)
+        interval = 1
+        self.set_params(NoRefreshInterval=interval, RefreshInterval=interval,
+                        zone=self.zone, Aging=1,
+                        AllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE)
+        name, txt = 'agingtest', ['test txt']
+        rec = self.dns_update_record(name,txt)
+        rec = self.dns_update_record(name+'2',txt)
+        def mod_ts(rec):
+            rec.dwTimeStamp -= interval*5
+        self.ldap_modify_dnsrecs(name, mod_ts)
+        dsdb._scavenge_dns_records(self.samdb)
+
+        recs = self.ldap_get_dns_records(name)
+        self.assertEqual(len(recs), 1)
+        self.assertEqual(recs[0].wType, dnsp.DNS_TYPE_TOMBSTONE)
+        recs = self.ldap_get_dns_records(name+'2')
+        self.assertEqual(len(recs), 1)
+        self.assertEqual(recs[0].wType, dnsp.DNS_TYPE_TXT)
+
     def delete_zone(self, zone):
         self.rpc_conn.DnssrvOperation2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
                                        0,
diff --git a/python/samba/tests/dns_base.py b/python/samba/tests/dns_base.py
index 10c7a0a6af5..0709bbe1c40 100644
--- a/python/samba/tests/dns_base.py
+++ b/python/samba/tests/dns_base.py
@@ -166,18 +166,18 @@ class DNSTest(TestCaseInTempDir):
 
         return (response, recv_packet[2:])
 
-    def make_txt_update(self, prefix, txt_array):
+    def make_txt_update(self, prefix, txt_array, domain=None):
         p = self.make_name_packet(dns.DNS_OPCODE_UPDATE)
         updates = []
 
-        name = self.get_dns_domain()
+        name = domain or self.get_dns_domain()
         u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
         updates.append(u)
         self.finish_name_packet(p, updates)
 
         updates = []
         r = dns.res_rec()
-        r.name = "%s.%s" % (prefix, self.get_dns_domain())
+        r.name = "%s.%s" % (prefix, name)
         r.rr_type = dns.DNS_QTYPE_TXT
         r.rr_class = dns.DNS_QCLASS_IN
         r.ttl = 900
@@ -190,8 +190,8 @@ class DNSTest(TestCaseInTempDir):
 
         return p
 
-    def check_query_txt(self, prefix, txt_array):
-        name = "%s.%s" % (prefix, self.get_dns_domain())
+    def check_query_txt(self, prefix, txt_array, zone=None):
+        name = "%s.%s" % (prefix, zone or self.get_dns_domain())
         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
         questions = []
 
diff --git a/selftest/knownfail.d/dns b/selftest/knownfail.d/dns
index cb3003240ea..9fa35a2b6ea 100644
--- a/selftest/knownfail.d/dns
+++ b/selftest/knownfail.d/dns
@@ -33,7 +33,23 @@ samba.tests.dns.__main__.TestRPCRoundtrip.test_update_add_padding_rpc_to_dns\(ro
 samba.tests.dns.__main__.TestRPCRoundtrip.test_update_add_slash_rpc_to_dns\(rodc:local\)
 samba.tests.dns.__main__.TestRPCRoundtrip.test_update_add_two_rpc_to_dns\(rodc:local\)
 samba.tests.dns.__main__.TestRPCRoundtrip.test_update_add_txt_rpc_to_dns\(rodc:local\)
+
+samba.tests.dns.__main__.TestZones.test_set_aging_disabled
+
 samba.tests.dns.__main__.TestZones.test_soa_query\(rodc:local\)
+samba.tests.dns.__main__.TestZones.test_set_aging\(rodc:local\)
+samba.tests.dns.__main__.TestZones.test_aging_update\(rodc:local\)
+samba.tests.dns.__main__.TestZones.test_aging_update_disabled\(rodc:local\)
+samba.tests.dns.__main__.TestZones.test_aging_refresh\(rodc:local\)
+samba.tests.dns.__main__.TestZones.test_rpc_add_no_timestamp\(rodc:local\)
+samba.tests.dns.__main__.TestZones.test_basic_scavenging\(rodc:local\)
+
+samba.tests.dns.__main__.TestZones.test_set_aging\(vampire_dc:local\)
+samba.tests.dns.__main__.TestZones.test_aging_update\(vampire_dc:local\)
+samba.tests.dns.__main__.TestZones.test_aging_update_disabled\(vampire_dc:local\)
+samba.tests.dns.__main__.TestZones.test_aging_refresh\(vampire_dc:local\)
+samba.tests.dns.__main__.TestZones.test_basic_scavenging\(vampire_dc:local\)
+
 samba.tests.dns.__main__.TestComplexQueries.test_cname_two_chain\(vampire_dc:local\)
 samba.tests.dns.__main__.TestComplexQueries.test_one_a_query\(vampire_dc:local\)
 samba.tests.dns.__main__.TestSimpleQueries.test_one_a_query\(vampire_dc:local\)
diff --git a/selftest/knownfail.d/dns-scavenging b/selftest/knownfail.d/dns-scavenging
new file mode 100644
index 00000000000..3c4cdc97a78
--- /dev/null
+++ b/selftest/knownfail.d/dns-scavenging
@@ -0,0 +1,12 @@
+#
+# Tests added for the dns scavenging changes
+#
+# Will be removed once the tests are implemented.
+#
+samba.tests.dns.__main__.TestZones.test_aging_refresh\(fl2003dc:local\)
+samba.tests.dns.__main__.TestZones.test_aging_update\(fl2003dc:local\)
+samba.tests.dns.__main__.TestZones.test_aging_update_disabled\(fl2003dc:local\)
+samba.tests.dns.__main__.TestZones.test_basic_scavenging\(fl2003dc:local\)
+samba.tests.dns.__main__.TestZones.test_rpc_add_no_timestamp\(fl2003dc:local\)
+samba.tests.dns.__main__.TestZones.test_set_aging\(fl2003dc:local\)
+samba.tests.dns.__main__.TestZones.test_rpc_add_no_timestamp\(vampire_dc:local\)
-- 
2.11.0


From 475a8263e918afadfc77c3d63ac1f589dece8a67 Mon Sep 17 00:00:00 2001
From: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Date: Wed, 30 May 2018 18:56:16 +1200
Subject: [PATCH 02/18] rpc dns: setting timestamp to 0 on RPC processed
 records

All records created by RPC DNS server calls should have timestamp set to 0
according to [MS-DNSP]

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12451

Signed-off-by: Aaron Haslett<aaronhaslett at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/rpc_server/dnsserver/dnsdb.c | 29 +++++------------------------
 1 file changed, 5 insertions(+), 24 deletions(-)

diff --git a/source4/rpc_server/dnsserver/dnsdb.c b/source4/rpc_server/dnsserver/dnsdb.c
index 81a2d2070a0..252d39f29c0 100644
--- a/source4/rpc_server/dnsserver/dnsdb.c
+++ b/source4/rpc_server/dnsserver/dnsdb.c
@@ -258,11 +258,6 @@ static unsigned int dnsserver_update_soa(TALLOC_CTX *mem_ctx,
 	struct ldb_message_element *el;
 	enum ndr_err_code ndr_err;
 	int ret, i, serial = -1;
-	NTTIME t;
-
-	unix_to_nt_time(&t, time(NULL));
-	t /= 10*1000*1000; /* convert to seconds (NT time is in 100ns units) */
-	t /= 3600;         /* convert to hours */
 
 	ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
 			"(&(objectClass=dnsNode)(name=@))");
@@ -285,7 +280,7 @@ static unsigned int dnsserver_update_soa(TALLOC_CTX *mem_ctx,
 		if (rec.wType == DNS_TYPE_SOA) {
 			serial = rec.data.soa.serial + 1;
 			rec.dwSerial = serial;
-			rec.dwTimeStamp = (uint32_t)t;
+			rec.dwTimeStamp = 0;
 			rec.data.soa.serial = serial;
 
 			ndr_err = ndr_push_struct_blob(&el->values[i], mem_ctx, &rec,
@@ -403,7 +398,6 @@ WERROR dnsserver_db_add_record(TALLOC_CTX *mem_ctx,
 	struct ldb_message_element *el;
 	struct ldb_dn *dn;
 	enum ndr_err_code ndr_err;
-	NTTIME t;
 	int ret, i;
 	int serial;
 	WERROR werr;
@@ -431,12 +425,8 @@ WERROR dnsserver_db_add_record(TALLOC_CTX *mem_ctx,
 		return WERR_INTERNAL_DB_ERROR;
 	}
 
-	unix_to_nt_time(&t, time(NULL));
-	t /= 10*1000*1000; /* convert to seconds (NT time is in 100ns units) */
-	t /= 3600;         /* convert to hours */
-
 	rec->dwSerial = serial;
-	rec->dwTimeStamp = t;
+	rec->dwTimeStamp = 0;
 
 	ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
 			"(&(objectClass=dnsNode)(name=%s))",
@@ -524,7 +514,6 @@ WERROR dnsserver_db_update_record(TALLOC_CTX *mem_ctx,
 	struct dnsp_DnssrvRpcRecord *arec = NULL, *drec = NULL;
 	struct ldb_message_element *el;
 	enum ndr_err_code ndr_err;
-	NTTIME t;
 	int ret, i;
 	int serial;
 	WERROR werr;
@@ -540,10 +529,7 @@ WERROR dnsserver_db_update_record(TALLOC_CTX *mem_ctx,
 		return werr;
 	}
 
-	unix_to_nt_time(&t, time(NULL));
-	t /= 10*1000*1000;
-
-	arec->dwTimeStamp = t;
+	arec->dwTimeStamp = 0;
 
 	ret = ldb_search(samdb, mem_ctx, &res, z->zone_dn, LDB_SCOPE_ONELEVEL, attrs,
 			"(&(objectClass=dnsNode)(name=%s)(!(dNSTombstoned=TRUE)))",
@@ -886,7 +872,6 @@ WERROR dnsserver_db_create_zone(struct ldb_context *samdb,
 	struct dnsp_DnssrvRpcRecord *dns_rec;
 	struct dnsp_soa soa;
 	char *tmpstr, *server_fqdn, *soa_email;
-	NTTIME t;
 
 	/* We only support primary zones for now */
 	if (zone->zoneinfo->dwZoneType != DNS_ZONE_TYPE_PRIMARY) {
@@ -947,10 +932,6 @@ WERROR dnsserver_db_create_zone(struct ldb_context *samdb,
 	W_ERROR_HAVE_NO_MEMORY_AND_FREE(soa_email, tmp_ctx);
 	talloc_free(tmpstr);
 
-	unix_to_nt_time(&t, time(NULL));
-	t /= 10*1000*1000; /* convert to seconds (NT time is in 100ns units) */
-	t /= 3600;         /* convert to hours */
-
 	/* SOA Record - values same as defined in provision/sambadns.py */
 	soa.serial = 1;
 	soa.refresh = 900;
@@ -964,7 +945,7 @@ WERROR dnsserver_db_create_zone(struct ldb_context *samdb,
 	dns_rec[0].rank = DNS_RANK_ZONE;
 	dns_rec[0].dwSerial = soa.serial;
 	dns_rec[0].dwTtlSeconds = 3600;
-	dns_rec[0].dwTimeStamp = (uint32_t)t;
+	dns_rec[0].dwTimeStamp = 0;
 	dns_rec[0].data.soa = soa;
 
 	/* NS Record */
@@ -972,7 +953,7 @@ WERROR dnsserver_db_create_zone(struct ldb_context *samdb,
 	dns_rec[1].rank = DNS_RANK_ZONE;
 	dns_rec[1].dwSerial = soa.serial;
 	dns_rec[1].dwTtlSeconds = 3600;
-	dns_rec[1].dwTimeStamp = (uint32_t)t;
+	dns_rec[1].dwTimeStamp = 0;
 	dns_rec[1].data.ns = server_fqdn;
 
 	/* Add @ Record */
-- 
2.11.0


From d6b50e36de8af6201183acfa84f2f212a7312821 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Tue, 10 Jul 2018 13:37:18 +1200
Subject: [PATCH 03/18] dns: Reformat DNS with clang-format

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 lib/ldb-samba/ldb_matching_rules.c              |  2 +-
 source4/dns_server/dlz_bind9.c                  | 19 ++++++++++++++-----
 source4/dns_server/dns_update.c                 |  1 -
 source4/dsdb/kcc/kcc_periodic.c                 |  4 ++--
 source4/rpc_server/dnsserver/dcerpc_dnsserver.c |  3 ++-
 source4/rpc_server/dnsserver/dnsdata.c          | 10 ++++++----
 source4/rpc_server/dnsserver/dnsdb.c            |  6 +++---
 source4/rpc_server/dnsserver/dnsserver.h        |  8 ++++----
 source4/rpc_server/dnsserver/dnsutils.c         | 15 ++++++++-------
 9 files changed, 40 insertions(+), 28 deletions(-)

diff --git a/lib/ldb-samba/ldb_matching_rules.c b/lib/ldb-samba/ldb_matching_rules.c
index 063a5d3c3a3..5999943dcd2 100644
--- a/lib/ldb-samba/ldb_matching_rules.c
+++ b/lib/ldb-samba/ldb_matching_rules.c
@@ -448,7 +448,7 @@ static int dsdb_match_for_expunge(struct ldb_context *ldb,
 int ldb_register_samba_matching_rules(struct ldb_context *ldb)
 {
 	struct ldb_extended_match_rule *transitive_eval = NULL,
-		*match_for_expunge = NULL;
+				       *match_for_expunge = NULL;
 	int ret;
 
 	transitive_eval = talloc_zero(ldb, struct ldb_extended_match_rule);
diff --git a/source4/dns_server/dlz_bind9.c b/source4/dns_server/dlz_bind9.c
index ac785f0d4f3..0463c6e47a6 100644
--- a/source4/dns_server/dlz_bind9.c
+++ b/source4/dns_server/dlz_bind9.c
@@ -1488,15 +1488,18 @@ static bool dns_name_equal(const char *name1, const char *name2)
 {
 	size_t len1 = strlen(name1);
 	size_t len2 = strlen(name2);
-	if (name1[len1-1] == '.') len1--;
-	if (name2[len2-1] == '.') len2--;
+	if (name1[len1 - 1] == '.') {
+		len1--;
+	}
+	if (name2[len2 - 1] == '.') {
+		len2--;
+	}
 	if (len1 != len2) {
 		return false;
 	}
 	return strncasecmp_m(name1, name2, len1) == 0;
 }
 
-
 /*
   see if two dns records match
  */
@@ -1648,8 +1651,14 @@ _PUBLIC_ isc_result_t dlz_addrdataset(const char *name, const char *rdatastr, vo
 	}
 
 	unix_to_nt_time(&t, time(NULL));
-	t /= 10*1000*1000; /* convert to seconds (NT time is in 100ns units) */
-	t /= 3600;         /* convert to hours */
+	/*
+	 * convert to seconds (NT time is in 100ns units)
+	 */
+	t /= 10 * 1000 * 1000;
+	/*
+	 * convert to hours
+	 */
+	t /= 3600;
 
 	rec->rank        = DNS_RANK_ZONE;
 	rec->dwTimeStamp = (uint32_t)t;
diff --git a/source4/dns_server/dns_update.c b/source4/dns_server/dns_update.c
index a48f27bd992..ac3c3e11bae 100644
--- a/source4/dns_server/dns_update.c
+++ b/source4/dns_server/dns_update.c
@@ -610,7 +610,6 @@ static WERROR handle_one_update(struct dns_server *dns,
 						struct dnsp_DnssrvRpcRecord);
 			W_ERROR_HAVE_NO_MEMORY(ns_rec);
 
-
 			werror = dns_rr_to_dnsp(ns_rec, update, ns_rec);
 			W_ERROR_NOT_OK_RETURN(werror);
 
diff --git a/source4/dsdb/kcc/kcc_periodic.c b/source4/dsdb/kcc/kcc_periodic.c
index fa19ba7efc5..855b82b4fd5 100644
--- a/source4/dsdb/kcc/kcc_periodic.c
+++ b/source4/dsdb/kcc/kcc_periodic.c
@@ -601,8 +601,8 @@ WERROR kccsrv_periodic_schedule(struct kccsrv_service *service, uint32_t next_in
 static NTSTATUS kccsrv_check_deleted(struct kccsrv_service *s, TALLOC_CTX *mem_ctx)
 {
 	time_t current_time = time(NULL);
-	time_t interval = lpcfg_parm_int(s->task->lp_ctx, NULL, "kccsrv",
-					 "check_deleted_interval", 86400);
+	time_t interval = lpcfg_parm_int(
+	    s->task->lp_ctx, NULL, "kccsrv", "check_deleted_interval", 86400);
 	uint32_t tombstoneLifetime;
 	int ret;
 	unsigned int num_objects_removed = 0;
diff --git a/source4/rpc_server/dnsserver/dcerpc_dnsserver.c b/source4/rpc_server/dnsserver/dcerpc_dnsserver.c
index b9ed3dd5887..c342ccadfc9 100644
--- a/source4/rpc_server/dnsserver/dcerpc_dnsserver.c
+++ b/source4/rpc_server/dnsserver/dcerpc_dnsserver.c
@@ -1517,7 +1517,8 @@ static WERROR dnsserver_operate_zone(struct dnsserver_state *dsstate,
 		}
 
 		/* Ignore property resets */
-		if (strcasecmp(r->NameAndParam->pszNodeName, "AllowUpdate") == 0) {
+		if (strcasecmp(r->NameAndParam->pszNodeName, "AllowUpdate") ==
+		    0) {
 			return WERR_OK;
 		}
 		valid_operation = true;
diff --git a/source4/rpc_server/dnsserver/dnsdata.c b/source4/rpc_server/dnsserver/dnsdata.c
index a7b8e74685b..6889cc306ce 100644
--- a/source4/rpc_server/dnsserver/dnsdata.c
+++ b/source4/rpc_server/dnsserver/dnsdata.c
@@ -1119,21 +1119,23 @@ int dns_name_compare(const struct ldb_message **m1, const struct ldb_message **m
 	return strcasecmp(ptr1, ptr2);
 }
 
-
 bool dns_name_equal(const char *name1, const char *name2)
 {
 	size_t len1 = strlen(name1);
 	size_t len2 = strlen(name2);
 
-	if (len1 > 0 && name1[len1-1] == '.') len1--;
-	if (len2 > 0 && name2[len2-1] == '.') len2--;
+	if (len1 > 0 && name1[len1 - 1] == '.') {
+		len1--;
+	}
+	if (len2 > 0 && name2[len2 - 1] == '.') {
+		len2--;
+	}
 	if (len1 != len2) {
 		return false;
 	}
 	return strncasecmp(name1, name2, len1) == 0;
 }
 
-
 bool dns_record_match(struct dnsp_DnssrvRpcRecord *rec1, struct dnsp_DnssrvRpcRecord *rec2)
 {
 	bool status;
diff --git a/source4/rpc_server/dnsserver/dnsdb.c b/source4/rpc_server/dnsserver/dnsdb.c
index 252d39f29c0..caf30798846 100644
--- a/source4/rpc_server/dnsserver/dnsdb.c
+++ b/source4/rpc_server/dnsserver/dnsdb.c
@@ -86,7 +86,7 @@ struct dnsserver_zone *dnsserver_db_enumerate_zones(TALLOC_CTX *mem_ctx,
 						struct dnsserver_partition *p)
 {
 	TALLOC_CTX *tmp_ctx;
-	const char * const attrs[] = {"name", NULL};
+	const char *const attrs[] = {"name", NULL};
 	struct ldb_dn *dn;
 	struct ldb_result *res;
 	struct dnsserver_zone *zones, *z;
@@ -122,8 +122,8 @@ struct dnsserver_zone *dnsserver_db_enumerate_zones(TALLOC_CTX *mem_ctx,
 		}
 
 		z->partition = p;
-		name = talloc_strdup(z,
-				ldb_msg_find_attr_as_string(res->msgs[i], "name", NULL));
+		name = talloc_strdup(
+		    z, ldb_msg_find_attr_as_string(res->msgs[i], "name", NULL));
 		if (strcmp(name, "..TrustAnchors") == 0) {
 			talloc_free(z);
 			continue;
diff --git a/source4/rpc_server/dnsserver/dnsserver.h b/source4/rpc_server/dnsserver/dnsserver.h
index 0b08f08fa2d..a30d4ee1acc 100644
--- a/source4/rpc_server/dnsserver/dnsserver.h
+++ b/source4/rpc_server/dnsserver/dnsserver.h
@@ -248,10 +248,10 @@ WERROR dnsserver_db_update_record(TALLOC_CTX *mem_ctx,
 					struct DNS_RPC_RECORD *add_record,
 					struct DNS_RPC_RECORD *del_record);
 WERROR dnsserver_db_delete_record(TALLOC_CTX *mem_ctx,
-					struct ldb_context *samdb,
-					struct dnsserver_zone *z,
-					const char *node_name,
-					struct DNS_RPC_RECORD *del_record);
+				  struct ldb_context *samdb,
+				  struct dnsserver_zone *z,
+				  const char *node_name,
+				  struct DNS_RPC_RECORD *del_record);
 WERROR dnsserver_db_create_zone(struct ldb_context *samdb,
 				struct dnsserver_partition *partitions,
 				struct dnsserver_zone *z,
diff --git a/source4/rpc_server/dnsserver/dnsutils.c b/source4/rpc_server/dnsserver/dnsutils.c
index 72b47f72b4f..f0d7e283110 100644
--- a/source4/rpc_server/dnsserver/dnsutils.c
+++ b/source4/rpc_server/dnsserver/dnsutils.c
@@ -172,15 +172,16 @@ struct dnsserver_serverinfo *dnsserver_init_serverinfo(TALLOC_CTX *mem_ctx,
 	serverinfo->dwRpcProtocol = 5;
 	serverinfo->dwNameCheckFlag = DNS_ALLOW_MULTIBYTE_NAMES;
 	serverinfo->cAddressAnswerLimit = 0;
-	serverinfo->dwRecursionRetry = 3 /* seconds (default) */;
-	serverinfo->dwRecursionTimeout = 8 /* seconds (default) */;
-	serverinfo->dwMaxCacheTtl = 0x00015180; /* 1 day (default) */;
-	serverinfo->dwDsPollingInterval = 0xB4; /* 3 minutes (default) */;
-	serverinfo->dwLocalNetPriorityNetMask = 0x000000FF;;
+	serverinfo->dwRecursionRetry = 3;       /* seconds (default) */
+	serverinfo->dwRecursionTimeout = 8;     /* seconds (default) */
+	serverinfo->dwMaxCacheTtl = 0x00015180; /* 1 day (default) */
+	serverinfo->dwDsPollingInterval = 0xB4; /* 3 minutes (default) */
+	serverinfo->dwLocalNetPriorityNetMask = 0x000000FF;
 
 	serverinfo->dwScavengingInterval = 0;
-	serverinfo->dwDefaultRefreshInterval = 0xA8; /* 7 days in hours */;
-	serverinfo->dwDefaultNoRefreshInterval = 0xA8; /* 7 days in hours */;;
+	serverinfo->dwDefaultRefreshInterval = 0xA8;   /* 7 days in hours */
+	serverinfo->dwDefaultNoRefreshInterval = 0xA8; /* 7 days in hours */
+
 	serverinfo->dwLastScavengeTime = 0;
 
 	serverinfo->fAutoReverseZones = 0;
-- 
2.11.0


From 5615916c5d3c7a5973b725df9facb581e3d99947 Mon Sep 17 00:00:00 2001
From: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Date: Tue, 3 Jul 2018 15:33:06 +1200
Subject: [PATCH 04/18] rpc dns: reading zone properties from LDB

Reading zone properties from LDB on server connection initialisation, instead
of them being volatile fields.

Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/rpc_server/dnsserver/dnsdb.c     | 33 ++++++++++++++++---
 source4/rpc_server/dnsserver/dnsserver.h |  2 ++
 source4/rpc_server/dnsserver/dnsutils.c  | 54 ++++++++++++++++++++++++++++++++
 3 files changed, 84 insertions(+), 5 deletions(-)

diff --git a/source4/rpc_server/dnsserver/dnsdb.c b/source4/rpc_server/dnsserver/dnsdb.c
index caf30798846..cdd6c0258b6 100644
--- a/source4/rpc_server/dnsserver/dnsdb.c
+++ b/source4/rpc_server/dnsserver/dnsdb.c
@@ -86,11 +86,11 @@ struct dnsserver_zone *dnsserver_db_enumerate_zones(TALLOC_CTX *mem_ctx,
 						struct dnsserver_partition *p)
 {
 	TALLOC_CTX *tmp_ctx;
-	const char *const attrs[] = {"name", NULL};
+	const char * const attrs[] = {"name", "dNSProperty", NULL};
 	struct ldb_dn *dn;
 	struct ldb_result *res;
 	struct dnsserver_zone *zones, *z;
-	int i, ret;
+	int i, j, ret;
 
 	tmp_ctx = talloc_new(mem_ctx);
 	if (tmp_ctx == NULL) {
@@ -116,14 +116,18 @@ struct dnsserver_zone *dnsserver_db_enumerate_zones(TALLOC_CTX *mem_ctx,
 	zones = NULL;
 	for(i=0; i<res->count; i++) {
 		char *name;
+		struct ldb_message_element *element = NULL;
+		struct dnsp_DnsProperty *props = NULL;
+		enum ndr_err_code err;
 		z = talloc_zero(mem_ctx, struct dnsserver_zone);
 		if (z == NULL) {
 			goto failed;
 		}
 
 		z->partition = p;
-		name = talloc_strdup(
-		    z, ldb_msg_find_attr_as_string(res->msgs[i], "name", NULL));
+		name = talloc_strdup(z,
+				ldb_msg_find_attr_as_string(res->msgs[i],
+							    "name", NULL));
 		if (strcmp(name, "..TrustAnchors") == 0) {
 			talloc_free(z);
 			continue;
@@ -138,8 +142,27 @@ struct dnsserver_zone *dnsserver_db_enumerate_zones(TALLOC_CTX *mem_ctx,
 
 		DLIST_ADD_END(zones, z);
 		DEBUG(2, ("dnsserver: Found DNS zone %s\n", z->name));
-	}
 
+		element = ldb_msg_find_element(res->msgs[i], "dNSProperty");
+		if(element != NULL){
+			props = talloc_zero_array(mem_ctx,
+						  struct dnsp_DnsProperty,
+						  element->num_values);
+			for (j = 0; j < element->num_values; j++ ) {
+				err = ndr_pull_struct_blob(
+					&(element->values[j]),
+					mem_ctx,
+					&props[j],
+					(ndr_pull_flags_fn_t)
+						ndr_pull_dnsp_DnsProperty);
+				if (!NDR_ERR_CODE_IS_SUCCESS(err)){
+					goto failed;
+				}
+			}
+			z->tmp_props = props;
+			z->num_props = element->num_values;
+		}
+	}
 	return zones;
 
 failed:
diff --git a/source4/rpc_server/dnsserver/dnsserver.h b/source4/rpc_server/dnsserver/dnsserver.h
index a30d4ee1acc..83dccf5e6c5 100644
--- a/source4/rpc_server/dnsserver/dnsserver.h
+++ b/source4/rpc_server/dnsserver/dnsserver.h
@@ -164,6 +164,8 @@ struct dnsserver_zone {
 	const char *name;
 	struct ldb_dn *zone_dn;
 	struct dnsserver_zoneinfo *zoneinfo;
+	struct dnsp_DnsProperty *tmp_props;
+	int32_t num_props;
 };
 
 
diff --git a/source4/rpc_server/dnsserver/dnsutils.c b/source4/rpc_server/dnsserver/dnsutils.c
index f0d7e283110..5eb95f8f339 100644
--- a/source4/rpc_server/dnsserver/dnsutils.c
+++ b/source4/rpc_server/dnsserver/dnsutils.c
@@ -214,6 +214,8 @@ struct dnsserver_zoneinfo *dnsserver_init_zoneinfo(struct dnsserver_zone *zone,
 	const char *revzone = "in-addr.arpa";
 	const char *revzone6 = "ip6.arpa";
 	int len1, len2;
+	union dnsPropertyData *prop = NULL;
+	int i=0;
 
 	zoneinfo = talloc_zero(zone, struct dnsserver_zoneinfo);
 	if (zoneinfo == NULL) {
@@ -280,6 +282,58 @@ struct dnsserver_zoneinfo *dnsserver_init_zoneinfo(struct dnsserver_zone *zone,
 	zoneinfo->dwLastXfrAttempt = 0;
 	zoneinfo->dwLastXfrResult = 0;
 
+	for(i=0; i<zone->num_props; i++){
+		prop=&(zone->tmp_props[i].data);
+		switch (zone->tmp_props[i].id) {
+		case DSPROPERTY_ZONE_TYPE:
+			zoneinfo->dwZoneType =
+				prop->zone_type;
+			break;
+		case DSPROPERTY_ZONE_ALLOW_UPDATE:
+			zoneinfo->fAllowUpdate =
+				prop->allow_update_flag;
+			break;
+		case DSPROPERTY_ZONE_NOREFRESH_INTERVAL:
+			zoneinfo->dwNoRefreshInterval =
+				prop->norefresh_hours;
+			break;
+		case DSPROPERTY_ZONE_REFRESH_INTERVAL:
+			zoneinfo->dwRefreshInterval =
+				prop->refresh_hours;
+			break;
+		case DSPROPERTY_ZONE_AGING_STATE:
+			zoneinfo->fAging =
+				prop->aging_enabled;
+			break;
+		case DSPROPERTY_ZONE_SCAVENGING_SERVERS:
+			zoneinfo->aipScavengeServers->AddrCount =
+				prop->servers.addrCount;
+			zoneinfo->aipScavengeServers->AddrArray =
+				prop->servers.addr;
+			break;
+		case DSPROPERTY_ZONE_AGING_ENABLED_TIME:
+			zoneinfo->dwAvailForScavengeTime =
+				prop->next_scavenging_cycle_hours;
+			break;
+		case DSPROPERTY_ZONE_MASTER_SERVERS:
+			zoneinfo->aipLocalMasters->AddrCount =
+				prop->master_servers.addrCount;
+			zoneinfo->aipLocalMasters->AddrArray =
+				prop->master_servers.addr;
+			break;
+		case DSPROPERTY_ZONE_EMPTY:
+		case DSPROPERTY_ZONE_SECURE_TIME:
+		case DSPROPERTY_ZONE_DELETED_FROM_HOSTNAME:
+		case DSPROPERTY_ZONE_AUTO_NS_SERVERS:
+		case DSPROPERTY_ZONE_DCPROMO_CONVERT:
+		case DSPROPERTY_ZONE_SCAVENGING_SERVERS_DA:
+		case DSPROPERTY_ZONE_MASTER_SERVERS_DA:
+		case DSPROPERTY_ZONE_NS_SERVERS_DA:
+		case DSPROPERTY_ZONE_NODE_DBFLAGS:
+			break;
+		}
+	}
+
 	return zoneinfo;
 }
 
-- 
2.11.0


From 977006a0d42ed92684109705ae36f47cabd829ab Mon Sep 17 00:00:00 2001
From: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Date: Tue, 3 Jul 2018 15:34:32 +1200
Subject: [PATCH 05/18] rpc dns: reset dword aging related zone properties

This allows a user to set zone properties relevant to DNS record aging over RPC.

Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/tests/dns.py                       |   4 +
 selftest/knownfail.d/dns                        |   3 +-
 selftest/knownfail.d/dns-scavenging             |   3 +-
 source4/rpc_server/dnsserver/dcerpc_dnsserver.c |  10 +-
 source4/rpc_server/dnsserver/dnsdb.c            | 123 ++++++++++++++++++++++++
 source4/rpc_server/dnsserver/dnsserver.h        |   3 +
 6 files changed, 137 insertions(+), 9 deletions(-)

diff --git a/python/samba/tests/dns.py b/python/samba/tests/dns.py
index 083dc460afd..722b75ce81b 100644
--- a/python/samba/tests/dns.py
+++ b/python/samba/tests/dns.py
@@ -1040,6 +1040,7 @@ class TestZones(DNSTest):
             self.set_params(zone=self.zone, Aging=0)
         dec = 2
         def mod_ts(rec):
+            self.assertTrue(rec.dwTimeStamp > 0)
             rec.dwTimeStamp -= dec
         self.ldap_modify_dnsrecs(name, mod_ts)
         after_mod = self.ldap_get_dns_records(name)
@@ -1064,10 +1065,12 @@ class TestZones(DNSTest):
                         AllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE)
         before_mod = self.dns_update_record(name, txt)
         def mod_ts(rec):
+            self.assertTrue(rec.dwTimeStamp > 0)
             rec.dwTimeStamp -= interval/2
         self.ldap_modify_dnsrecs(name, mod_ts)
         update_during_norefresh = self.dns_update_record(name, txt)
         def mod_ts(rec):
+            self.assertTrue(rec.dwTimeStamp > 0)
             rec.dwTimeStamp -= interval + interval/2
         self.ldap_modify_dnsrecs(name, mod_ts)
         update_during_refresh = self.dns_update_record(name, txt)
@@ -1098,6 +1101,7 @@ class TestZones(DNSTest):
         rec = self.dns_update_record(name,txt)
         rec = self.dns_update_record(name+'2',txt)
         def mod_ts(rec):
+            self.assertTrue(rec.dwTimeStamp > 0)
             rec.dwTimeStamp -= interval*5
         self.ldap_modify_dnsrecs(name, mod_ts)
         dsdb._scavenge_dns_records(self.samdb)
diff --git a/selftest/knownfail.d/dns b/selftest/knownfail.d/dns
index 9fa35a2b6ea..4beeabf4386 100644
--- a/selftest/knownfail.d/dns
+++ b/selftest/knownfail.d/dns
@@ -34,7 +34,8 @@ samba.tests.dns.__main__.TestRPCRoundtrip.test_update_add_slash_rpc_to_dns\(rodc
 samba.tests.dns.__main__.TestRPCRoundtrip.test_update_add_two_rpc_to_dns\(rodc:local\)
 samba.tests.dns.__main__.TestRPCRoundtrip.test_update_add_txt_rpc_to_dns\(rodc:local\)
 
-samba.tests.dns.__main__.TestZones.test_set_aging_disabled
+samba.tests.dns.__main__.TestZones.test_set_aging_disabled\(rodc:local\)
+samba.tests.dns.__main__.TestZones.test_set_aging_disabled\(vampire_dc:local\)
 
 samba.tests.dns.__main__.TestZones.test_soa_query\(rodc:local\)
 samba.tests.dns.__main__.TestZones.test_set_aging\(rodc:local\)
diff --git a/selftest/knownfail.d/dns-scavenging b/selftest/knownfail.d/dns-scavenging
index 3c4cdc97a78..715e14527c9 100644
--- a/selftest/knownfail.d/dns-scavenging
+++ b/selftest/knownfail.d/dns-scavenging
@@ -7,6 +7,5 @@ samba.tests.dns.__main__.TestZones.test_aging_refresh\(fl2003dc:local\)
 samba.tests.dns.__main__.TestZones.test_aging_update\(fl2003dc:local\)
 samba.tests.dns.__main__.TestZones.test_aging_update_disabled\(fl2003dc:local\)
 samba.tests.dns.__main__.TestZones.test_basic_scavenging\(fl2003dc:local\)
-samba.tests.dns.__main__.TestZones.test_rpc_add_no_timestamp\(fl2003dc:local\)
 samba.tests.dns.__main__.TestZones.test_set_aging\(fl2003dc:local\)
-samba.tests.dns.__main__.TestZones.test_rpc_add_no_timestamp\(vampire_dc:local\)
+samba.tests.dns.__main__.TestZones.test_set_aging_disabled\(fl2003dc:local\)
diff --git a/source4/rpc_server/dnsserver/dcerpc_dnsserver.c b/source4/rpc_server/dnsserver/dcerpc_dnsserver.c
index c342ccadfc9..b42d7c549d1 100644
--- a/source4/rpc_server/dnsserver/dcerpc_dnsserver.c
+++ b/source4/rpc_server/dnsserver/dcerpc_dnsserver.c
@@ -1512,16 +1512,14 @@ static WERROR dnsserver_operate_zone(struct dnsserver_state *dsstate,
 	bool valid_operation = false;
 
 	if (strcasecmp(operation, "ResetDwordProperty") == 0) {
+
 		if (typeid != DNSSRV_TYPEID_NAME_AND_PARAM) {
 			return WERR_DNS_ERROR_INVALID_PROPERTY;
 		}
 
-		/* Ignore property resets */
-		if (strcasecmp(r->NameAndParam->pszNodeName, "AllowUpdate") ==
-		    0) {
-			return WERR_OK;
-		}
-		valid_operation = true;
+		return dnsserver_db_do_reset_dword(dsstate->samdb, z,
+					           r->NameAndParam);
+
 	} else if (strcasecmp(operation, "ZoneTypeReset") == 0) {
 		valid_operation = true;
 	} else if (strcasecmp(operation, "PauseZone") == 0) {
diff --git a/source4/rpc_server/dnsserver/dnsdb.c b/source4/rpc_server/dnsserver/dnsdb.c
index cdd6c0258b6..350e29aa1e0 100644
--- a/source4/rpc_server/dnsserver/dnsdb.c
+++ b/source4/rpc_server/dnsserver/dnsdb.c
@@ -733,6 +733,129 @@ static bool dnsserver_db_msg_add_dnsproperty(TALLOC_CTX *mem_ctx,
 	return true;
 }
 
+WERROR dnsserver_db_do_reset_dword(struct ldb_context *samdb,
+				   struct dnsserver_zone *z,
+				   struct DNS_RPC_NAME_AND_PARAM *n_p)
+{
+	struct ldb_message_element *element = NULL;
+	struct dnsp_DnsProperty *prop = NULL;
+	enum ndr_err_code err;
+	TALLOC_CTX *tmp_ctx = NULL;
+	const char * const attrs[] = {"dNSProperty", NULL};
+	struct ldb_result *res = NULL;
+	int i, ret, prop_id;
+
+	if (strcasecmp(n_p->pszNodeName, "Aging") == 0) {
+		z->zoneinfo->fAging = n_p->dwParam;
+		prop_id = DSPROPERTY_ZONE_AGING_STATE;
+	} else if (strcasecmp(n_p->pszNodeName, "RefreshInterval") == 0) {
+		z->zoneinfo->dwRefreshInterval = n_p->dwParam;
+		prop_id = DSPROPERTY_ZONE_REFRESH_INTERVAL;
+	} else if (strcasecmp(n_p->pszNodeName, "NoRefreshInterval") == 0) {
+		z->zoneinfo->dwNoRefreshInterval = n_p->dwParam;
+		prop_id = DSPROPERTY_ZONE_NOREFRESH_INTERVAL;
+	} else if (strcasecmp(n_p->pszNodeName, "AllowUpdate") == 0) {
+		z->zoneinfo->fAllowUpdate = n_p->dwParam;
+		prop_id = DSPROPERTY_ZONE_ALLOW_UPDATE;
+	} else {
+		return WERR_UNKNOWN_PROPERTY;
+	}
+
+	tmp_ctx = talloc_new(NULL);
+	if (tmp_ctx == NULL) {
+		return WERR_NOT_ENOUGH_MEMORY;
+	}
+
+	ret = ldb_search(samdb, tmp_ctx, &res, z->zone_dn, LDB_SCOPE_BASE,
+			 attrs, "(objectClass=dnsZone)");
+	if (ret != LDB_SUCCESS) {
+		DBG_ERR("dnsserver: no zone: %s\n",
+			ldb_dn_get_linearized(z->zone_dn));
+		TALLOC_FREE(tmp_ctx);
+		return WERR_INTERNAL_DB_ERROR;
+	}
+
+	if (res->count != 1) {
+		DBG_ERR("dnsserver: duplicate zone: %s\n",
+			ldb_dn_get_linearized(z->zone_dn));
+		TALLOC_FREE(tmp_ctx);
+		return WERR_GEN_FAILURE;
+	}
+
+	element = ldb_msg_find_element(res->msgs[0], "dNSProperty");
+	if (element == NULL) {
+		DBG_ERR("dnsserver: zone %s has no properties.\n",
+			ldb_dn_get_linearized(z->zone_dn));
+		TALLOC_FREE(tmp_ctx);
+		return WERR_INTERNAL_DB_ERROR;
+	}
+
+	for (i = 0; i < element->num_values; i++) {
+		prop = talloc_zero(element, struct dnsp_DnsProperty);
+		if (prop == NULL) {
+			TALLOC_FREE(tmp_ctx);
+			return WERR_NOT_ENOUGH_MEMORY;
+		}
+		err = ndr_pull_struct_blob(
+			&(element->values[i]),
+			tmp_ctx,
+			prop,
+			(ndr_pull_flags_fn_t)ndr_pull_dnsp_DnsProperty);
+		if (!NDR_ERR_CODE_IS_SUCCESS(err)){
+			DBG_ERR("dnsserver: couldn't PULL dns property id "
+				"%d in zone %s\n",
+				prop->id,
+				ldb_dn_get_linearized(z->zone_dn));
+			TALLOC_FREE(tmp_ctx);
+			return WERR_INTERNAL_DB_ERROR;
+		}
+
+		if (prop->id == prop_id) {
+			switch (prop_id) {
+			case DSPROPERTY_ZONE_AGING_STATE:
+				prop->data.aging_enabled = n_p->dwParam;
+				break;
+			case DSPROPERTY_ZONE_NOREFRESH_INTERVAL:
+				prop->data.norefresh_hours = n_p->dwParam;
+				break;
+			case DSPROPERTY_ZONE_REFRESH_INTERVAL:
+				prop->data.refresh_hours = n_p->dwParam;
+				break;
+			case DSPROPERTY_ZONE_ALLOW_UPDATE:
+				prop->data.allow_update_flag = n_p->dwParam;
+				break;
+			}
+
+			err = ndr_push_struct_blob(
+				&(element->values[i]),
+				tmp_ctx,
+				prop,
+				(ndr_push_flags_fn_t)ndr_push_dnsp_DnsProperty);
+			if (!NDR_ERR_CODE_IS_SUCCESS(err)){
+				DBG_ERR("dnsserver: couldn't PUSH dns prop id "
+					"%d in zone %s\n",
+					prop->id,
+					ldb_dn_get_linearized(z->zone_dn));
+				TALLOC_FREE(tmp_ctx);
+				return WERR_INTERNAL_DB_ERROR;
+			}
+		}
+	}
+
+	element->flags = LDB_FLAG_MOD_REPLACE;
+	ret = ldb_modify(samdb, res->msgs[0]);
+	if (ret != LDB_SUCCESS) {
+		TALLOC_FREE(tmp_ctx);
+		DBG_ERR("dnsserver: Failed to modify zone %s prop %s: %s\n",
+			z->name,
+			n_p->pszNodeName,
+			ldb_errstring(samdb));
+		return WERR_INTERNAL_DB_ERROR;
+	}
+	TALLOC_FREE(tmp_ctx);
+
+	return WERR_OK;
+}
 
 /* Create dnsZone record to database and set security descriptor */
 static WERROR dnsserver_db_do_create_zone(TALLOC_CTX *tmp_ctx,
diff --git a/source4/rpc_server/dnsserver/dnsserver.h b/source4/rpc_server/dnsserver/dnsserver.h
index 83dccf5e6c5..6948fb5d42d 100644
--- a/source4/rpc_server/dnsserver/dnsserver.h
+++ b/source4/rpc_server/dnsserver/dnsserver.h
@@ -249,6 +249,9 @@ WERROR dnsserver_db_update_record(TALLOC_CTX *mem_ctx,
 					const char *node_name,
 					struct DNS_RPC_RECORD *add_record,
 					struct DNS_RPC_RECORD *del_record);
+WERROR dnsserver_db_do_reset_dword(struct ldb_context *samdb,
+					struct dnsserver_zone *z,
+					struct DNS_RPC_NAME_AND_PARAM *n_p);
 WERROR dnsserver_db_delete_record(TALLOC_CTX *mem_ctx,
 				  struct ldb_context *samdb,
 				  struct dnsserver_zone *z,
-- 
2.11.0


From 870df95acec3029c53e2b88df5b5739ab52103ea Mon Sep 17 00:00:00 2001
From: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Date: Tue, 5 Jun 2018 17:12:44 +1200
Subject: [PATCH 06/18] dns: moving name_equal func into common

This function is duplicated in the BIND9 and RPC DNS servers.

Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/dns_server/dlz_bind9.c           | 21 +--------------------
 source4/dns_server/dns_server.h          |  1 -
 source4/dns_server/dnsserver_common.c    | 20 ++++++++++++++++++++
 source4/dns_server/dnsserver_common.h    |  1 +
 source4/rpc_server/dnsserver/dnsdata.c   | 17 -----------------
 source4/rpc_server/dnsserver/dnsserver.h |  1 -
 6 files changed, 22 insertions(+), 39 deletions(-)

diff --git a/source4/dns_server/dlz_bind9.c b/source4/dns_server/dlz_bind9.c
index 0463c6e47a6..e55d73ba50f 100644
--- a/source4/dns_server/dlz_bind9.c
+++ b/source4/dns_server/dlz_bind9.c
@@ -38,7 +38,7 @@
 #include <popt.h>
 #include "lib/util/dlinklist.h"
 #include "dlz_minimal.h"
-#include "dns_server/dnsserver_common.h"
+#include "dnsserver_common.h"
 
 struct b9_options {
 	const char *url;
@@ -1482,25 +1482,6 @@ _PUBLIC_ isc_boolean_t dlz_ssumatch(const char *signer, const char *name, const
 }
 
 /*
-  see if two DNS names are the same
- */
-static bool dns_name_equal(const char *name1, const char *name2)
-{
-	size_t len1 = strlen(name1);
-	size_t len2 = strlen(name2);
-	if (name1[len1 - 1] == '.') {
-		len1--;
-	}
-	if (name2[len2 - 1] == '.') {
-		len2--;
-	}
-	if (len1 != len2) {
-		return false;
-	}
-	return strncasecmp_m(name1, name2, len1) == 0;
-}
-
-/*
   see if two dns records match
  */
 static bool b9_record_match(struct dlz_bind9_data *state,
diff --git a/source4/dns_server/dns_server.h b/source4/dns_server/dns_server.h
index 05ef302680f..48dd5ff8185 100644
--- a/source4/dns_server/dns_server.h
+++ b/source4/dns_server/dns_server.h
@@ -82,7 +82,6 @@ WERROR dns_server_process_update(struct dns_server *dns,
 				 struct dns_res_rec **updates,    uint16_t *update_count,
 				 struct dns_res_rec **additional, uint16_t *arcount);
 
-bool dns_name_equal(const char *name1, const char *name2);
 bool dns_records_match(struct dnsp_DnssrvRpcRecord *rec1,
 		       struct dnsp_DnssrvRpcRecord *rec2);
 bool dns_authoritative_for_zone(struct dns_server *dns,
diff --git a/source4/dns_server/dnsserver_common.c b/source4/dns_server/dnsserver_common.c
index 6c7ab802575..20eaf125ada 100644
--- a/source4/dns_server/dnsserver_common.c
+++ b/source4/dns_server/dnsserver_common.c
@@ -1047,3 +1047,23 @@ NTSTATUS dns_common_zones(struct ldb_context *samdb,
 	TALLOC_FREE(frame);
 	return NT_STATUS_OK;
 }
+
+/*
+  see if two DNS names are the same
+ */
+bool dns_name_equal(const char *name1, const char *name2)
+{
+	size_t len1 = strlen(name1);
+	size_t len2 = strlen(name2);
+
+	if (len1 > 0 && name1[len1 - 1] == '.') {
+		len1--;
+	}
+	if (len2 > 0 && name2[len2 - 1] == '.') {
+		len2--;
+	}
+	if (len1 != len2) {
+		return false;
+	}
+	return strncasecmp(name1, name2, len1) == 0;
+}
diff --git a/source4/dns_server/dnsserver_common.h b/source4/dns_server/dnsserver_common.h
index f2be44ff0d6..e37c7b8f9ab 100644
--- a/source4/dns_server/dnsserver_common.h
+++ b/source4/dns_server/dnsserver_common.h
@@ -68,6 +68,7 @@ WERROR dns_common_name2dn(struct ldb_context *samdb,
 			  TALLOC_CTX *mem_ctx,
 			  const char *name,
 			  struct ldb_dn **_dn);
+bool dns_name_equal(const char *name1, const char *name2);
 
 /*
  * For this routine, base_dn is generally NULL.  The exception comes
diff --git a/source4/rpc_server/dnsserver/dnsdata.c b/source4/rpc_server/dnsserver/dnsdata.c
index 6889cc306ce..59e29f029a6 100644
--- a/source4/rpc_server/dnsserver/dnsdata.c
+++ b/source4/rpc_server/dnsserver/dnsdata.c
@@ -1119,23 +1119,6 @@ int dns_name_compare(const struct ldb_message **m1, const struct ldb_message **m
 	return strcasecmp(ptr1, ptr2);
 }
 
-bool dns_name_equal(const char *name1, const char *name2)
-{
-	size_t len1 = strlen(name1);
-	size_t len2 = strlen(name2);
-
-	if (len1 > 0 && name1[len1 - 1] == '.') {
-		len1--;
-	}
-	if (len2 > 0 && name2[len2 - 1] == '.') {
-		len2--;
-	}
-	if (len1 != len2) {
-		return false;
-	}
-	return strncasecmp(name1, name2, len1) == 0;
-}
-
 bool dns_record_match(struct dnsp_DnssrvRpcRecord *rec1, struct dnsp_DnssrvRpcRecord *rec2)
 {
 	bool status;
diff --git a/source4/rpc_server/dnsserver/dnsserver.h b/source4/rpc_server/dnsserver/dnsserver.h
index 6948fb5d42d..93f1d72f2ef 100644
--- a/source4/rpc_server/dnsserver/dnsserver.h
+++ b/source4/rpc_server/dnsserver/dnsserver.h
@@ -190,7 +190,6 @@ char *dns_split_node_name(TALLOC_CTX *mem_ctx, const char *node_name, const char
 
 int dns_name_compare(const struct ldb_message **m1, const struct ldb_message **m2,
 			char *search_name);
-bool dns_name_equal(const char *name1, const char *name2);
 bool dns_record_match(struct dnsp_DnssrvRpcRecord *rec1, struct dnsp_DnssrvRpcRecord *rec2);
 
 void dnsp_to_dns_copy(TALLOC_CTX *mem_ctx, struct dnsp_DnssrvRpcRecord *dnsp,
-- 
2.11.0


From 5c1a96cc522b17721907087c5c4bce4ad0425eb2 Mon Sep 17 00:00:00 2001
From: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Date: Mon, 2 Jul 2018 13:43:33 +1200
Subject: [PATCH 07/18] dns: server side implementation of record aging

Code for retrieving aging properties from a zone and using them for timestamp
setting logic during processing of DNS requests.

Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/tests/dns.py               |   1 +
 selftest/knownfail.d/dns-scavenging     |   5 --
 source4/dns_server/dns_update.c         |  10 ++-
 source4/dns_server/dnsserver_common.c   | 123 ++++++++++++++++++++++++++++++++
 source4/dns_server/dnsserver_common.h   |   6 ++
 source4/rpc_server/dnsserver/dnsutils.c |   9 ++-
 6 files changed, 145 insertions(+), 9 deletions(-)

diff --git a/python/samba/tests/dns.py b/python/samba/tests/dns.py
index 722b75ce81b..800ce576dec 100644
--- a/python/samba/tests/dns.py
+++ b/python/samba/tests/dns.py
@@ -1104,6 +1104,7 @@ class TestZones(DNSTest):
             self.assertTrue(rec.dwTimeStamp > 0)
             rec.dwTimeStamp -= interval*5
         self.ldap_modify_dnsrecs(name, mod_ts)
+        self.assertTrue(callable(getattr(dsdb, '_scavenge_dns_records', None)))
         dsdb._scavenge_dns_records(self.samdb)
 
         recs = self.ldap_get_dns_records(name)
diff --git a/selftest/knownfail.d/dns-scavenging b/selftest/knownfail.d/dns-scavenging
index 715e14527c9..fe71f17c03c 100644
--- a/selftest/knownfail.d/dns-scavenging
+++ b/selftest/knownfail.d/dns-scavenging
@@ -3,9 +3,4 @@
 #
 # Will be removed once the tests are implemented.
 #
-samba.tests.dns.__main__.TestZones.test_aging_refresh\(fl2003dc:local\)
-samba.tests.dns.__main__.TestZones.test_aging_update\(fl2003dc:local\)
-samba.tests.dns.__main__.TestZones.test_aging_update_disabled\(fl2003dc:local\)
 samba.tests.dns.__main__.TestZones.test_basic_scavenging\(fl2003dc:local\)
-samba.tests.dns.__main__.TestZones.test_set_aging\(fl2003dc:local\)
-samba.tests.dns.__main__.TestZones.test_set_aging_disabled\(fl2003dc:local\)
diff --git a/source4/dns_server/dns_update.c b/source4/dns_server/dns_update.c
index ac3c3e11bae..ebed4495dbd 100644
--- a/source4/dns_server/dns_update.c
+++ b/source4/dns_server/dns_update.c
@@ -300,6 +300,7 @@ static WERROR dns_rr_to_dnsp(TALLOC_CTX *mem_ctx,
 			     struct dnsp_DnssrvRpcRecord *r)
 {
 	enum ndr_err_code ndr_err;
+	NTTIME t;
 
 	if (rrec->rr_type == DNS_QTYPE_ALL) {
 		return DNS_ERR(FORMAT_ERROR);
@@ -310,6 +311,10 @@ static WERROR dns_rr_to_dnsp(TALLOC_CTX *mem_ctx,
 	r->wType = (enum dns_record_type) rrec->rr_type;
 	r->dwTtlSeconds = rrec->ttl;
 	r->rank = DNS_RANK_ZONE;
+	unix_to_nt_time(&t, time(NULL));
+	t /= 10 * 1000 * 1000;
+	t /= 3600;
+	r->dwTimeStamp = t;
 
 	/* If we get QCLASS_ANY, we're done here */
 	if (rrec->rr_class == DNS_QCLASS_ANY) {
@@ -535,7 +540,10 @@ static WERROR handle_one_update(struct dns_server *dns,
 				continue;
 			}
 
-			recs[i] = recs[rcount];
+			recs[i].data = recs[rcount].data;
+			recs[i].wType = recs[rcount].wType;
+			recs[i].dwTtlSeconds = recs[rcount].dwTtlSeconds;
+			recs[i].rank = recs[rcount].rank;
 
 			werror = dns_replace_records(dns, mem_ctx, dn,
 						     needs_add, recs, rcount);
diff --git a/source4/dns_server/dnsserver_common.c b/source4/dns_server/dnsserver_common.c
index 20eaf125ada..2551240fca4 100644
--- a/source4/dns_server/dnsserver_common.c
+++ b/source4/dns_server/dnsserver_common.c
@@ -31,6 +31,7 @@
 #include "dsdb/samdb/samdb.h"
 #include "dsdb/common/util.h"
 #include "dns_server/dnsserver_common.h"
+#include "rpc_server/dnsserver/dnsserver.h"
 #include "lib/util/dlinklist.h"
 
 #undef DBGC_CLASS
@@ -723,6 +724,93 @@ static WERROR check_name_list(TALLOC_CTX *mem_ctx, uint16_t rec_count,
 	return WERR_OK;
 }
 
+WERROR dns_get_zone_properties(struct ldb_context *samdb,
+			       TALLOC_CTX *mem_ctx,
+			       struct ldb_dn *zone_dn,
+			       struct dnsserver_zoneinfo *zoneinfo)
+{
+
+	int ret, i;
+	struct dnsp_DnsProperty *prop = NULL;
+	struct ldb_message_element *element = NULL;
+	const char *const attrs[] = {"dNSProperty", NULL};
+	struct ldb_result *res = NULL;
+	enum ndr_err_code err;
+
+	ret = ldb_search(samdb,
+			 mem_ctx,
+			 &res,
+			 zone_dn,
+			 LDB_SCOPE_BASE,
+			 attrs,
+			 "(objectClass=dnsZone)");
+	if (ret != LDB_SUCCESS) {
+		DBG_ERR("dnsserver: Failed to find DNS zone: %s\n",
+			ldb_dn_get_linearized(zone_dn));
+		return DNS_ERR(SERVER_FAILURE);
+	}
+
+	element = ldb_msg_find_element(res->msgs[0], "dNSProperty");
+	if (element == NULL) {
+		return DNS_ERR(NOTZONE);
+	}
+
+	for (i = 0; i < element->num_values; i++) {
+		prop = talloc_zero(mem_ctx, struct dnsp_DnsProperty);
+		if (prop == NULL) {
+			return WERR_NOT_ENOUGH_MEMORY;
+		}
+		err = ndr_pull_struct_blob(
+		    &(element->values[i]),
+		    mem_ctx,
+		    prop,
+		    (ndr_pull_flags_fn_t)ndr_pull_dnsp_DnsProperty);
+		if (!NDR_ERR_CODE_IS_SUCCESS(err)) {
+			return DNS_ERR(SERVER_FAILURE);
+		}
+
+		switch (prop->id) {
+		case DSPROPERTY_ZONE_AGING_STATE:
+			zoneinfo->fAging = prop->data.aging_enabled;
+			break;
+		case DSPROPERTY_ZONE_NOREFRESH_INTERVAL:
+			zoneinfo->dwNoRefreshInterval =
+			    prop->data.norefresh_hours;
+			break;
+		case DSPROPERTY_ZONE_REFRESH_INTERVAL:
+			zoneinfo->dwRefreshInterval = prop->data.refresh_hours;
+			break;
+		case DSPROPERTY_ZONE_ALLOW_UPDATE:
+			zoneinfo->fAllowUpdate = prop->data.allow_update_flag;
+			break;
+		case DSPROPERTY_ZONE_AGING_ENABLED_TIME:
+			zoneinfo->dwAvailForScavengeTime =
+			    prop->data.next_scavenging_cycle_hours;
+			break;
+		case DSPROPERTY_ZONE_SCAVENGING_SERVERS:
+			zoneinfo->aipScavengeServers->AddrCount =
+			    prop->data.servers.addrCount;
+			zoneinfo->aipScavengeServers->AddrArray =
+			    prop->data.servers.addr;
+			break;
+		case DSPROPERTY_ZONE_EMPTY:
+		case DSPROPERTY_ZONE_TYPE:
+		case DSPROPERTY_ZONE_SECURE_TIME:
+		case DSPROPERTY_ZONE_DELETED_FROM_HOSTNAME:
+		case DSPROPERTY_ZONE_MASTER_SERVERS:
+		case DSPROPERTY_ZONE_AUTO_NS_SERVERS:
+		case DSPROPERTY_ZONE_DCPROMO_CONVERT:
+		case DSPROPERTY_ZONE_SCAVENGING_SERVERS_DA:
+		case DSPROPERTY_ZONE_MASTER_SERVERS_DA:
+		case DSPROPERTY_ZONE_NS_SERVERS_DA:
+		case DSPROPERTY_ZONE_NODE_DBFLAGS:
+			break;
+		}
+	}
+
+	return WERR_OK;
+}
+
 WERROR dns_common_replace(struct ldb_context *samdb,
 			  TALLOC_CTX *mem_ctx,
 			  struct ldb_dn *dn,
@@ -738,12 +826,37 @@ WERROR dns_common_replace(struct ldb_context *samdb,
 	struct ldb_message *msg = NULL;
 	bool was_tombstoned = false;
 	bool become_tombstoned = false;
+	struct ldb_dn *zone_dn = NULL;
+	struct dnsserver_zoneinfo *zoneinfo = NULL;
+	NTTIME t;
 
 	msg = ldb_msg_new(mem_ctx);
 	W_ERROR_HAVE_NO_MEMORY(msg);
 
 	msg->dn = dn;
 
+	zone_dn = ldb_dn_copy(mem_ctx, dn);
+	if (zone_dn == NULL) {
+		return WERR_NOT_ENOUGH_MEMORY;
+	}
+	if (!ldb_dn_remove_child_components(zone_dn, 1)) {
+		return DNS_ERR(SERVER_FAILURE);
+	}
+	zoneinfo = talloc(mem_ctx, struct dnsserver_zoneinfo);
+	if (zoneinfo == NULL) {
+		return WERR_NOT_ENOUGH_MEMORY;
+	}
+	werr = dns_get_zone_properties(samdb, mem_ctx, zone_dn, zoneinfo);
+	if (W_ERROR_EQUAL(DNS_ERR(NOTZONE), werr)) {
+		/*
+		 * We only got zoneinfo for aging so if we didn't find any
+		 * properties then just disable aging and keep going.
+		 */
+		zoneinfo->fAging = 0;
+	} else if (!W_ERROR_IS_OK(werr)) {
+		return werr;
+	}
+
 	werr = check_name_list(mem_ctx, rec_count, records);
 	if (!W_ERROR_IS_OK(werr)) {
 		return werr;
@@ -781,6 +894,16 @@ WERROR dns_common_replace(struct ldb_context *samdb,
 			continue;
 		}
 
+		if (zoneinfo->fAging == 1 && records[i].dwTimeStamp != 0) {
+			unix_to_nt_time(&t, time(NULL));
+			t /= 10 * 1000 * 1000;
+			t /= 3600;
+			if (t - records[i].dwTimeStamp >
+			    zoneinfo->dwNoRefreshInterval) {
+				records[i].dwTimeStamp = t;
+			}
+		}
+
 		records[i].dwSerial = serial;
 		ndr_err = ndr_push_struct_blob(v, el->values, &records[i],
 				(ndr_push_flags_fn_t)ndr_push_dnsp_DnssrvRpcRecord);
diff --git a/source4/dns_server/dnsserver_common.h b/source4/dns_server/dnsserver_common.h
index e37c7b8f9ab..9067e2234e7 100644
--- a/source4/dns_server/dnsserver_common.h
+++ b/source4/dns_server/dnsserver_common.h
@@ -19,6 +19,8 @@
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+#include "rpc_server/dnsserver/dnsserver.h"
+
 #ifndef __DNSSERVER_COMMON_H__
 #define __DNSSERVER_COMMON_H__
 
@@ -55,6 +57,10 @@ WERROR dns_common_wildcard_lookup(struct ldb_context *samdb,
 WERROR dns_name_check(TALLOC_CTX *mem_ctx,
 		      size_t len,
 		      const char *name);
+WERROR dns_get_zone_properties(struct ldb_context *samdb,
+			       TALLOC_CTX *mem_ctx,
+			       struct ldb_dn *zone_dn,
+			       struct dnsserver_zoneinfo *zoneinfo);
 WERROR dns_common_replace(struct ldb_context *samdb,
 			  TALLOC_CTX *mem_ctx,
 			  struct ldb_dn *dn,
diff --git a/source4/rpc_server/dnsserver/dnsutils.c b/source4/rpc_server/dnsserver/dnsutils.c
index 5eb95f8f339..a1c749074af 100644
--- a/source4/rpc_server/dnsserver/dnsutils.c
+++ b/source4/rpc_server/dnsserver/dnsutils.c
@@ -178,9 +178,12 @@ struct dnsserver_serverinfo *dnsserver_init_serverinfo(TALLOC_CTX *mem_ctx,
 	serverinfo->dwDsPollingInterval = 0xB4; /* 3 minutes (default) */
 	serverinfo->dwLocalNetPriorityNetMask = 0x000000FF;
 
-	serverinfo->dwScavengingInterval = 0;
-	serverinfo->dwDefaultRefreshInterval = 0xA8;   /* 7 days in hours */
-	serverinfo->dwDefaultNoRefreshInterval = 0xA8; /* 7 days in hours */
+	serverinfo->dwScavengingInterval = lpcfg_parm_int(
+	    lp_ctx, NULL, "dnsserver", "ScavengingInterval", 24 * 7);
+	serverinfo->dwDefaultRefreshInterval = lpcfg_parm_int(
+	    lp_ctx, NULL, "dnsserver", "DefaultRefreshInterval", 24 * 3);
+	serverinfo->dwDefaultNoRefreshInterval = lpcfg_parm_int(
+	    lp_ctx, NULL, "dnsserver", "DefaultNoRefreshInterval", 24 * 3);
 
 	serverinfo->dwLastScavengeTime = 0;
 
-- 
2.11.0


From f047f161fd627960613c49d5c5892813a93a833e Mon Sep 17 00:00:00 2001
From: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Date: Mon, 2 Jul 2018 13:48:06 +1200
Subject: [PATCH 08/18] dns: custom match rule for DNS records to be tombstoned

A custom match rule for records to be tombstoned by the scavenging process.
Needed because DNS records are a multi-valued attribute on name records, so
without a custom match rule we'd have entire zones into memory to search for
expired records.

Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 lib/ldb-samba/ldb_matching_rules.c  | 148 +++++++++++++++++++++++++++++++++++-
 lib/ldb-samba/ldb_matching_rules.h  |   1 +
 python/samba/tests/dns.py           |  46 +++++++++++
 selftest/knownfail.d/dns            |   3 +
 selftest/knownfail.d/dns-scavenging |   1 +
 source4/setup/schema_samba4.ldif    |   1 +
 6 files changed, 199 insertions(+), 1 deletion(-)

diff --git a/lib/ldb-samba/ldb_matching_rules.c b/lib/ldb-samba/ldb_matching_rules.c
index 5999943dcd2..2a16ca01b17 100644
--- a/lib/ldb-samba/ldb_matching_rules.c
+++ b/lib/ldb-samba/ldb_matching_rules.c
@@ -26,6 +26,7 @@
 #include "ldb_matching_rules.h"
 #include "libcli/security/security.h"
 #include "dsdb/common/util.h"
+#include "librpc/gen_ndr/ndr_dnsp.h"
 
 static int ldb_eval_transitive_filter_helper(TALLOC_CTX *mem_ctx,
 					     struct ldb_context *ldb,
@@ -328,6 +329,139 @@ static int ldb_comparator_trans(struct ldb_context *ldb,
 
 
 /*
+ * This rule provides match of a dns object with expired records.
+ *
+ * This allows a search filter such as:
+ *
+ * dnsRecord:1.3.6.1.4.1.7165.4.5.3:=131139216000000000
+ */
+static int dsdb_match_for_dns_tombstone(struct ldb_context *ldb,
+				        const char *oid,
+				        const struct ldb_message *msg,
+				        const char *attribute_to_match,
+				        const struct ldb_val *value_to_match,
+				        bool *matched)
+{
+	TALLOC_CTX *tmp_ctx;
+	unsigned int i;
+	struct ldb_message_element *el = NULL;
+	struct ldb_message_element *el2 = NULL;
+	struct auth_session_info *session_info = NULL;
+	uint64_t tombstone_time;
+	struct dnsp_DnssrvRpcRecord *rec = NULL;
+	enum ndr_err_code err;
+	*matched = false;
+
+	/* Needs to be dnsRecord, no match otherwise */
+	if (ldb_attr_cmp(attribute_to_match, "dnsRecord") != 0) {
+		return LDB_SUCCESS;
+	}
+
+	el = ldb_msg_find_element(msg, attribute_to_match);
+	if (el == NULL) {
+		return LDB_SUCCESS;
+	}
+
+	session_info = talloc_get_type(ldb_get_opaque(ldb, "sessionInfo"),
+				       struct auth_session_info);
+	if (session_info == NULL) {
+		return ldb_oom(ldb);
+	}
+	if (security_session_user_level(session_info, NULL)
+		!= SECURITY_SYSTEM) {
+
+		DBG_ERR("unauthorised access\n");
+		return LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS;
+	}
+
+	el2 = ldb_msg_find_element(msg, "dNSTombstoned");
+	if (el2 != NULL
+		&& strcasecmp((const char *)el2->values[0].data, "TRUE") == 0) {
+		if (el->num_values > 1) {
+			DBG_WARNING("Possible DB corruption on %s "
+				    "in %s el->num_values = %d\n",
+				    el->name,
+				    ldb_dn_get_linearized(msg->dn),
+				    el->num_values);
+			return LDB_ERR_OPERATIONS_ERROR;
+		}
+		return LDB_SUCCESS;
+	}
+
+	/* Just check we don't allow the caller to fill our stack */
+	if (value_to_match->length >= 64) {
+		DBG_ERR("Invalid timestamp passed\n");
+		return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+	} else {
+		char *p = NULL;
+		char s[value_to_match->length+1];
+		memcpy(s, value_to_match->data, value_to_match->length);
+		s[value_to_match->length] = 0;
+		if (s[0] == '\0' || s[0] == '-') {
+			DBG_ERR("Empty timestamp passed\n");
+			return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+		}
+		tombstone_time = strtoull(s, &p, 10);
+		if (p == NULL || p == s || *p != '\0' ||
+		    tombstone_time == ULLONG_MAX) {
+			DBG_ERR("Invalid timestamp string passed\n");
+			return LDB_ERR_INVALID_ATTRIBUTE_SYNTAX;
+		}
+	}
+
+	tmp_ctx = talloc_new(ldb);
+	if (tmp_ctx == NULL) {
+		return ldb_oom(ldb);
+	}
+
+	for (i = 0; i < el->num_values; i++) {
+		rec = talloc_zero(tmp_ctx, struct dnsp_DnssrvRpcRecord);
+		if (rec == NULL) {
+			TALLOC_FREE(tmp_ctx);
+			return ldb_oom(ldb);
+		}
+		err = ndr_pull_struct_blob(
+			&(el->values[i]),
+			tmp_ctx,
+			rec,
+			(ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
+		if (!NDR_ERR_CODE_IS_SUCCESS(err)){
+			DBG_ERR("Failed to pull dns rec blob.\n");
+			TALLOC_FREE(tmp_ctx);
+			return LDB_ERR_OPERATIONS_ERROR;
+		}
+
+		if (rec->wType == DNS_TYPE_SOA || rec->wType == DNS_TYPE_NS) {
+			TALLOC_FREE(tmp_ctx);
+			continue;
+		}
+
+		if (rec->wType == DNS_TYPE_TOMBSTONE) {
+			DBG_ERR("DNS tombstone record found "
+				 "in non-tombstone DNS object.  Possible DB "
+				 "corruption\n");
+			TALLOC_FREE(tmp_ctx);
+			return LDB_ERR_OPERATIONS_ERROR;
+		}
+		if (rec->dwTimeStamp == 0) {
+			TALLOC_FREE(tmp_ctx);
+			continue;
+		}
+		if (rec->dwTimeStamp > tombstone_time) {
+			TALLOC_FREE(tmp_ctx);
+			continue;
+		}
+
+		*matched = true;
+		break;
+	}
+
+	TALLOC_FREE(tmp_ctx);
+	return LDB_SUCCESS;
+}
+
+
+/*
  * This rule provides match of a link attribute against a 'should be expunged' criteria
  *
  * This allows a search filter such as:
@@ -448,7 +582,8 @@ static int dsdb_match_for_expunge(struct ldb_context *ldb,
 int ldb_register_samba_matching_rules(struct ldb_context *ldb)
 {
 	struct ldb_extended_match_rule *transitive_eval = NULL,
-				       *match_for_expunge = NULL;
+		*match_for_expunge = NULL,
+		*match_for_dns_tombstone = NULL;
 	int ret;
 
 	transitive_eval = talloc_zero(ldb, struct ldb_extended_match_rule);
@@ -469,5 +604,16 @@ int ldb_register_samba_matching_rules(struct ldb_context *ldb)
 		return ret;
 	}
 
+	match_for_dns_tombstone = talloc_zero(
+		ldb,
+		struct ldb_extended_match_rule);
+	match_for_dns_tombstone->oid = DSDB_MATCH_FOR_DNS_TOMBSTONE;
+	match_for_dns_tombstone->callback = dsdb_match_for_dns_tombstone;
+	ret = ldb_register_extended_match_rule(ldb, match_for_dns_tombstone);
+	if (ret != LDB_SUCCESS) {
+		TALLOC_FREE(match_for_dns_tombstone);
+		return ret;
+	}
+
 	return LDB_SUCCESS;
 }
diff --git a/lib/ldb-samba/ldb_matching_rules.h b/lib/ldb-samba/ldb_matching_rules.h
index 421e1ceaec8..d85230d8d0d 100644
--- a/lib/ldb-samba/ldb_matching_rules.h
+++ b/lib/ldb-samba/ldb_matching_rules.h
@@ -25,5 +25,6 @@
 /* This rule provides recursive search of a link attribute */
 #define SAMBA_LDAP_MATCH_RULE_TRANSITIVE_EVAL	"1.2.840.113556.1.4.1941"
 #define DSDB_MATCH_FOR_EXPUNGE	"1.3.6.1.4.1.7165.4.5.2"
+#define DSDB_MATCH_FOR_DNS_TOMBSTONE "1.3.6.1.4.1.7165.4.5.3"
 
 #endif /* _LDB_MATCHING_RULES_H_ */
diff --git a/python/samba/tests/dns.py b/python/samba/tests/dns.py
index 800ce576dec..52505560e1c 100644
--- a/python/samba/tests/dns.py
+++ b/python/samba/tests/dns.py
@@ -1091,6 +1091,52 @@ class TestZones(DNSTest):
         self.assertEqual(len(recs), 1)
         self.assertEqual(recs[0].dwTimeStamp, 0)
 
+    def test_dns_tombstone_custom_match_rule(self):
+        name,txt = 'agingtest', ['test txt']
+        name2,txt2 = 'agingtest2', ['test txt2']
+        name3,txt3 = 'agingtest3', ['test txt3']
+        self.create_zone(self.zone, aging_enabled=True)
+        interval = 10
+        self.set_params(NoRefreshInterval=interval, RefreshInterval=interval,
+                        Aging=1, zone=self.zone,
+                        AllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE)
+
+        self.dns_update_record(name, txt),
+
+        self.dns_update_record(name2, txt),
+        self.dns_update_record(name2, txt2),
+
+        self.dns_update_record(name3, txt),
+        self.dns_update_record(name3, txt2),
+        last_update = self.dns_update_record(name3, txt3)
+
+        # Modify txt1 of the first 2 names
+        def mod_ts(rec):
+            if rec.data.str == txt:
+                rec.dwTimeStamp -= 2
+        self.ldap_modify_dnsrecs(name, mod_ts)
+        self.ldap_modify_dnsrecs(name2, mod_ts)
+
+        recs = self.ldap_get_dns_records(name3)
+        expr = "(dnsRecord:1.3.6.1.4.1.7165.4.5.3:={})"
+        expr = expr.format(int(last_update.dwTimeStamp)-1)
+        try:
+            res = self.samdb.search(base=self.zone_dn, scope=ldb.SCOPE_SUBTREE,
+                                    expression=expr, attrs=["*"])
+        except ldb.LdbError as e:
+            self.fail(str(e))
+        updated_names = {str(r.get('name')) for r in res}
+        self.assertEqual(updated_names, set([name, name2]))
+
+    def test_dns_tombstone_custom_match_rule_fail(self):
+        self.create_zone(self.zone, aging_enabled=True)
+
+        # The check here is that this does not blow up on silly input
+        expr = "(dnsProperty:1.3.6.1.4.1.7165.4.5.3:=1)"
+        res = self.samdb.search(base=self.zone_dn, scope=ldb.SCOPE_SUBTREE,
+                                expression=expr, attrs=["*"])
+        self.assertEquals(len(res), 0)
+
     def test_basic_scavenging(self):
         self.create_zone(self.zone, aging_enabled=True)
         interval = 1
diff --git a/selftest/knownfail.d/dns b/selftest/knownfail.d/dns
index 4beeabf4386..5a3e456b9c8 100644
--- a/selftest/knownfail.d/dns
+++ b/selftest/knownfail.d/dns
@@ -44,12 +44,15 @@ samba.tests.dns.__main__.TestZones.test_aging_update_disabled\(rodc:local\)
 samba.tests.dns.__main__.TestZones.test_aging_refresh\(rodc:local\)
 samba.tests.dns.__main__.TestZones.test_rpc_add_no_timestamp\(rodc:local\)
 samba.tests.dns.__main__.TestZones.test_basic_scavenging\(rodc:local\)
+samba.tests.dns.__main__.TestZones.test_dns_tombstone_custom_match_rule\(rodc:local\)
+samba.tests.dns.__main__.TestZones.test_dns_tombstone_custom_match_rule_fail\(rodc:local\)
 
 samba.tests.dns.__main__.TestZones.test_set_aging\(vampire_dc:local\)
 samba.tests.dns.__main__.TestZones.test_aging_update\(vampire_dc:local\)
 samba.tests.dns.__main__.TestZones.test_aging_update_disabled\(vampire_dc:local\)
 samba.tests.dns.__main__.TestZones.test_aging_refresh\(vampire_dc:local\)
 samba.tests.dns.__main__.TestZones.test_basic_scavenging\(vampire_dc:local\)
+samba.tests.dns.__main__.TestZones.test_dns_tombstone_custom_match_rule\(vampire_dc:local\)
 
 samba.tests.dns.__main__.TestComplexQueries.test_cname_two_chain\(vampire_dc:local\)
 samba.tests.dns.__main__.TestComplexQueries.test_one_a_query\(vampire_dc:local\)
diff --git a/selftest/knownfail.d/dns-scavenging b/selftest/knownfail.d/dns-scavenging
index fe71f17c03c..8de310186d1 100644
--- a/selftest/knownfail.d/dns-scavenging
+++ b/selftest/knownfail.d/dns-scavenging
@@ -4,3 +4,4 @@
 # Will be removed once the tests are implemented.
 #
 samba.tests.dns.__main__.TestZones.test_basic_scavenging\(fl2003dc:local\)
+samba.tests.dns.__main__.TestZones.test_dns_tombstone_custom_match_rule\(fl2003dc:local\)
diff --git a/source4/setup/schema_samba4.ldif b/source4/setup/schema_samba4.ldif
index 5b26dc0bee5..25a20298d63 100644
--- a/source4/setup/schema_samba4.ldif
+++ b/source4/setup/schema_samba4.ldif
@@ -246,6 +246,7 @@
 # ldap extended matches
 #Allocated: SAMBA_LDAP_MATCH_ALWAYS_FALSE 1.3.6.1.4.1.7165.4.5.1
 #Allocated: DSDB_MATCH_FOR_EXPUNGE 1.3.6.1.4.1.7165.4.5.2
+#Allocated: DSDB_MATCH_FOR_DNS_TOMBSTONE 1.3.6.1.4.1.7165.4.5.3
 
 
 #Allocated: (middleName) attributeID: 1.3.6.1.4.1.7165.4.255.1
-- 
2.11.0


From e2bdb7968594ea3914c883323aa2dc909222b8ce Mon Sep 17 00:00:00 2001
From: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Date: Tue, 10 Jul 2018 13:23:42 +1200
Subject: [PATCH 09/18] dns: Use ldb.SCOPE_SUBTREE in ldap_get_records()
 routine in tests/dns.py

DNS records have the odd property that the DN can be reliably determined by the
name only, so we do not need a subtree search.

However by using a subtree search under the zone we can without
trapping exceptions confirm if the record exists or not in the tests.

Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Signed-off-by: Andrew Bartlett <abartlet at samba.org>
Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
---
 python/samba/tests/dns.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/python/samba/tests/dns.py b/python/samba/tests/dns.py
index 52505560e1c..c0f71dbf633 100644
--- a/python/samba/tests/dns.py
+++ b/python/samba/tests/dns.py
@@ -969,9 +969,11 @@ class TestZones(DNSTest):
         return recs[0]
 
     def ldap_get_records(self, name):
-        dn = 'DC={},{}'.format(name, self.zone_dn)
+        # The use of SCOPE_SUBTREE here avoids raising an exception in the
+        # 0 results case for a test below.
+
         expr = "(&(objectClass=dnsNode)(name={}))".format(name)
-        return self.samdb.search(base=dn, scope=ldb.SCOPE_SUBTREE,
+        return self.samdb.search(base=self.zone_dn, scope=ldb.SCOPE_SUBTREE,
                                  expression=expr, attrs=["*"])
 
     def ldap_get_dns_records(self, name):
-- 
2.11.0


From e01c8e4bd5b1bf525e997b16975371e1d85ed1e5 Mon Sep 17 00:00:00 2001
From: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Date: Fri, 1 Jun 2018 16:07:46 +1200
Subject: [PATCH 10/18] dns: dns record scavenging function (without task)

DNS record scavenging function with testing.  The logic of the custom match rule
in previous commit is inverted so that calculations using zone properties can
be taken out of the function's inner loop. Periodic task to come.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=10812

Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/tests/dns.py            | 49 ++++++++++++++++++++++--
 selftest/knownfail.d/dns-scavenging  |  2 -
 source4/dsdb/kcc/kcc_service.h       |  1 +
 source4/dsdb/pydsdb.c                | 73 ++++++++++++++++++++++++++++++++++++
 source4/dsdb/wscript_build           | 10 ++++-
 source4/rpc_server/dnsserver/dnsdb.c |  2 +-
 6 files changed, 128 insertions(+), 9 deletions(-)

diff --git a/python/samba/tests/dns.py b/python/samba/tests/dns.py
index c0f71dbf633..faf4c524076 100644
--- a/python/samba/tests/dns.py
+++ b/python/samba/tests/dns.py
@@ -1094,6 +1094,11 @@ class TestZones(DNSTest):
         self.assertEqual(recs[0].dwTimeStamp, 0)
 
     def test_dns_tombstone_custom_match_rule(self):
+        lp = self.get_loadparm()
+        self.samdb = SamDB(url = lp.samdb_url(), lp = lp,
+                           session_info=system_session(),
+                           credentials=self.creds)
+
         name,txt = 'agingtest', ['test txt']
         name2,txt2 = 'agingtest2', ['test txt2']
         name3,txt3 = 'agingtest3', ['test txt3']
@@ -1140,27 +1145,63 @@ class TestZones(DNSTest):
         self.assertEquals(len(res), 0)
 
     def test_basic_scavenging(self):
+        lp = self.get_loadparm()
+        self.samdb = SamDB(url = lp.samdb_url(), lp = lp,
+                           session_info=system_session(),
+                           credentials=self.creds)
+
         self.create_zone(self.zone, aging_enabled=True)
         interval = 1
         self.set_params(NoRefreshInterval=interval, RefreshInterval=interval,
                         zone=self.zone, Aging=1,
                         AllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE)
         name, txt = 'agingtest', ['test txt']
-        rec = self.dns_update_record(name,txt)
-        rec = self.dns_update_record(name+'2',txt)
+        name2, txt2 = 'agingtest2', ['test txt2']
+        name3, txt3 = 'agingtest3', ['test txt3']
+        self.dns_update_record(name,txt)
+        self.dns_update_record(name2,txt)
+        self.dns_update_record(name2,txt2)
+        self.dns_update_record(name3,txt)
+        self.dns_update_record(name3,txt2)
+        last_add = self.dns_update_record(name3,txt3)
+
         def mod_ts(rec):
             self.assertTrue(rec.dwTimeStamp > 0)
-            rec.dwTimeStamp -= interval*5
+            if rec.data.str == txt:
+                rec.dwTimeStamp -= interval*5
         self.ldap_modify_dnsrecs(name, mod_ts)
+        self.ldap_modify_dnsrecs(name2, mod_ts)
+        self.ldap_modify_dnsrecs(name3, mod_ts)
         self.assertTrue(callable(getattr(dsdb, '_scavenge_dns_records', None)))
         dsdb._scavenge_dns_records(self.samdb)
 
         recs = self.ldap_get_dns_records(name)
         self.assertEqual(len(recs), 1)
         self.assertEqual(recs[0].wType, dnsp.DNS_TYPE_TOMBSTONE)
-        recs = self.ldap_get_dns_records(name+'2')
+
+        recs = self.ldap_get_dns_records(name2)
         self.assertEqual(len(recs), 1)
         self.assertEqual(recs[0].wType, dnsp.DNS_TYPE_TXT)
+        self.assertEqual(recs[0].data.str, txt2)
+
+        recs = self.ldap_get_dns_records(name3)
+        self.assertEqual(len(recs), 2)
+        txts = {str(r.data.str) for r in recs}
+        self.assertEqual(txts, {str(txt2),str(txt3)})
+        self.assertEqual(recs[0].wType, dnsp.DNS_TYPE_TXT)
+        self.assertEqual(recs[1].wType, dnsp.DNS_TYPE_TXT)
+
+        for make_it_work in [False, True]:
+            inc = -1 if make_it_work else 1
+            def mod_ts(rec):
+                rec.data = (last_add.dwTimeStamp - 24*14) + inc
+            self.ldap_modify_dnsrecs(name, mod_ts)
+            dsdb._dns_delete_tombstones(self.samdb)
+            recs = self.ldap_get_records(name)
+            if make_it_work:
+                self.assertEqual(len(recs), 0)
+            else:
+                self.assertEqual(len(recs), 1)
 
     def delete_zone(self, zone):
         self.rpc_conn.DnssrvOperation2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
diff --git a/selftest/knownfail.d/dns-scavenging b/selftest/knownfail.d/dns-scavenging
index 8de310186d1..86c0fa81e37 100644
--- a/selftest/knownfail.d/dns-scavenging
+++ b/selftest/knownfail.d/dns-scavenging
@@ -3,5 +3,3 @@
 #
 # Will be removed once the tests are implemented.
 #
-samba.tests.dns.__main__.TestZones.test_basic_scavenging\(fl2003dc:local\)
-samba.tests.dns.__main__.TestZones.test_dns_tombstone_custom_match_rule\(fl2003dc:local\)
diff --git a/source4/dsdb/kcc/kcc_service.h b/source4/dsdb/kcc/kcc_service.h
index b62fb12f1d0..a4569033866 100644
--- a/source4/dsdb/kcc/kcc_service.h
+++ b/source4/dsdb/kcc/kcc_service.h
@@ -91,6 +91,7 @@ struct kccsrv_service {
 struct kcc_connection_list;
 
 #include "dsdb/kcc/garbage_collect_tombstones.h"
+#include "dsdb/kcc/scavenge_dns_records.h"
 #include "dsdb/kcc/kcc_service_proto.h"
 
 #endif /* _DSDB_REPL_KCC_SERVICE_H_ */
diff --git a/source4/dsdb/pydsdb.c b/source4/dsdb/pydsdb.c
index a25f3411ae7..62d2a9120a9 100644
--- a/source4/dsdb/pydsdb.c
+++ b/source4/dsdb/pydsdb.c
@@ -32,6 +32,7 @@
 #include "param/pyparam.h"
 #include "lib/util/dlinklist.h"
 #include "dsdb/kcc/garbage_collect_tombstones.h"
+#include "dsdb/kcc/scavenge_dns_records.h"
 
 
 /* FIXME: These should be in a header file somewhere */
@@ -1150,6 +1151,74 @@ static PyObject *py_dsdb_allocate_rid(PyObject *self, PyObject *args)
 	return PyInt_FromLong(rid);
 }
 
+static PyObject *py_dns_delete_tombstones(PyObject *self, PyObject *args)
+{
+	PyObject *py_ldb;
+	NTSTATUS status;
+	struct ldb_context *ldb = NULL;
+	TALLOC_CTX *mem_ctx = NULL;
+	char *error_string = NULL;
+
+	if (!PyArg_ParseTuple(args, "O", &py_ldb)) {
+		return NULL;
+	}
+	PyErr_LDB_OR_RAISE(py_ldb, ldb);
+
+	mem_ctx = talloc_new(ldb);
+	if (mem_ctx == NULL) {
+		return PyErr_NoMemory();
+	}
+
+	status = dns_delete_tombstones(mem_ctx, ldb, &error_string);
+
+	if (!NT_STATUS_IS_OK(status)) {
+		if (error_string) {
+			PyErr_Format(PyExc_RuntimeError, "%s", error_string);
+		} else {
+			PyErr_SetNTSTATUS(status);
+		}
+		TALLOC_FREE(mem_ctx);
+		return NULL;
+	}
+
+	TALLOC_FREE(mem_ctx);
+	Py_RETURN_NONE;
+}
+
+static PyObject *py_scavenge_dns_records(PyObject *self, PyObject *args)
+{
+	PyObject *py_ldb;
+	NTSTATUS status;
+	struct ldb_context *ldb = NULL;
+	TALLOC_CTX *mem_ctx = NULL;
+	char *error_string = NULL;
+
+	if (!PyArg_ParseTuple(args, "O", &py_ldb)) {
+		return NULL;
+	}
+	PyErr_LDB_OR_RAISE(py_ldb, ldb);
+
+	mem_ctx = talloc_new(ldb);
+	if (mem_ctx == NULL) {
+		return PyErr_NoMemory();
+	}
+
+	status = dns_tombstone_records(mem_ctx, ldb, &error_string);
+
+	if (!NT_STATUS_IS_OK(status)) {
+		if (error_string) {
+			PyErr_Format(PyExc_RuntimeError, "%s", error_string);
+		} else {
+			PyErr_SetNTSTATUS(status);
+		}
+		TALLOC_FREE(mem_ctx);
+		return NULL;
+	}
+
+	TALLOC_FREE(mem_ctx);
+	Py_RETURN_NONE;
+}
+
 static PyObject *py_dsdb_garbage_collect_tombstones(PyObject *self, PyObject *args)
 {
 	PyObject *py_ldb, *py_list_dn;
@@ -1383,6 +1452,10 @@ static PyMethodDef py_dsdb_methods[] = {
 	{ "_dsdb_garbage_collect_tombstones", (PyCFunction)py_dsdb_garbage_collect_tombstones, METH_VARARGS,
 		"_dsdb_kcc_check_deleted(samdb, [dn], current_time, tombstone_lifetime)"
 		" -> (num_objects_expunged, num_links_expunged)" },
+	{ "_scavenge_dns_records", (PyCFunction)py_scavenge_dns_records,
+		METH_VARARGS, NULL},
+	{ "_dns_delete_tombstones", (PyCFunction)py_dns_delete_tombstones,
+		METH_VARARGS, NULL},
 	{ "_dsdb_create_own_rid_set", (PyCFunction)py_dsdb_create_own_rid_set, METH_VARARGS,
 		"_dsdb_create_own_rid_set(samdb)"
 		" -> None" },
diff --git a/source4/dsdb/wscript_build b/source4/dsdb/wscript_build
index e1f1e61c55e..be99e9950ef 100644
--- a/source4/dsdb/wscript_build
+++ b/source4/dsdb/wscript_build
@@ -42,12 +42,17 @@ bld.SAMBA_LIBRARY('dsdb_garbage_collect_tombstones',
                   deps='samdb RPC_NDR_DRSUAPI',
                   private_library=True)
 
+bld.SAMBA_LIBRARY('scavenge_dns_records',
+                  source='kcc/scavenge_dns_records.c',
+                  deps='samdb RPC_NDR_DRSUAPI dnsserver_common',
+                  private_library=True)
+
 bld.SAMBA_MODULE('service_kcc',
 	source='kcc/kcc_service.c kcc/kcc_connection.c kcc/kcc_periodic.c kcc/kcc_drs_replica_info.c',
 	autoproto='kcc/kcc_service_proto.h',
 	subsystem='service',
 	init_function='server_service_kcc_init',
-	deps='samdb process_model RPC_NDR_IRPC RPC_NDR_DRSUAPI UTIL_RUNCMD dsdb_garbage_collect_tombstones',
+	deps='samdb process_model RPC_NDR_IRPC RPC_NDR_DRSUAPI UTIL_RUNCMD dsdb_garbage_collect_tombstones scavenge_dns_records',
 	internal_module=False,
 	enabled=bld.AD_DC_BUILD_IS_ENABLED()
 	)
@@ -71,6 +76,7 @@ for env in bld.gen_python_environments():
 		# the dependency on dcerpc here is because gensec
 		# depends on dcerpc but the waf circular dependency finder
 		# removes it so we end up with unresolved symbols.
-		deps='samdb %s dcerpc com_err %s %s dsdb_garbage_collect_tombstones' % (pyldb_util, pyrpc_util, pyparam_util),
+		deps='samdb %s dcerpc com_err %s %s dsdb_garbage_collect_tombstones scavenge_dns_records' %\
+			 (pyldb_util, pyrpc_util, pyparam_util),
 		realname='samba/dsdb.so'
 		)
diff --git a/source4/rpc_server/dnsserver/dnsdb.c b/source4/rpc_server/dnsserver/dnsdb.c
index 350e29aa1e0..899c7ecedb6 100644
--- a/source4/rpc_server/dnsserver/dnsdb.c
+++ b/source4/rpc_server/dnsserver/dnsdb.c
@@ -145,7 +145,7 @@ struct dnsserver_zone *dnsserver_db_enumerate_zones(TALLOC_CTX *mem_ctx,
 
 		element = ldb_msg_find_element(res->msgs[i], "dNSProperty");
 		if(element != NULL){
-			props = talloc_zero_array(mem_ctx,
+			props = talloc_zero_array(tmp_ctx,
 						  struct dnsp_DnsProperty,
 						  element->num_values);
 			for (j = 0; j < element->num_values; j++ ) {
-- 
2.11.0


From d0b84a916988ed156424687022e89a6855073f55 Mon Sep 17 00:00:00 2001
From: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Date: Tue, 29 May 2018 15:50:19 +1200
Subject: [PATCH 11/18] dns+kcc: adding dns scavenging to kcc periodic run

BUG: https://bugzilla.samba.org/show_bug.cgi?id=10812

Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 selftest/knownfail.d/dns-scavenging |  5 ----
 source4/dsdb/kcc/kcc_periodic.c     | 50 +++++++++++++++++++++++++++++++++++--
 source4/dsdb/kcc/kcc_service.h      |  4 +++
 3 files changed, 52 insertions(+), 7 deletions(-)
 delete mode 100644 selftest/knownfail.d/dns-scavenging

diff --git a/selftest/knownfail.d/dns-scavenging b/selftest/knownfail.d/dns-scavenging
deleted file mode 100644
index 86c0fa81e37..00000000000
--- a/selftest/knownfail.d/dns-scavenging
+++ /dev/null
@@ -1,5 +0,0 @@
-#
-# Tests added for the dns scavenging changes
-#
-# Will be removed once the tests are implemented.
-#
diff --git a/source4/dsdb/kcc/kcc_periodic.c b/source4/dsdb/kcc/kcc_periodic.c
index 855b82b4fd5..980e14405bd 100644
--- a/source4/dsdb/kcc/kcc_periodic.c
+++ b/source4/dsdb/kcc/kcc_periodic.c
@@ -601,8 +601,22 @@ WERROR kccsrv_periodic_schedule(struct kccsrv_service *service, uint32_t next_in
 static NTSTATUS kccsrv_check_deleted(struct kccsrv_service *s, TALLOC_CTX *mem_ctx)
 {
 	time_t current_time = time(NULL);
-	time_t interval = lpcfg_parm_int(
-	    s->task->lp_ctx, NULL, "kccsrv", "check_deleted_interval", 86400);
+	time_t interval = lpcfg_parm_int(s->task->lp_ctx,
+					 NULL,
+					 "kccsrv",
+					 "check_deleted_interval",
+					 24 * 60 * 60);
+	time_t dns_scavenge_interval = lpcfg_parm_int(s->task->lp_ctx,
+						      NULL,
+						      "dnsserver",
+						      "scavenging_interval",
+						      2 * 60 * 60);
+	time_t dns_collection_interval =
+	    lpcfg_parm_int(s->task->lp_ctx,
+			   NULL,
+			   "dnsserver",
+			   "tombstone_collection_interval",
+			   24 * 60 * 60);
 	uint32_t tombstoneLifetime;
 	int ret;
 	unsigned int num_objects_removed = 0;
@@ -610,6 +624,38 @@ static NTSTATUS kccsrv_check_deleted(struct kccsrv_service *s, TALLOC_CTX *mem_c
 	NTSTATUS status;
 	char *error_string = NULL;
 
+	if ((current_time - s->last_dns_scavenge) > dns_scavenge_interval) {
+		s->last_dns_scavenge = current_time;
+		status = dns_tombstone_records(mem_ctx, s->samdb,
+					       &error_string);
+		if (!NT_STATUS_IS_OK(status)) {
+			const char *err = NULL;
+			if (error_string != NULL) {
+				err = error_string;
+			} else {
+				err = nt_errstr(status);
+			}
+			DBG_ERR("DNS record scavenging process failed: %s",
+				err);
+		}
+	}
+
+	if ((current_time - s->last_dns_tombstone_collection) >
+	    dns_collection_interval) {
+		s->last_dns_tombstone_collection = current_time;
+		status = dns_delete_tombstones(mem_ctx, s->samdb,
+					       &error_string);
+		if (!NT_STATUS_IS_OK(status)) {
+			const char *err = NULL;
+			if (error_string != NULL) {
+				err = error_string;
+			} else {
+				err = nt_errstr(status);
+			}
+			DBG_ERR("DNS tombstone deletion failed: %s", err);
+		}
+	}
+
 	if (current_time - s->last_deleted_check < interval) {
 		return NT_STATUS_OK;
 	}
diff --git a/source4/dsdb/kcc/kcc_service.h b/source4/dsdb/kcc/kcc_service.h
index a4569033866..00122ab84c7 100644
--- a/source4/dsdb/kcc/kcc_service.h
+++ b/source4/dsdb/kcc/kcc_service.h
@@ -80,6 +80,10 @@ struct kccsrv_service {
 
 	time_t last_deleted_check;
 
+	time_t last_dns_scavenge;
+
+	time_t last_dns_tombstone_collection;
+
 	time_t last_full_scan_deleted_check;
 
 	bool am_rodc;
-- 
2.11.0


From 2a6856ffe21053e28ba18ed9aabcbfc370159ca5 Mon Sep 17 00:00:00 2001
From: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Date: Tue, 10 Jul 2018 13:14:18 +1200
Subject: [PATCH 12/18] dns: update tool changed for scavenging

Now that scavenging is implemented, the DNS update tool needs to be changed so
that it always updates every name required by the DC.  Otherwise, the records
might be scavenged.

Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/tests/blackbox/samba_dnsupdate.py | 18 +++++++++++++-----
 selftest/knownfail.d/dns                       |  2 ++
 source4/scripting/bin/samba_dnsupdate          | 10 +---------
 3 files changed, 16 insertions(+), 14 deletions(-)

diff --git a/python/samba/tests/blackbox/samba_dnsupdate.py b/python/samba/tests/blackbox/samba_dnsupdate.py
index e6cad3bbaba..c4f14b319dc 100644
--- a/python/samba/tests/blackbox/samba_dnsupdate.py
+++ b/python/samba/tests/blackbox/samba_dnsupdate.py
@@ -23,6 +23,7 @@ from samba.credentials import Credentials
 from samba.auth import system_session
 from samba.samdb import SamDB
 import ldb
+import shutil, os
 
 class SambaDnsUpdateTests(samba.tests.BlackboxTestCase):
     """Blackbox test case for samba_dnsupdate."""
@@ -37,7 +38,10 @@ class SambaDnsUpdateTests(samba.tests.BlackboxTestCase):
             pass
 
     def test_samba_dnsupate_no_change(self):
-        out = self.check_output("samba_dnsupdate --verbose")
+        try:
+            out = self.check_output("samba_dnsupdate --verbose")
+        except samba.tests.BlackboxProcessError as e:
+            self.fail("Error calling samba_dnsupdate: %s" % e)
         self.assertTrue("No DNS updates needed" in out, out)
 
     def test_samba_dnsupate_set_ip(self):
@@ -83,6 +87,9 @@ class SambaDnsUpdateTests(samba.tests.BlackboxTestCase):
         self.creds = Credentials()
         self.creds.guess(self.lp)
         self.session = system_session()
+        uc_fn = self.lp.private_path('dns_update_cache')
+        tmp_uc = uc_fn + '_tmp'
+        shutil.copyfile(uc_fn, tmp_uc)
 
         self.samdb = SamDB(session_info=self.session,
                            credentials=self.creds,
@@ -97,13 +104,14 @@ class SambaDnsUpdateTests(samba.tests.BlackboxTestCase):
             self.samdb.get_config_basedn()),
             ldb.FLAG_MOD_ADD, "siteList")
 
-        out = self.check_output("samba_dnsupdate --verbose")
-        self.assertTrue("No DNS updates needed" in out, out)
+        dns_c = "samba_dnsupdate --verbose --use-file={}".format(tmp_uc)
+        out = self.check_output(dns_c)
+        self.assertFalse(site_name.lower() in out, out)
 
         self.samdb.modify(m)
 
-        out = self.check_output("samba_dnsupdate --verbose --use-samba-tool"
-                                " --rpc-server-ip={}".format(self.server_ip))
+        shutil.copyfile(uc_fn, tmp_uc)
+        out = self.check_output(dns_c)
 
         self.assertFalse("No DNS updates needed" in out, out)
         self.assertTrue(site_name.lower() in out, out)
diff --git a/selftest/knownfail.d/dns b/selftest/knownfail.d/dns
index 5a3e456b9c8..7ae19f65e33 100644
--- a/selftest/knownfail.d/dns
+++ b/selftest/knownfail.d/dns
@@ -65,3 +65,5 @@ samba.tests.dns.__main__.TestSimpleQueries.test_qtype_all_query\(rodc:local\)
 
 # The SOA override should not pass against the RODC, it must not overstamp
 samba.tests.dns.__main__.TestSimpleQueries.test_one_SOA_query\(rodc:local\)
+.*samba.tests.blackbox.samba_dnsupdate.SambaDnsUpdateTests.test_samba_dnsupate_set_ip
+.*samba.tests.blackbox.samba_dnsupdate.SambaDnsUpdateTests.test_samba_dnsupate_no_change
diff --git a/source4/scripting/bin/samba_dnsupdate b/source4/scripting/bin/samba_dnsupdate
index 2d3fedefbc5..071cebee7ee 100755
--- a/source4/scripting/bin/samba_dnsupdate
+++ b/source4/scripting/bin/samba_dnsupdate
@@ -847,15 +847,7 @@ for d in dns_list:
         rebuild_cache = True
         if opts.verbose:
             print "need cache add: %s" % d
-    if opts.all_names:
-        update_list.append(d)
-        if opts.verbose:
-            print "force update: %s" % d
-    elif not check_dns_name(d):
-        update_list.append(d)
-        if opts.verbose:
-            print "need update: %s" % d
-
+    update_list.append(d)
 
 for c in cache_list:
     found = False
-- 
2.11.0


From fd2cafbf3647cd57f68f47746ab783a6d92163d7 Mon Sep 17 00:00:00 2001
From: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Date: Thu, 7 Jun 2018 16:51:37 +1200
Subject: [PATCH 13/18] dns: static records

Modifies bind9 and internal dns to match windows static records behaviour.

Signed-off-by: Aaron Haslett <aaronhaslett at catalyst.net.nz>
Reviewed-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/tests/dns.py             | 36 +++++++++++++++++++++++++++++++++
 selftest/knownfail.d/dns              |  4 ++++
 source4/dns_server/dlz_bind9.c        | 20 +++++++++---------
 source4/dns_server/dns_update.c       | 38 ++++++++++++++++++++++++-----------
 source4/dns_server/dnsserver_common.c | 17 ++++++++++++++++
 source4/dns_server/dnsserver_common.h |  2 ++
 6 files changed, 94 insertions(+), 23 deletions(-)

diff --git a/python/samba/tests/dns.py b/python/samba/tests/dns.py
index faf4c524076..508d49f47d8 100644
--- a/python/samba/tests/dns.py
+++ b/python/samba/tests/dns.py
@@ -1093,6 +1093,42 @@ class TestZones(DNSTest):
         self.assertEqual(len(recs), 1)
         self.assertEqual(recs[0].dwTimeStamp, 0)
 
+    def test_static_record_dynamic_update(self):
+        name,txt = 'agingtest', ['test txt']
+        txt2 = ['test txt2']
+        self.set_aging(enable=True)
+        rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+        rec_buf.rec = TXTRecord(txt)
+        self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                                          0, self.server_ip,
+                                          self.zone, name, rec_buf, None)
+
+        rec2 = self.dns_update_record(name, txt2)
+        self.assertEqual(rec2.dwTimeStamp, 0)
+
+    def test_dynamic_record_static_update(self):
+        name,txt = 'agingtest', ['test txt']
+        txt2 = ['test txt2']
+        txt3 = ['test txt3']
+        self.set_aging(enable=True)
+
+        self.dns_update_record(name, txt)
+
+        rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+        rec_buf.rec = TXTRecord(txt2)
+        self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                                          0, self.server_ip,
+                                          self.zone, name, rec_buf, None)
+
+        self.dns_update_record(name, txt3)
+
+        recs = self.ldap_get_dns_records(name)
+        # Put in dict because ldap recs might be out of order
+        recs = {str(r.data.str):r for r in recs}
+        self.assertNotEqual(recs[str(txt)].dwTimeStamp, 0)
+        self.assertEqual(recs[str(txt2)].dwTimeStamp, 0)
+        self.assertEqual(recs[str(txt3)].dwTimeStamp, 0)
+
     def test_dns_tombstone_custom_match_rule(self):
         lp = self.get_loadparm()
         self.samdb = SamDB(url = lp.samdb_url(), lp = lp,
diff --git a/selftest/knownfail.d/dns b/selftest/knownfail.d/dns
index 7ae19f65e33..99b0f1d63a0 100644
--- a/selftest/knownfail.d/dns
+++ b/selftest/knownfail.d/dns
@@ -46,6 +46,8 @@ samba.tests.dns.__main__.TestZones.test_rpc_add_no_timestamp\(rodc:local\)
 samba.tests.dns.__main__.TestZones.test_basic_scavenging\(rodc:local\)
 samba.tests.dns.__main__.TestZones.test_dns_tombstone_custom_match_rule\(rodc:local\)
 samba.tests.dns.__main__.TestZones.test_dns_tombstone_custom_match_rule_fail\(rodc:local\)
+samba.tests.dns.__main__.TestZones.test_dynamic_record_static_update\(rodc:local\)
+samba.tests.dns.__main__.TestZones.test_static_record_dynamic_update\(rodc:local\)
 
 samba.tests.dns.__main__.TestZones.test_set_aging\(vampire_dc:local\)
 samba.tests.dns.__main__.TestZones.test_aging_update\(vampire_dc:local\)
@@ -53,6 +55,8 @@ samba.tests.dns.__main__.TestZones.test_aging_update_disabled\(vampire_dc:local\
 samba.tests.dns.__main__.TestZones.test_aging_refresh\(vampire_dc:local\)
 samba.tests.dns.__main__.TestZones.test_basic_scavenging\(vampire_dc:local\)
 samba.tests.dns.__main__.TestZones.test_dns_tombstone_custom_match_rule\(vampire_dc:local\)
+samba.tests.dns.__main__.TestZones.test_dynamic_record_static_update\(vampire_dc:local\)
+samba.tests.dns.__main__.TestZones.test_static_record_dynamic_update\(vampire_dc:local\)
 
 samba.tests.dns.__main__.TestComplexQueries.test_cname_two_chain\(vampire_dc:local\)
 samba.tests.dns.__main__.TestComplexQueries.test_one_a_query\(vampire_dc:local\)
diff --git a/source4/dns_server/dlz_bind9.c b/source4/dns_server/dlz_bind9.c
index e55d73ba50f..5f9a71dd741 100644
--- a/source4/dns_server/dlz_bind9.c
+++ b/source4/dns_server/dlz_bind9.c
@@ -1631,18 +1631,7 @@ _PUBLIC_ isc_result_t dlz_addrdataset(const char *name, const char *rdatastr, vo
 		return ISC_R_NOMEMORY;
 	}
 
-	unix_to_nt_time(&t, time(NULL));
-	/*
-	 * convert to seconds (NT time is in 100ns units)
-	 */
-	t /= 10 * 1000 * 1000;
-	/*
-	 * convert to hours
-	 */
-	t /= 3600;
-
 	rec->rank        = DNS_RANK_ZONE;
-	rec->dwTimeStamp = (uint32_t)t;
 
 	if (!b9_parse(state, rdatastr, rec)) {
 		state->log(ISC_LOG_INFO, "samba_dlz: failed to parse rdataset '%s'", rdatastr);
@@ -1704,6 +1693,15 @@ _PUBLIC_ isc_result_t dlz_addrdataset(const char *name, const char *rdatastr, vo
 			return ISC_R_NOMEMORY;
 		}
 		num_recs++;
+
+		if (dns_name_is_static(recs, num_recs)) {
+			rec->dwTimeStamp = 0;
+		} else {
+			unix_to_nt_time(&t, time(NULL));
+			t /= 10 * 1000 * 1000; /* convert to seconds */
+			t /= 3600;	     /* convert to hours */
+			rec->dwTimeStamp = (uint32_t)t;
+		}
 	}
 
 	recs[i] = *rec;
diff --git a/source4/dns_server/dns_update.c b/source4/dns_server/dns_update.c
index ebed4495dbd..f9866611119 100644
--- a/source4/dns_server/dns_update.c
+++ b/source4/dns_server/dns_update.c
@@ -38,7 +38,8 @@
 
 static WERROR dns_rr_to_dnsp(TALLOC_CTX *mem_ctx,
 			     const struct dns_res_rec *rrec,
-			     struct dnsp_DnssrvRpcRecord *r);
+			     struct dnsp_DnssrvRpcRecord *r,
+			     bool name_is_static);
 
 static WERROR check_one_prerequisite(struct dns_server *dns,
 				     TALLOC_CTX *mem_ctx,
@@ -181,7 +182,7 @@ static WERROR check_one_prerequisite(struct dns_server *dns,
 	rec = talloc_zero(mem_ctx, struct dnsp_DnssrvRpcRecord);
 	W_ERROR_HAVE_NO_MEMORY(rec);
 
-	werror = dns_rr_to_dnsp(rec, pr, rec);
+	werror = dns_rr_to_dnsp(rec, pr, rec, dns_name_is_static(ans, acount));
 	W_ERROR_NOT_OK_RETURN(werror);
 
 	for (i = 0; i < acount; i++) {
@@ -297,7 +298,8 @@ static WERROR update_prescan(const struct dns_name_question *zone,
 
 static WERROR dns_rr_to_dnsp(TALLOC_CTX *mem_ctx,
 			     const struct dns_res_rec *rrec,
-			     struct dnsp_DnssrvRpcRecord *r)
+			     struct dnsp_DnssrvRpcRecord *r,
+			     bool name_is_static)
 {
 	enum ndr_err_code ndr_err;
 	NTTIME t;
@@ -311,10 +313,14 @@ static WERROR dns_rr_to_dnsp(TALLOC_CTX *mem_ctx,
 	r->wType = (enum dns_record_type) rrec->rr_type;
 	r->dwTtlSeconds = rrec->ttl;
 	r->rank = DNS_RANK_ZONE;
-	unix_to_nt_time(&t, time(NULL));
-	t /= 10 * 1000 * 1000;
-	t /= 3600;
-	r->dwTimeStamp = t;
+	if (name_is_static) {
+		r->dwTimeStamp = 0;
+	} else {
+		unix_to_nt_time(&t, time(NULL));
+		t /= 10 * 1000 * 1000;
+		t /= 3600;
+		r->dwTimeStamp = t;
+	}
 
 	/* If we get QCLASS_ANY, we're done here */
 	if (rrec->rr_class == DNS_QCLASS_ANY) {
@@ -390,6 +396,7 @@ static WERROR handle_one_update(struct dns_server *dns,
 	WERROR werror;
 	bool tombstoned = false;
 	bool needs_add = false;
+	bool name_is_static;
 
 	DEBUG(2, ("Looking at record: \n"));
 	if (DEBUGLVL(2)) {
@@ -432,6 +439,8 @@ static WERROR handle_one_update(struct dns_server *dns,
 		first = rcount;
 	}
 
+	name_is_static = dns_name_is_static(recs, rcount);
+
 	if (update->rr_class == zone->question_class) {
 		if (update->rr_type == DNS_QTYPE_CNAME) {
 			/*
@@ -456,7 +465,8 @@ static WERROR handle_one_update(struct dns_server *dns,
 					struct dnsp_DnssrvRpcRecord, rcount + 1);
 			W_ERROR_HAVE_NO_MEMORY(recs);
 
-			werror = dns_rr_to_dnsp(recs, update, &recs[rcount]);
+			werror = dns_rr_to_dnsp(
+			    recs, update, &recs[rcount], name_is_static);
 			W_ERROR_NOT_OK_RETURN(werror);
 			rcount += 1;
 
@@ -508,7 +518,8 @@ static WERROR handle_one_update(struct dns_server *dns,
 				return WERR_OK;
 			}
 
-			werror = dns_rr_to_dnsp(mem_ctx, update, &recs[i]);
+			werror = dns_rr_to_dnsp(
+			    mem_ctx, update, &recs[i], name_is_static);
 			W_ERROR_NOT_OK_RETURN(werror);
 
 			for (i++; i < rcount; i++) {
@@ -532,7 +543,8 @@ static WERROR handle_one_update(struct dns_server *dns,
 				struct dnsp_DnssrvRpcRecord, rcount+1);
 		W_ERROR_HAVE_NO_MEMORY(recs);
 
-		werror = dns_rr_to_dnsp(recs, update, &recs[rcount]);
+		werror =
+		    dns_rr_to_dnsp(recs, update, &recs[rcount], name_is_static);
 		W_ERROR_NOT_OK_RETURN(werror);
 
 		for (i = first; i < rcount; i++) {
@@ -618,7 +630,8 @@ static WERROR handle_one_update(struct dns_server *dns,
 						struct dnsp_DnssrvRpcRecord);
 			W_ERROR_HAVE_NO_MEMORY(ns_rec);
 
-			werror = dns_rr_to_dnsp(ns_rec, update, ns_rec);
+			werror = dns_rr_to_dnsp(
+			    ns_rec, update, ns_rec, name_is_static);
 			W_ERROR_NOT_OK_RETURN(werror);
 
 			for (i = first; i < rcount; i++) {
@@ -635,7 +648,8 @@ static WERROR handle_one_update(struct dns_server *dns,
 		del_rec = talloc(mem_ctx, struct dnsp_DnssrvRpcRecord);
 		W_ERROR_HAVE_NO_MEMORY(del_rec);
 
-		werror = dns_rr_to_dnsp(del_rec, update, del_rec);
+		werror =
+		    dns_rr_to_dnsp(del_rec, update, del_rec, name_is_static);
 		W_ERROR_NOT_OK_RETURN(werror);
 
 		for (i = first; i < rcount; i++) {
diff --git a/source4/dns_server/dnsserver_common.c b/source4/dns_server/dnsserver_common.c
index 2551240fca4..2a493702ed2 100644
--- a/source4/dns_server/dnsserver_common.c
+++ b/source4/dns_server/dnsserver_common.c
@@ -724,6 +724,23 @@ static WERROR check_name_list(TALLOC_CTX *mem_ctx, uint16_t rec_count,
 	return WERR_OK;
 }
 
+bool dns_name_is_static(struct dnsp_DnssrvRpcRecord *records,
+			uint16_t rec_count)
+{
+	int i = 0;
+	for (i = 0; i < rec_count; i++) {
+		if (records[i].wType == DNS_TYPE_TOMBSTONE) {
+			continue;
+		}
+
+		if (records[i].wType == DNS_TYPE_SOA ||
+		    records[i].dwTimeStamp == 0) {
+			return true;
+		}
+	}
+	return false;
+}
+
 WERROR dns_get_zone_properties(struct ldb_context *samdb,
 			       TALLOC_CTX *mem_ctx,
 			       struct ldb_dn *zone_dn,
diff --git a/source4/dns_server/dnsserver_common.h b/source4/dns_server/dnsserver_common.h
index 9067e2234e7..380f61b8dbc 100644
--- a/source4/dns_server/dnsserver_common.h
+++ b/source4/dns_server/dnsserver_common.h
@@ -61,6 +61,8 @@ WERROR dns_get_zone_properties(struct ldb_context *samdb,
 			       TALLOC_CTX *mem_ctx,
 			       struct ldb_dn *zone_dn,
 			       struct dnsserver_zoneinfo *zoneinfo);
+bool dns_name_is_static(struct dnsp_DnssrvRpcRecord *records,
+			uint16_t rec_count);
 WERROR dns_common_replace(struct ldb_context *samdb,
 			  TALLOC_CTX *mem_ctx,
 			  struct ldb_dn *dn,
-- 
2.11.0


From 8bc12b6d8993105900b1db4b578ea595c06ee8fe Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Mon, 2 Jul 2018 16:47:16 +1200
Subject: [PATCH 14/18] tests dns: fix rpc null byte test

Fix update_add_null_char_rpc_to_dns so that the test matches the name.
It was not passing the embedded null to the rpc call.

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/tests/dns.py | 29 +++++++++++++++++++----------
 1 file changed, 19 insertions(+), 10 deletions(-)

diff --git a/python/samba/tests/dns.py b/python/samba/tests/dns.py
index 508d49f47d8..511471145a2 100644
--- a/python/samba/tests/dns.py
+++ b/python/samba/tests/dns.py
@@ -1435,27 +1435,36 @@ class TestRPCRoundtrip(DNSTest):
                              dnsp.DNS_TYPE_TXT, '"NULL" "NULL"'))
 
     def test_update_add_null_char_rpc_to_dns(self):
-        prefix, txt = 'nulltextrec', ['NULL\x00BYTE']
-        prefix = 'rpc' + prefix
+        prefix = 'rpcnulltextrec'
         name = "%s.%s" % (prefix, self.get_dns_domain())
 
-        rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '"NULL"')
+        rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '"NULL\x00BYTE"')
         add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
         add_rec_buf.rec = rec
         try:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                     0, self.server_ip, self.get_dns_domain(),
-                                     name, add_rec_buf, None)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                add_rec_buf,
+                None)
 
         except WERRORError as e:
             self.fail(str(e))
 
         try:
-           self.check_query_txt(prefix, ['NULL'])
+            self.check_query_txt(prefix, ['NULL'])
         finally:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                              0, self.server_ip, self.get_dns_domain(),
-                                              name, None, add_rec_buf)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                None,
+                add_rec_buf)
 
     def test_update_add_hex_char_txt_record(self):
         "test adding records works"
-- 
2.11.0


From d62a0b076c2dd3165df59335ba406f5725587356 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Mon, 2 Jul 2018 16:51:00 +1200
Subject: [PATCH 15/18] tests dns: dns.py remove flake8 warnings

Remove flake8 warnings from the code, this highlighted the issue with
test_update_add_null_char_rpc_to_dns fixed in the preceding commit.

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/tests/dns.py | 513 +++++++++++++++++++++++++++++++---------------
 1 file changed, 346 insertions(+), 167 deletions(-)

diff --git a/python/samba/tests/dns.py b/python/samba/tests/dns.py
index 511471145a2..6771e3bb8c4 100644
--- a/python/samba/tests/dns.py
+++ b/python/samba/tests/dns.py
@@ -25,10 +25,9 @@ import ldb
 import os
 import sys
 import struct
-import random
 import socket
 import samba.ndr as ndr
-from samba import credentials, param
+from samba import credentials
 from samba.dcerpc import dns, dnsp, dnsserver
 from samba.netcmd.dns import TXTRecord, dns_record_match, data_to_dns_record
 from samba.tests.subunitrun import SubunitOptions, TestProgram
@@ -67,6 +66,7 @@ server_name = args[0]
 server_ip = args[1]
 creds.set_krb_forwardable(credentials.NO_KRB_FORWARDABLE)
 
+
 class TestSimpleQueries(DNSTest):
     def setUp(self):
         super(TestSimpleQueries, self).setUp()
@@ -88,7 +88,8 @@ class TestSimpleQueries(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
         self.assertEquals(response.ancount, 1)
@@ -106,12 +107,14 @@ class TestSimpleQueries(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
         self.assertEquals(response.ancount, 1)
-        self.assertEquals(response.answers[0].rdata.mname.upper(),
-                          ("%s.%s" % (self.server, self.get_dns_domain())).upper())
+        self.assertEquals(
+            response.answers[0].rdata.mname.upper(),
+            ("%s.%s" % (self.server, self.get_dns_domain())).upper())
 
     def test_one_a_query_tcp(self):
         "create a query packet containing one query record via TCP"
@@ -124,7 +127,8 @@ class TestSimpleQueries(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_tcp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_tcp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
         self.assertEquals(response.ancount, 1)
@@ -142,7 +146,8 @@ class TestSimpleQueries(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
         self.assertEquals(response.ancount, 0)
@@ -156,7 +161,8 @@ class TestSimpleQueries(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NXDOMAIN)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
         self.assertEquals(response.ancount, 0)
@@ -176,7 +182,8 @@ class TestSimpleQueries(DNSTest):
 
         self.finish_name_packet(p, questions)
         try:
-            (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+            (response, response_packet) =\
+                self.dns_transaction_udp(p, host=server_ip)
             self.assert_dns_rcode_equals(response, dns.DNS_RCODE_FORMERR)
         except socket.timeout:
             # Windows chooses not to respond to incorrectly formatted queries.
@@ -196,7 +203,8 @@ class TestSimpleQueries(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
 
         num_answers = 1
         dc_ipv6 = os.getenv('SERVER_IPV6')
@@ -217,12 +225,16 @@ class TestSimpleQueries(DNSTest):
         questions = []
 
         name = "%s.%s" % (self.server, self.get_dns_domain())
-        q = self.make_name_question(name, dns.DNS_QTYPE_ALL, dns.DNS_QCLASS_NONE)
+        q = self.make_name_question(
+            name,
+            dns.DNS_QTYPE_ALL,
+            dns.DNS_QCLASS_NONE)
         questions.append(q)
 
         self.finish_name_packet(p, questions)
         try:
-            (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+            (response, response_packet) =\
+                self.dns_transaction_udp(p, host=server_ip)
             self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NOTIMP)
         except socket.timeout:
             # Windows chooses not to respond to incorrectly formatted queries.
@@ -241,7 +253,8 @@ class TestSimpleQueries(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
         # We don't get SOA records for single hosts
@@ -259,7 +272,8 @@ class TestSimpleQueries(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
         self.assertEquals(response.ancount, 1)
@@ -291,7 +305,8 @@ class TestDNSUpdates(DNSTest):
 
         self.finish_name_packet(p, updates)
         try:
-            (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+            (response, response_packet) =\
+                self.dns_transaction_udp(p, host=server_ip)
             self.assert_dns_rcode_equals(response, dns.DNS_RCODE_FORMERR)
         except socket.timeout:
             # Windows chooses not to respond to incorrectly formatted queries.
@@ -310,7 +325,8 @@ class TestDNSUpdates(DNSTest):
         updates.append(u)
 
         self.finish_name_packet(p, updates)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NOTIMP)
 
     def test_update_prereq_with_non_null_ttl(self):
@@ -337,7 +353,8 @@ class TestDNSUpdates(DNSTest):
         p.answers = prereqs
 
         try:
-            (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+            (response, response_packet) =\
+                self.dns_transaction_udp(p, host=server_ip)
             self.assert_dns_rcode_equals(response, dns.DNS_RCODE_FORMERR)
         except socket.timeout:
             # Windows chooses not to respond to incorrectly formatted queries.
@@ -369,7 +386,8 @@ class TestDNSUpdates(DNSTest):
         p.ancount = len(prereqs)
         p.answers = prereqs
 
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NXRRSET)
 
     def test_update_prereq_nonexisting_name(self):
@@ -395,14 +413,16 @@ class TestDNSUpdates(DNSTest):
         p.ancount = len(prereqs)
         p.answers = prereqs
 
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NXRRSET)
 
     def test_update_add_txt_record(self):
         "test adding records works"
         prefix, txt = 'textrec', ['"This is a test"']
         p = self.make_txt_update(prefix, txt)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.check_query_txt(prefix, txt)
 
@@ -434,7 +454,8 @@ class TestDNSUpdates(DNSTest):
         p.nscount = len(updates)
         p.nsrecs = updates
 
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
         # Now check the record is around
@@ -444,7 +465,8 @@ class TestDNSUpdates(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
         # Now delete the record
@@ -470,7 +492,8 @@ class TestDNSUpdates(DNSTest):
         p.nscount = len(updates)
         p.nsrecs = updates
 
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
         # And finally check it's gone
@@ -481,7 +504,8 @@ class TestDNSUpdates(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NXDOMAIN)
 
     def test_readd_record(self):
@@ -512,7 +536,8 @@ class TestDNSUpdates(DNSTest):
         p.nscount = len(updates)
         p.nsrecs = updates
 
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
         # Now check the record is around
@@ -522,7 +547,8 @@ class TestDNSUpdates(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
         # Now delete the record
@@ -548,7 +574,8 @@ class TestDNSUpdates(DNSTest):
         p.nscount = len(updates)
         p.nsrecs = updates
 
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
         # check it's gone
@@ -559,7 +586,8 @@ class TestDNSUpdates(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NXDOMAIN)
 
         # recreate the record
@@ -585,7 +613,8 @@ class TestDNSUpdates(DNSTest):
         p.nscount = len(updates)
         p.nsrecs = updates
 
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
         # Now check the record is around
@@ -595,7 +624,8 @@ class TestDNSUpdates(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
     def test_update_add_mx_record(self):
@@ -624,7 +654,8 @@ class TestDNSUpdates(DNSTest):
         p.nscount = len(updates)
         p.nsrecs = updates
 
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
@@ -635,7 +666,8 @@ class TestDNSUpdates(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.assertEqual(response.ancount, 1)
         ans = response.answers[0]
@@ -661,7 +693,8 @@ class TestComplexQueries(DNSTest):
         r.rdata = value
         p.nscount = 1
         p.nsrecs = [r]
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
     def setUp(self):
@@ -689,12 +722,15 @@ class TestComplexQueries(DNSTest):
 
             # Check the record
             name = "cname_test.%s" % self.get_dns_domain()
-            q = self.make_name_question(name, dns.DNS_QTYPE_A, dns.DNS_QCLASS_IN)
+            q = self.make_name_question(name,
+                                        dns.DNS_QTYPE_A,
+                                        dns.DNS_QCLASS_IN)
             print("asking for ", q.name)
             questions.append(q)
 
             self.finish_name_packet(p, questions)
-            (response, response_packet) = self.dns_transaction_udp(p, host=self.server_ip)
+            (response, response_packet) =\
+                self.dns_transaction_udp(p, host=self.server_ip)
             self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
             self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
             self.assertEquals(response.ancount, 2)
@@ -712,7 +748,9 @@ class TestComplexQueries(DNSTest):
 
             name = self.get_dns_domain()
 
-            u = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+            u = self.make_name_question(name,
+                                        dns.DNS_QTYPE_SOA,
+                                        dns.DNS_QCLASS_IN)
             updates.append(u)
             self.finish_name_packet(p, updates)
 
@@ -728,7 +766,8 @@ class TestComplexQueries(DNSTest):
             p.nscount = len(updates)
             p.nsrecs = updates
 
-            (response, response_packet) = self.dns_transaction_udp(p, host=self.server_ip)
+            (response, response_packet) =\
+                self.dns_transaction_udp(p, host=self.server_ip)
             self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
     def test_cname_two_chain(self):
@@ -746,7 +785,8 @@ class TestComplexQueries(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
         self.assertEquals(response.ancount, 3)
@@ -767,7 +807,7 @@ class TestComplexQueries(DNSTest):
         name0 = "cnamedotprefix0.%s" % self.get_dns_domain()
         try:
             self.make_dns_update(name0, "", dns.DNS_QTYPE_CNAME)
-        except AssertionError as e:
+        except AssertionError:
             pass
         else:
             self.fail("Successfully added empty CNAME, which is invalid.")
@@ -787,7 +827,8 @@ class TestComplexQueries(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
 
@@ -803,6 +844,7 @@ class TestComplexQueries(DNSTest):
         self.assertEquals(response.answers[1].name, name2)
         self.assertEquals(response.answers[1].rdata, name0)
 
+
 class TestInvalidQueries(DNSTest):
     def setUp(self):
         super(TestInvalidQueries, self).setUp()
@@ -814,7 +856,8 @@ class TestInvalidQueries(DNSTest):
         self.timeout = timeout
 
     def test_one_a_query(self):
-        "send 0 bytes follows by create a query packet containing one query record"
+        """send 0 bytes follows by create a query packet
+           containing one query record"""
 
         s = None
         try:
@@ -834,7 +877,8 @@ class TestInvalidQueries(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=self.server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=self.server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
         self.assertEquals(response.ancount, 1)
@@ -860,7 +904,7 @@ class TestInvalidQueries(DNSTest):
             send_packet = ndr.ndr_pack(p)
             s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
             s.settimeout(timeout)
-            host=self.server_ip
+            host = self.server_ip
             s.connect((host, 53))
             tcp_packet = struct.pack('!H', len(send_packet))
             tcp_packet += send_packet
@@ -877,6 +921,7 @@ class TestInvalidQueries(DNSTest):
             if s is not None:
                 s.close()
 
+
 class TestZones(DNSTest):
     def setUp(self):
         super(TestZones, self).setUp()
@@ -888,12 +933,12 @@ class TestZones(DNSTest):
         self.timeout = timeout
 
         self.zone = "test.lan"
-        self.rpc_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[sign]" %\
+        self.rpc_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[sign]" %
                                             (self.server_ip),
                                             self.lp, self.creds)
 
         self.samdb = SamDB(url="ldap://" + self.server_ip,
-                           lp = self.get_loadparm(),
+                           lp=self.get_loadparm(),
                            session_info=system_session(),
                            credentials=self.creds)
 
@@ -935,7 +980,7 @@ class TestZones(DNSTest):
 
     def set_params(self, **kwargs):
         zone = kwargs.pop('zone', None)
-        for key,val in kwargs.items():
+        for key, val in kwargs.items():
             name_param = dnsserver.DNS_RPC_NAME_AND_PARAM()
             name_param.dwParam = val
             name_param.pszNodeName = key
@@ -943,8 +988,13 @@ class TestZones(DNSTest):
             client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
             nap_type = dnsserver.DNSSRV_TYPEID_NAME_AND_PARAM
             try:
-                self.rpc_conn.DnssrvOperation2(client_version, 0, self.server, zone,
-                                               0, 'ResetDwordProperty', nap_type,
+                self.rpc_conn.DnssrvOperation2(client_version,
+                                               0,
+                                               self.server,
+                                               zone,
+                                               0,
+                                               'ResetDwordProperty',
+                                               nap_type,
                                                name_param)
             except WERRORError as e:
                 self.fail(str(e))
@@ -954,7 +1004,7 @@ class TestZones(DNSTest):
         dns_recs = self.ldap_get_dns_records(name)
         for rec in dns_recs:
             func(rec)
-        update_dict = {'dn':dn, 'dnsRecord':[ndr_pack(r) for r in dns_recs]}
+        update_dict = {'dn': dn, 'dnsRecord': [ndr_pack(r) for r in dns_recs]}
         self.samdb.modify(ldb.Message.from_dict(self.samdb,
                                                 update_dict,
                                                 ldb.FLAG_MOD_REPLACE))
@@ -983,14 +1033,14 @@ class TestZones(DNSTest):
 
     def ldap_get_zone_settings(self):
         records = self.samdb.search(base=self.zone_dn, scope=ldb.SCOPE_BASE,
-                   expression="(&(objectClass=dnsZone)"+\
+                   expression="(&(objectClass=dnsZone)" +
                                 "(name={}))".format(self.zone),
                                     attrs=["dNSProperty"])
         self.assertEqual(len(records), 1)
         props = [ndr_unpack(dnsp.DnsProperty, r)
                  for r in records[0].get('dNSProperty')]
 
-        #We have no choice but to repeat these here.
+        # We have no choice but to repeat these here.
         zone_prop_ids = {0x00: "EMPTY",
                          0x01: "TYPE",
                          0x02: "ALLOW_UPDATE",
@@ -1014,7 +1064,7 @@ class TestZones(DNSTest):
         self.create_zone(self.zone, aging_enabled=enable)
         self.set_params(NoRefreshInterval=1, RefreshInterval=1,
                         Aging=int(bool(enable)), zone=self.zone,
-                        AllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE)
+                        AllowUpdate=dnsp.DNS_ZONE_UPDATE_UNSECURE)
 
     def test_set_aging(self, enable=True, name='agingtest', txt=['test txt']):
         self.set_aging(enable=True)
@@ -1041,6 +1091,7 @@ class TestZones(DNSTest):
         if not enable:
             self.set_params(zone=self.zone, Aging=0)
         dec = 2
+
         def mod_ts(rec):
             self.assertTrue(rec.dwTimeStamp > 0)
             rec.dwTimeStamp -= dec
@@ -1059,55 +1110,67 @@ class TestZones(DNSTest):
         self.test_aging_update(enable=False)
 
     def test_aging_refresh(self):
-        name,txt = 'agingtest', ['test txt']
+        name, txt = 'agingtest', ['test txt']
         self.create_zone(self.zone, aging_enabled=True)
         interval = 10
         self.set_params(NoRefreshInterval=interval, RefreshInterval=interval,
                         Aging=1, zone=self.zone,
-                        AllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE)
+                        AllowUpdate=dnsp.DNS_ZONE_UPDATE_UNSECURE)
         before_mod = self.dns_update_record(name, txt)
+
         def mod_ts(rec):
             self.assertTrue(rec.dwTimeStamp > 0)
-            rec.dwTimeStamp -= interval/2
+            rec.dwTimeStamp -= interval / 2
         self.ldap_modify_dnsrecs(name, mod_ts)
         update_during_norefresh = self.dns_update_record(name, txt)
+
         def mod_ts(rec):
             self.assertTrue(rec.dwTimeStamp > 0)
-            rec.dwTimeStamp -= interval + interval/2
+            rec.dwTimeStamp -= interval + interval / 2
         self.ldap_modify_dnsrecs(name, mod_ts)
         update_during_refresh = self.dns_update_record(name, txt)
         self.assertEqual(update_during_norefresh.dwTimeStamp,
-                         before_mod.dwTimeStamp - interval/2)
+                         before_mod.dwTimeStamp - interval / 2)
         self.assertEqual(update_during_refresh.dwTimeStamp,
                          before_mod.dwTimeStamp)
 
     def test_rpc_add_no_timestamp(self):
-        name,txt = 'agingtest', ['test txt']
+        name, txt = 'agingtest', ['test txt']
         self.set_aging(enable=True)
         rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
         rec_buf.rec = TXTRecord(txt)
-        self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                          0, self.server_ip,
-                                          self.zone, name, rec_buf, None)
+        self.rpc_conn.DnssrvUpdateRecord2(
+            dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+            0,
+            self.server_ip,
+            self.zone,
+            name,
+            rec_buf,
+            None)
         recs = self.ldap_get_dns_records(name)
         self.assertEqual(len(recs), 1)
         self.assertEqual(recs[0].dwTimeStamp, 0)
 
     def test_static_record_dynamic_update(self):
-        name,txt = 'agingtest', ['test txt']
+        name, txt = 'agingtest', ['test txt']
         txt2 = ['test txt2']
         self.set_aging(enable=True)
         rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
         rec_buf.rec = TXTRecord(txt)
-        self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                          0, self.server_ip,
-                                          self.zone, name, rec_buf, None)
+        self.rpc_conn.DnssrvUpdateRecord2(
+            dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+            0,
+            self.server_ip,
+            self.zone,
+            name,
+            rec_buf,
+            None)
 
         rec2 = self.dns_update_record(name, txt2)
         self.assertEqual(rec2.dwTimeStamp, 0)
 
     def test_dynamic_record_static_update(self):
-        name,txt = 'agingtest', ['test txt']
+        name, txt = 'agingtest', ['test txt']
         txt2 = ['test txt2']
         txt3 = ['test txt3']
         self.set_aging(enable=True)
@@ -1116,33 +1179,38 @@ class TestZones(DNSTest):
 
         rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
         rec_buf.rec = TXTRecord(txt2)
-        self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                          0, self.server_ip,
-                                          self.zone, name, rec_buf, None)
+        self.rpc_conn.DnssrvUpdateRecord2(
+            dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+            0,
+            self.server_ip,
+            self.zone,
+            name,
+            rec_buf,
+            None)
 
         self.dns_update_record(name, txt3)
 
         recs = self.ldap_get_dns_records(name)
         # Put in dict because ldap recs might be out of order
-        recs = {str(r.data.str):r for r in recs}
+        recs = {str(r.data.str): r for r in recs}
         self.assertNotEqual(recs[str(txt)].dwTimeStamp, 0)
         self.assertEqual(recs[str(txt2)].dwTimeStamp, 0)
         self.assertEqual(recs[str(txt3)].dwTimeStamp, 0)
 
     def test_dns_tombstone_custom_match_rule(self):
         lp = self.get_loadparm()
-        self.samdb = SamDB(url = lp.samdb_url(), lp = lp,
+        self.samdb = SamDB(url=lp.samdb_url(), lp=lp,
                            session_info=system_session(),
                            credentials=self.creds)
 
-        name,txt = 'agingtest', ['test txt']
-        name2,txt2 = 'agingtest2', ['test txt2']
-        name3,txt3 = 'agingtest3', ['test txt3']
+        name, txt = 'agingtest', ['test txt']
+        name2, txt2 = 'agingtest2', ['test txt2']
+        name3, txt3 = 'agingtest3', ['test txt3']
         self.create_zone(self.zone, aging_enabled=True)
         interval = 10
         self.set_params(NoRefreshInterval=interval, RefreshInterval=interval,
                         Aging=1, zone=self.zone,
-                        AllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE)
+                        AllowUpdate=dnsp.DNS_ZONE_UPDATE_UNSECURE)
 
         self.dns_update_record(name, txt),
 
@@ -1160,9 +1228,9 @@ class TestZones(DNSTest):
         self.ldap_modify_dnsrecs(name, mod_ts)
         self.ldap_modify_dnsrecs(name2, mod_ts)
 
-        recs = self.ldap_get_dns_records(name3)
+        self.ldap_get_dns_records(name3)
         expr = "(dnsRecord:1.3.6.1.4.1.7165.4.5.3:={})"
-        expr = expr.format(int(last_update.dwTimeStamp)-1)
+        expr = expr.format(int(last_update.dwTimeStamp) - 1)
         try:
             res = self.samdb.search(base=self.zone_dn, scope=ldb.SCOPE_SUBTREE,
                                     expression=expr, attrs=["*"])
@@ -1182,7 +1250,7 @@ class TestZones(DNSTest):
 
     def test_basic_scavenging(self):
         lp = self.get_loadparm()
-        self.samdb = SamDB(url = lp.samdb_url(), lp = lp,
+        self.samdb = SamDB(url=lp.samdb_url(), lp=lp,
                            session_info=system_session(),
                            credentials=self.creds)
 
@@ -1190,21 +1258,21 @@ class TestZones(DNSTest):
         interval = 1
         self.set_params(NoRefreshInterval=interval, RefreshInterval=interval,
                         zone=self.zone, Aging=1,
-                        AllowUpdate = dnsp.DNS_ZONE_UPDATE_UNSECURE)
+                        AllowUpdate=dnsp.DNS_ZONE_UPDATE_UNSECURE)
         name, txt = 'agingtest', ['test txt']
         name2, txt2 = 'agingtest2', ['test txt2']
         name3, txt3 = 'agingtest3', ['test txt3']
-        self.dns_update_record(name,txt)
-        self.dns_update_record(name2,txt)
-        self.dns_update_record(name2,txt2)
-        self.dns_update_record(name3,txt)
-        self.dns_update_record(name3,txt2)
-        last_add = self.dns_update_record(name3,txt3)
+        self.dns_update_record(name, txt)
+        self.dns_update_record(name2, txt)
+        self.dns_update_record(name2, txt2)
+        self.dns_update_record(name3, txt)
+        self.dns_update_record(name3, txt2)
+        last_add = self.dns_update_record(name3, txt3)
 
         def mod_ts(rec):
             self.assertTrue(rec.dwTimeStamp > 0)
             if rec.data.str == txt:
-                rec.dwTimeStamp -= interval*5
+                rec.dwTimeStamp -= interval * 5
         self.ldap_modify_dnsrecs(name, mod_ts)
         self.ldap_modify_dnsrecs(name2, mod_ts)
         self.ldap_modify_dnsrecs(name3, mod_ts)
@@ -1223,14 +1291,15 @@ class TestZones(DNSTest):
         recs = self.ldap_get_dns_records(name3)
         self.assertEqual(len(recs), 2)
         txts = {str(r.data.str) for r in recs}
-        self.assertEqual(txts, {str(txt2),str(txt3)})
+        self.assertEqual(txts, {str(txt2), str(txt3)})
         self.assertEqual(recs[0].wType, dnsp.DNS_TYPE_TXT)
         self.assertEqual(recs[1].wType, dnsp.DNS_TYPE_TXT)
 
         for make_it_work in [False, True]:
             inc = -1 if make_it_work else 1
+
             def mod_ts(rec):
-                rec.data = (last_add.dwTimeStamp - 24*14) + inc
+                rec.data = (last_add.dwTimeStamp - 24 * 14) + inc
             self.ldap_modify_dnsrecs(name, mod_ts)
             dsdb._dns_delete_tombstones(self.samdb)
             recs = self.ldap_get_records(name)
@@ -1258,25 +1327,29 @@ class TestZones(DNSTest):
         questions.append(q)
         self.finish_name_packet(p, questions)
 
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         # Windows returns OK while BIND logically seems to return NXDOMAIN
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NXDOMAIN)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
         self.assertEquals(response.ancount, 0)
 
         self.create_zone(zone)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
         self.assertEquals(response.ancount, 1)
         self.assertEquals(response.answers[0].rr_type, dns.DNS_QTYPE_SOA)
 
         self.delete_zone(zone)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_NXDOMAIN)
         self.assert_dns_opcode_equals(response, dns.DNS_OPCODE_QUERY)
         self.assertEquals(response.ancount, 0)
 
+
 class TestRPCRoundtrip(DNSTest):
     def setUp(self):
         super(TestRPCRoundtrip, self).setUp()
@@ -1285,8 +1358,10 @@ class TestRPCRoundtrip(DNSTest):
         self.server_ip = server_ip
         self.lp = lp
         self.creds = creds
-        self.rpc_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[sign]" % (self.server_ip),
-                                            self.lp, self.creds)
+        self.rpc_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[sign]" %
+                                            (self.server_ip),
+                                            self.lp,
+                                            self.creds)
 
     def tearDown(self):
         super(TestRPCRoundtrip, self).tearDown()
@@ -1300,63 +1375,94 @@ class TestRPCRoundtrip(DNSTest):
         add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
         add_rec_buf.rec = rec
         try:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                     0, self.server_ip, self.get_dns_domain(),
-                                     name, add_rec_buf, None)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                add_rec_buf,
+                None)
+
         except WERRORError as e:
             self.fail(str(e))
 
         try:
             self.check_query_txt(prefix, txt)
         finally:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                              0, self.server_ip, self.get_dns_domain(),
-                                              name, None, add_rec_buf)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                None,
+                add_rec_buf)
 
     def test_update_add_null_padded_txt_record(self):
         "test adding records works"
         prefix, txt = 'pad1textrec', ['"This is a test"', '', '']
         p = self.make_txt_update(prefix, txt)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.check_query_txt(prefix, txt)
-        self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip,
-                             self.get_dns_domain(),
-                             "%s.%s" % (prefix, self.get_dns_domain()),
-                             dnsp.DNS_TYPE_TXT, '"\\"This is a test\\"" "" ""'))
+        self.assertIsNotNone(
+            dns_record_match(self.rpc_conn,
+                self.server_ip,
+                self.get_dns_domain(),
+                "%s.%s" % (prefix, self.get_dns_domain()),
+                dnsp.DNS_TYPE_TXT,
+                '"\\"This is a test\\"" "" ""'))
 
         prefix, txt = 'pad2textrec', ['"This is a test"', '', '', 'more text']
         p = self.make_txt_update(prefix, txt)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.check_query_txt(prefix, txt)
-        self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip,
-                             self.get_dns_domain(),
-                             "%s.%s" % (prefix, self.get_dns_domain()),
-                             dnsp.DNS_TYPE_TXT, '"\\"This is a test\\"" "" "" "more text"'))
+        self.assertIsNotNone(
+            dns_record_match(
+                self.rpc_conn,
+                self.server_ip,
+                self.get_dns_domain(),
+                "%s.%s" % (prefix, self.get_dns_domain()),
+                dnsp.DNS_TYPE_TXT,
+                '"\\"This is a test\\"" "" "" "more text"'))
 
         prefix, txt = 'pad3textrec', ['', '', '"This is a test"']
         p = self.make_txt_update(prefix, txt)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.check_query_txt(prefix, txt)
-        self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip,
-                             self.get_dns_domain(),
-                             "%s.%s" % (prefix, self.get_dns_domain()),
-                             dnsp.DNS_TYPE_TXT, '"" "" "\\"This is a test\\""'))
+        self.assertIsNotNone(
+            dns_record_match(
+                self.rpc_conn,
+                self.server_ip,
+                self.get_dns_domain(),
+                "%s.%s" % (prefix, self.get_dns_domain()),
+                dnsp.DNS_TYPE_TXT,
+                '"" "" "\\"This is a test\\""'))
 
     def test_update_add_padding_rpc_to_dns(self):
         prefix, txt = 'pad1textrec', ['"This is a test"', '', '']
         prefix = 'rpc' + prefix
         name = "%s.%s" % (prefix, self.get_dns_domain())
 
-        rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '"\\"This is a test\\"" "" ""')
+        rec = data_to_dns_record(dnsp.DNS_TYPE_TXT,
+                                 '"\\"This is a test\\"" "" ""')
         add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
         add_rec_buf.rec = rec
         try:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                     0, self.server_ip, self.get_dns_domain(),
-                                     name, add_rec_buf, None)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                add_rec_buf,
+                None)
 
         except WERRORError as e:
             self.fail(str(e))
@@ -1364,21 +1470,32 @@ class TestRPCRoundtrip(DNSTest):
         try:
             self.check_query_txt(prefix, txt)
         finally:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                              0, self.server_ip, self.get_dns_domain(),
-                                              name, None, add_rec_buf)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                None,
+                add_rec_buf)
 
         prefix, txt = 'pad2textrec', ['"This is a test"', '', '', 'more text']
         prefix = 'rpc' + prefix
         name = "%s.%s" % (prefix, self.get_dns_domain())
 
-        rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '"\\"This is a test\\"" "" "" "more text"')
+        rec = data_to_dns_record(dnsp.DNS_TYPE_TXT,
+                                 '"\\"This is a test\\"" "" "" "more text"')
         add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
         add_rec_buf.rec = rec
         try:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                     0, self.server_ip, self.get_dns_domain(),
-                                     name, add_rec_buf, None)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                add_rec_buf,
+                None)
 
         except WERRORError as e:
             self.fail(str(e))
@@ -1386,37 +1503,54 @@ class TestRPCRoundtrip(DNSTest):
         try:
             self.check_query_txt(prefix, txt)
         finally:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                              0, self.server_ip, self.get_dns_domain(),
-                                              name, None, add_rec_buf)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                None,
+                add_rec_buf)
 
         prefix, txt = 'pad3textrec', ['', '', '"This is a test"']
         prefix = 'rpc' + prefix
         name = "%s.%s" % (prefix, self.get_dns_domain())
 
-        rec = data_to_dns_record(dnsp.DNS_TYPE_TXT, '"" "" "\\"This is a test\\""')
+        rec = data_to_dns_record(dnsp.DNS_TYPE_TXT,
+                                 '"" "" "\\"This is a test\\""')
         add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
         add_rec_buf.rec = rec
         try:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                     0, self.server_ip, self.get_dns_domain(),
-                                     name, add_rec_buf, None)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                add_rec_buf,
+                None)
         except WERRORError as e:
             self.fail(str(e))
 
         try:
             self.check_query_txt(prefix, txt)
         finally:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                              0, self.server_ip, self.get_dns_domain(),
-                                              name, None, add_rec_buf)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                None,
+                add_rec_buf)
 
     # Test is incomplete due to strlen against txt records
     def test_update_add_null_char_txt_record(self):
         "test adding records works"
         prefix, txt = 'nulltextrec', ['NULL\x00BYTE']
         p = self.make_txt_update(prefix, txt)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.check_query_txt(prefix, ['NULL'])
         self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip,
@@ -1426,7 +1560,8 @@ class TestRPCRoundtrip(DNSTest):
 
         prefix, txt = 'nulltextrec2', ['NULL\x00BYTE', 'NULL\x00BYTE']
         p = self.make_txt_update(prefix, txt)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.check_query_txt(prefix, ['NULL', 'NULL'])
         self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip,
@@ -1470,7 +1605,8 @@ class TestRPCRoundtrip(DNSTest):
         "test adding records works"
         prefix, txt = 'hextextrec', ['HIGH\xFFBYTE']
         p = self.make_txt_update(prefix, txt)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.check_query_txt(prefix, txt)
         self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip,
@@ -1487,25 +1623,36 @@ class TestRPCRoundtrip(DNSTest):
         add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
         add_rec_buf.rec = rec
         try:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                     0, self.server_ip, self.get_dns_domain(),
-                                     name, add_rec_buf, None)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                add_rec_buf,
+                None)
 
         except WERRORError as e:
             self.fail(str(e))
 
         try:
-           self.check_query_txt(prefix, txt)
+            self.check_query_txt(prefix, txt)
         finally:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                              0, self.server_ip, self.get_dns_domain(),
-                                              name, None, add_rec_buf)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                None,
+                add_rec_buf)
 
     def test_update_add_slash_txt_record(self):
         "test adding records works"
         prefix, txt = 'slashtextrec', ['Th\\=is=is a test']
         p = self.make_txt_update(prefix, txt)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.check_query_txt(prefix, txt)
         self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip,
@@ -1525,9 +1672,14 @@ class TestRPCRoundtrip(DNSTest):
         add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
         add_rec_buf.rec = rec
         try:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                     0, self.server_ip, self.get_dns_domain(),
-                                     name, add_rec_buf, None)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                add_rec_buf,
+                None)
 
         except WERRORError as e:
             self.fail(str(e))
@@ -1536,16 +1688,22 @@ class TestRPCRoundtrip(DNSTest):
             self.check_query_txt(prefix, txt)
 
         finally:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                              0, self.server_ip, self.get_dns_domain(),
-                                              name, None, add_rec_buf)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                None,
+                add_rec_buf)
 
     def test_update_add_two_txt_records(self):
         "test adding two txt records works"
         prefix, txt = 'textrec2', ['"This is a test"',
                                    '"and this is a test, too"']
         p = self.make_txt_update(prefix, txt)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.check_query_txt(prefix, txt)
         self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip,
@@ -1566,9 +1724,14 @@ class TestRPCRoundtrip(DNSTest):
         add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
         add_rec_buf.rec = rec
         try:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                     0, self.server_ip, self.get_dns_domain(),
-                                     name, add_rec_buf, None)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                add_rec_buf,
+                None)
 
         except WERRORError as e:
             self.fail(str(e))
@@ -1576,15 +1739,21 @@ class TestRPCRoundtrip(DNSTest):
         try:
             self.check_query_txt(prefix, txt)
         finally:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                              0, self.server_ip, self.get_dns_domain(),
-                                              name, None, add_rec_buf)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                None,
+                add_rec_buf)
 
     def test_update_add_empty_txt_records(self):
         "test adding two txt records works"
         prefix, txt = 'emptytextrec', []
         p = self.make_txt_update(prefix, txt)
-        (response, response_packet) = self.dns_transaction_udp(p, host=server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.check_query_txt(prefix, txt)
         self.assertIsNotNone(dns_record_match(self.rpc_conn, self.server_ip,
@@ -1601,17 +1770,27 @@ class TestRPCRoundtrip(DNSTest):
         add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
         add_rec_buf.rec = rec
         try:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                     0, self.server_ip, self.get_dns_domain(),
-                                     name, add_rec_buf, None)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                add_rec_buf,
+                None)
         except WERRORError as e:
             self.fail(str(e))
 
         try:
             self.check_query_txt(prefix, txt)
         finally:
-            self.rpc_conn.DnssrvUpdateRecord2(dnsserver.DNS_CLIENT_VERSION_LONGHORN,
-                                              0, self.server_ip, self.get_dns_domain(),
-                                              name, None, add_rec_buf)
+            self.rpc_conn.DnssrvUpdateRecord2(
+                dnsserver.DNS_CLIENT_VERSION_LONGHORN,
+                0,
+                self.server_ip,
+                self.get_dns_domain(),
+                name,
+                None,
+                add_rec_buf)
 
 TestProgram(module=__name__, opts=subunitopts)
-- 
2.11.0


From 54b0e6febbd5436c967294d8f1b4bcdbefb51971 Mon Sep 17 00:00:00 2001
From: Gary Lockyer <gary at catalyst.net.nz>
Date: Tue, 3 Jul 2018 17:03:38 +1200
Subject: [PATCH 16/18] tests dns: dns_base.py remove flake8 warnings

Signed-off-by: Gary Lockyer <gary at catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/tests/dns_base.py | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/python/samba/tests/dns_base.py b/python/samba/tests/dns_base.py
index 0709bbe1c40..5abd6bd13fd 100644
--- a/python/samba/tests/dns_base.py
+++ b/python/samba/tests/dns_base.py
@@ -28,6 +28,7 @@ import socket
 import uuid
 import time
 
+
 class DNSTest(TestCaseInTempDir):
 
     def setUp(self):
@@ -199,7 +200,8 @@ class DNSTest(TestCaseInTempDir):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, host=self.server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, host=self.server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
         self.assertEquals(response.ancount, 1)
         self.assertEquals(response.answers[0].rdata.txt.str, txt_array)
@@ -244,7 +246,7 @@ class DNSTKeyTest(DNSTest):
         rdata = dns.tkey_record()
         rdata.algorithm = "gss-tsig"
         rdata.inception = int(time.time())
-        rdata.expiration = int(time.time()) + 60*60
+        rdata.expiration = int(time.time()) + 60 * 60
         rdata.mode = dns.DNS_TKEY_MODE_GSSAPI
         rdata.error = 0
         rdata.other_size = 0
@@ -271,7 +273,8 @@ class DNSTKeyTest(DNSTest):
         p.arcount = 1
         p.additional = additional
 
-        (response, response_packet) = self.dns_transaction_tcp(p, self.server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_tcp(p, self.server_ip)
         self.assert_dns_rcode_equals(response, dns.DNS_RCODE_OK)
 
         tkey_record = response.answers[0].rdata
@@ -395,7 +398,8 @@ class DNSTKeyTest(DNSTest):
         questions.append(q)
 
         self.finish_name_packet(p, questions)
-        (response, response_packet) = self.dns_transaction_udp(p, self.server_ip)
+        (response, response_packet) =\
+            self.dns_transaction_udp(p, self.server_ip)
         return response.operation & 0x000F
 
     def make_update_request(self, delete=False):
-- 
2.11.0


From d85dfc3668901927a2f9b3dd744d42c437b7864c Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Fri, 9 Dec 2016 09:13:11 +1300
Subject: [PATCH 17/18] python/tests: check setting values on dnsRecord
 attributes

BUG: https://bugzilla.samba.org/show_bug.cgi?id=12451

Signed-off-by: Bob Campbell <bobcampbell at catalyst.net.nz>
Reviewed-by: Stefan Metzmacher <metze at samba.org>
---
 python/samba/tests/dcerpc/dnsserver.py | 34 ++++++++++++++++++++++++++++++++--
 1 file changed, 32 insertions(+), 2 deletions(-)

diff --git a/python/samba/tests/dcerpc/dnsserver.py b/python/samba/tests/dcerpc/dnsserver.py
index 93d4478997c..53e1abde042 100644
--- a/python/samba/tests/dcerpc/dnsserver.py
+++ b/python/samba/tests/dcerpc/dnsserver.py
@@ -212,7 +212,7 @@ class DnsserverTests(RpcInterfaceTestCase):
 
         zone_dn = None
         for zone in zones:
-            if zone_name in str(zone.dn):
+            if "DC=%s," % zone_name in str(zone.dn):
                 zone_dn = zone.dn
                 break
 
@@ -225,7 +225,8 @@ class DnsserverTests(RpcInterfaceTestCase):
 
         for old_packed_record in records:
             if record_name in str(old_packed_record.dn):
-                return (old_packed_record.dn, ndr_unpack(dnsp.DnssrvRpcRecord, old_packed_record["dnsRecord"][0]))
+                rec = ndr_unpack(dnsp.DnssrvRpcRecord, old_packed_record["dnsRecord"][0])
+                return (old_packed_record.dn, rec)
 
     def test_duplicate_matching(self):
         """
@@ -323,6 +324,35 @@ class DnsserverTests(RpcInterfaceTestCase):
                 self.assert_num_records(self.custom_zone, "testrecord", record_type_str)
                 self.delete_record(self.custom_zone, "testrecord", record_type_str, record_str)
 
+    def check_params(self, wDataLength, rank, flags, dwTtlSeconds, dwReserved, data,
+                     wType, dwTimeStamp=0, zone="zone", rec_name="testrecord"):
+        res = self.get_record_from_db(zone, rec_name)
+        self.assertIsNotNone(res, "Expected record %s but was not found over LDAP." % data)
+        (rec_dn, rec) = res
+        self.assertEqual(wDataLength, rec.wDataLength, "Unexpected data length for record %s. Got %s, expected %s." % (data, rec.wDataLength, wDataLength))
+        self.assertEqual(rank, rec.rank, "Unexpected rank for record %s. Got %s, expected %s." % (data, rec.rank, rank))
+        self.assertEqual(flags, rec.flags, "Unexpected flags for record %s. Got %s, expected %s." % (data, rec.flags, flags))
+        self.assertEqual(dwTtlSeconds, rec.dwTtlSeconds, "Unexpected time to live for record %s. Got %s, expected %s." % (data, rec.dwTtlSeconds, dwTtlSeconds))
+        self.assertEqual(dwReserved, rec.dwReserved, "Unexpected dwReserved for record %s. Got %s, expected %s." % (data, rec.dwReserved, dwReserved))
+        self.assertEqual(data.lower(), rec.data.lower(), "Unexpected data for record %s. Got %s, expected %s." % (data, rec.data.lower(), data.lower()))
+        self.assertEqual(wType, rec.wType, "Unexpected wType for record %s. Got %s, expected %s." % (data, rec.wType, wType))
+        self.assertEqual(dwTimeStamp, rec.dwTimeStamp, "Unexpected timestamp for record %s. Got %s, expected %s." % (data, rec.dwTimeStamp, dwTimeStamp))
+
+    def test_record_params(self):
+        """
+        Make sure that, when we add records to the database,
+        they're added with reasonable parameters.
+        """
+        self.add_record(self.custom_zone, "testrecord", "A", "192.168.50.50")
+        self.check_params(4, 240, 0, 900, 0, "192.168.50.50", 1)
+        self.delete_record(self.custom_zone, "testrecord", "A", "192.168.50.50")
+        self.add_record(self.custom_zone, "testrecord", "AAAA", "AAAA:AAAA::")
+        self.check_params(16, 240, 0, 900, 0, "AAAA:AAAA:0000:0000:0000:0000:0000:0000", 28)
+        self.delete_record(self.custom_zone, "testrecord", "AAAA", "AAAA:AAAA::")
+        self.add_record(self.custom_zone, "testrecord", "CNAME", "cnamedest")
+        self.check_params(13, 240, 0, 900, 0, "cnamedest", 5)
+        self.delete_record(self.custom_zone, "testrecord", "CNAME", "cnamedest")
+
     def test_reject_invalid_commands(self):
         """
         Make sure that we can't add a variety of invalid records,
-- 
2.11.0


From 437224c77db67322ef90402efa4431800e98a09d Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 10 Jul 2018 17:13:48 +1200
Subject: [PATCH 18/18] WHATSNEW: Add entry for "Dynamic DNS record scavenging
 support"

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 WHATSNEW.txt | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/WHATSNEW.txt b/WHATSNEW.txt
index 2ceacc41995..11f75da86d4 100644
--- a/WHATSNEW.txt
+++ b/WHATSNEW.txt
@@ -66,6 +66,23 @@ Kerberos would return ALICE as the username. Kerberos would not be able to map
 names can be correctly mapped. This only applies to GSSAPI authentication,
 not for the geting the initial ticket granting ticket.
 
+Dynamic DNS record scavenging support
+-------------------------------------
+
+It is now possible to enable scavenging of DNS Zones to remove DNS
+records that were dynamically created and have not been touched in
+some time.
+
+This support should however only be enabled on new zones or new
+installations.  Sadly old Samba versions suffer from BUG 12451 and
+mark dynamic DNS records as static and static records as dynamic.
+While a dbcheck rule may be able to find these in the future,
+currently a reliable test has not been devised.
+
+Finally, there is not currently a command-line tool to enable this
+feature, currently it should be enabled from the DNS Manager tool from
+Windows.
+
 REMOVED FEATURES
 ================
 
-- 
2.11.0



More information about the samba-technical mailing list