[PATCH][WIP] Create DC DNS entires at domain join

Andrew Bartlett abartlet at samba.org
Mon May 29 05:05:19 UTC 2017


G'Day metze,

Attached is my current revision of the DNS at Domain Join patch-set. 
I've addressed some of your comments, but haven't made any major
changes, so a number of your desires (for example re-using
samba_dnsupdate) have not been taken forward.

I have however made samba_dnsupdate much more robust, because it now
operates in environments where the /etc/resolv.conf does not point at
the SOA directly.  

Additionally, we now fix the DNS server to overstamp the SOA with the
current server name.  

These two elements actually address the biggest issues here, because
the way samba_dnsupate was previously written it would only talk to the
/etc/resolv.conf server, but insist on getting a ticket named with the
SOA's name.  This really didn't work well.

I hope we can agree that this, by ensuring the minimum entries for
replication are present after the join, is an improvement over the
current state.

I realise this is not exactly what you were looking for, but I still
hope to get this into master soon, as I both need to wrap this area of
work up and don't want this lost for 4.7.  

I plan to add in a couple of tests for the join.py changes and propose
it for review tomorrow, so if you do see something you are still really
unhappy about, please let me know.

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 a1054d7151e7fe81730e5b7ba66e5431587c755a Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Fri, 17 Feb 2017 18:24:27 +1300
Subject: [PATCH 01/13] samba_dnsupdate: Ensure we only force "server" under
 resolv_wrapper

This ensures that nsupdate can use a namserver in /etc/resolv.conf that is a
cache or forwarder, rather than the AD DC directly.

This avoids a regression from forcing the nameservers to the
/etc/resolv.conf nameservers in
e85ef1dbfef4b16c35cac80c0efc563d8cd1ba3e

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/scripting/bin/samba_dnsupdate | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/source4/scripting/bin/samba_dnsupdate b/source4/scripting/bin/samba_dnsupdate
index d382758168b..ba167da2876 100755
--- a/source4/scripting/bin/samba_dnsupdate
+++ b/source4/scripting/bin/samba_dnsupdate
@@ -430,8 +430,19 @@ def call_nsupdate(d, op="add"):
 
     (tmp_fd, tmpfile) = tempfile.mkstemp()
     f = os.fdopen(tmp_fd, 'w')
-    if d.nameservers != []:
+
+    # Getting this line right is really important.  When we are under
+    # resolv_wrapper, then we want to use RESOLV_CONF and the
+    # nameserver therein. The issue is that this parameter forces us
+    # to only ever use that server, and not some other server that the
+    # NS record may point to, even as we get a ticket to that other
+    # server.
+    #
+    # Therefore we must not set this in production.
+
+    if os.getenv('RESOLV_CONF') and d.nameservers != []:
         f.write('server %s\n' % d.nameservers[0])
+
     if d.type == "A":
         f.write("update %s %s %u A %s\n" % (op, normalised_name, default_ttl, d.ip))
     if d.type == "AAAA":
-- 
2.11.0


From 85a7c72121737ce0980eba9557b2e1a6f1aedc2c Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Mon, 27 Feb 2017 16:51:45 +1300
Subject: [PATCH 02/13] pydns: Fix leak of talloc_stackframe() in python
 bindings

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/dns_server/pydns.c | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/source4/dns_server/pydns.c b/source4/dns_server/pydns.c
index 9842f24edfd..18c3c2953d9 100644
--- a/source4/dns_server/pydns.c
+++ b/source4/dns_server/pydns.c
@@ -124,12 +124,14 @@ static PyObject *py_dsdb_dns_lookup(PyObject *self, PyObject *args)
 
 	status = dns_common_zones(samdb, frame, &zones_list);
 	if (!NT_STATUS_IS_OK(status)) {
+		talloc_free(frame);
 		PyErr_SetNTSTATUS(status);
 		return NULL;
 	}
 
 	werr = dns_common_name2dn(samdb, zones_list, frame, dns_name, &dn);
 	if (!W_ERROR_IS_OK(werr)) {
+		talloc_free(frame);
 		PyErr_SetWERROR(werr);
 		return NULL;
 	}
@@ -141,16 +143,19 @@ static PyObject *py_dsdb_dns_lookup(PyObject *self, PyObject *args)
 				 &num_records,
 				 NULL);
 	if (!W_ERROR_IS_OK(werr)) {
+		talloc_free(frame);
 		PyErr_SetWERROR(werr);
 		return NULL;
 	}
 
-	return py_dnsp_DnssrvRpcRecord_get_list(records, num_records);
+	ret = py_dnsp_DnssrvRpcRecord_get_list(records, num_records);
+	talloc_free(frame);
+	return ret;
 }
 
 static PyObject *py_dsdb_dns_extract(PyObject *self, PyObject *args)
 {
-	PyObject *py_dns_el;
+	PyObject *py_dns_el, *ret;
 	TALLOC_CTX *frame;
 	WERROR werr;
 	struct ldb_message_element *dns_el;
@@ -175,11 +180,14 @@ static PyObject *py_dsdb_dns_extract(PyObject *self, PyObject *args)
 				  &records,
 				  &num_records);
 	if (!W_ERROR_IS_OK(werr)) {
+		talloc_free(frame);
 		PyErr_SetWERROR(werr);
 		return NULL;
 	}
 
-	return py_dnsp_DnssrvRpcRecord_get_list(records, num_records);
+	ret = py_dnsp_DnssrvRpcRecord_get_list(records, num_records);
+	talloc_free(frame);
+	return ret;
 }
 
 static PyObject *py_dsdb_dns_replace(PyObject *self, PyObject *args)
@@ -213,12 +221,14 @@ static PyObject *py_dsdb_dns_replace(PyObject *self, PyObject *args)
 	status = dns_common_zones(samdb, frame, &zones_list);
 	if (!NT_STATUS_IS_OK(status)) {
 		PyErr_SetNTSTATUS(status);
+		talloc_free(frame);
 		return NULL;
 	}
 
 	werr = dns_common_name2dn(samdb, zones_list, frame, dns_name, &dn);
 	if (!W_ERROR_IS_OK(werr)) {
 		PyErr_SetWERROR(werr);
+		talloc_free(frame);
 		return NULL;
 	}
 
@@ -226,6 +236,7 @@ static PyObject *py_dsdb_dns_replace(PyObject *self, PyObject *args)
 						frame,
 						&records, &num_records);
 	if (ret != 0) {
+		talloc_free(frame);
 		return NULL;
 	}
 
@@ -238,9 +249,11 @@ static PyObject *py_dsdb_dns_replace(PyObject *self, PyObject *args)
 				  num_records);
 	if (!W_ERROR_IS_OK(werr)) {
 		PyErr_SetWERROR(werr);
+		talloc_free(frame);
 		return NULL;
 	}
 
+	talloc_free(frame);
 	Py_RETURN_NONE;
 }
 
@@ -275,6 +288,7 @@ static PyObject *py_dsdb_dns_replace_by_dn(PyObject *self, PyObject *args)
 						frame,
 						&records, &num_records);
 	if (ret != 0) {
+		talloc_free(frame);
 		return NULL;
 	}
 
@@ -287,9 +301,12 @@ static PyObject *py_dsdb_dns_replace_by_dn(PyObject *self, PyObject *args)
 				  num_records);
 	if (!W_ERROR_IS_OK(werr)) {
 		PyErr_SetWERROR(werr);
+		talloc_free(frame);
 		return NULL;
 	}
 
+	talloc_free(frame);
+
 	Py_RETURN_NONE;
 }
 
-- 
2.11.0


From 08303dae789540c7c27899d898a0fedcef7657cb Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Mon, 27 Feb 2017 17:09:56 +1300
Subject: [PATCH 03/13] pydns: Also return the DN of the LDB object when
 finding a DNS record

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/remove_dc.py  | 4 ++--
 source4/dns_server/pydns.c | 5 +++--
 2 files changed, 5 insertions(+), 4 deletions(-)

diff --git a/python/samba/remove_dc.py b/python/samba/remove_dc.py
index 61b5937ba7a..4c8ee892464 100644
--- a/python/samba/remove_dc.py
+++ b/python/samba/remove_dc.py
@@ -97,7 +97,7 @@ def remove_dns_references(samdb, logger, dnsHostName):
     dnsHostNameUpper = dnsHostName.upper()
 
     try:
-        primary_recs = samdb.dns_lookup(dnsHostName)
+        (dn, primary_recs) = samdb.dns_lookup(dnsHostName)
     except RuntimeError as (enum, estr):
         if enum == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
               return
@@ -140,7 +140,7 @@ def remove_dns_references(samdb, logger, dnsHostName):
     for a_name in a_names_to_remove_from:
         try:
             logger.debug("checking for DNS records to remove on %s" % a_name)
-            a_recs = samdb.dns_lookup(a_name)
+            (a_rec_dn, a_recs) = samdb.dns_lookup(a_name)
         except RuntimeError as (enum, estr):
             if enum == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
                 return
diff --git a/source4/dns_server/pydns.c b/source4/dns_server/pydns.c
index 18c3c2953d9..3de9739f1f1 100644
--- a/source4/dns_server/pydns.c
+++ b/source4/dns_server/pydns.c
@@ -105,7 +105,7 @@ static int py_dnsp_DnssrvRpcRecord_get_array(PyObject *value,
 static PyObject *py_dsdb_dns_lookup(PyObject *self, PyObject *args)
 {
 	struct ldb_context *samdb;
-	PyObject *py_ldb;
+	PyObject *py_ldb, *ret, *pydn;
 	char *dns_name;
 	TALLOC_CTX *frame;
 	NTSTATUS status;
@@ -149,8 +149,9 @@ static PyObject *py_dsdb_dns_lookup(PyObject *self, PyObject *args)
 	}
 
 	ret = py_dnsp_DnssrvRpcRecord_get_list(records, num_records);
+	pydn = pyldb_Dn_FromDn(dn);
 	talloc_free(frame);
-	return ret;
+	return Py_BuildValue("(OO)", pydn, ret);
 }
 
 static PyObject *py_dsdb_dns_extract(PyObject *self, PyObject *args)
-- 
2.11.0


From c9a527817d3f729780d7a3fab1ef7a22da88a9d4 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 28 Feb 2017 14:15:12 +1300
Subject: [PATCH 04/13] python: Allow sd_utils to take a Dn object, not just a
 string DN

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/sd_utils.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/python/samba/sd_utils.py b/python/samba/sd_utils.py
index 7592a2982a4..568829f9c36 100644
--- a/python/samba/sd_utils.py
+++ b/python/samba/sd_utils.py
@@ -37,7 +37,11 @@ class SDUtils(object):
             or security.descriptor object
         """
         m = Message()
-        m.dn = Dn(self.ldb, object_dn)
+        if isinstance(object_dn, Dn):
+            m.dn = object_dn
+        else:
+            m.dn = Dn(self.ldb, object_dn)
+
         assert(isinstance(sd, str) or isinstance(sd, security.descriptor))
         if isinstance(sd, str):
             tmp_desc = security.descriptor.from_sddl(sd, self.domain_sid)
-- 
2.11.0


From 54341ba8ad6e604f3cea9cec01d6d97229428224 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Fri, 17 Feb 2017 18:23:23 +1300
Subject: [PATCH 05/13] join.py Add DNS records at domain join time

This avoids issues getting replication going after the DC first starts
as the rest of the domain does not have to wait for samba_dnsupdate to
run successfully

We do not just run samba_dnsupdate as we want to strictly
operate against the DC we just joined:
 - We do not want to query another DNS server
 - We do not want to obtain a Kerberos ticket for the new DC
   (as the KDC we select may not be the DC we just joined,
   and so may not be in sync with the password we just set)
 - We do not wish to set the _ldap records until we have started
 - We do not wish to use NTLM (the --use-samba-tool mode forces
   NTLM)

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/join.py | 196 ++++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 193 insertions(+), 3 deletions(-)

diff --git a/python/samba/join.py b/python/samba/join.py
index 6a924359407..9e8e00f2348 100644
--- a/python/samba/join.py
+++ b/python/samba/join.py
@@ -22,8 +22,8 @@ from samba.auth import system_session
 from samba.samdb import SamDB
 from samba import gensec, Ldb, drs_utils, arcfour_encrypt, string_to_byte_array
 import ldb, samba, sys, uuid
-from samba.ndr import ndr_pack
-from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs
+from samba.ndr import ndr_pack, ndr_unpack
+from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp
 from samba.dsdb import DS_DOMAIN_FUNCTION_2003
 from samba.credentials import Credentials, DONT_USE_KERBEROS
 from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN
@@ -35,6 +35,9 @@ from samba.provision.sambadns import setup_bind9_dns
 from samba import read_and_sub_file
 from samba import werror
 from base64 import b64encode
+from samba import WERRORError
+from samba.dnsserver import ARecord, AAAARecord, PTRRecord, CNameRecord, NSRecord, MXRecord, SOARecord, SRVRecord, TXTRecord
+from samba import sd_utils
 import logging
 import talloc
 import random
@@ -184,6 +187,9 @@ class dc_join(object):
         ctx.adminpass = None
         ctx.partition_dn = None
 
+        ctx.dns_a_dn = None
+        ctx.dns_cname_dn = None
+
     def del_noerror(ctx, dn, recursive=False):
         if recursive:
             try:
@@ -289,6 +295,13 @@ class dc_join(object):
 
             lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
 
+        if ctx.dns_a_dn:
+            ctx.del_noerror(ctx.dns_a_dn)
+
+        if ctx.dns_cname_dn:
+            ctx.del_noerror(ctx.dns_cname_dn)
+
+
 
     def promote_possible(ctx):
         """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
@@ -687,12 +700,16 @@ class dc_join(object):
                                      newpassword=ctx.acct_pass.encode('utf-8'))
 
             res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
-                                   attrs=["msDS-KeyVersionNumber"])
+                                   attrs=["msDS-KeyVersionNumber",
+                                          "objectSID"])
             if "msDS-KeyVersionNumber" in res[0]:
                 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
             else:
                 ctx.key_version_number = None
 
+            ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
+                                                res[0]["objectSid"][0])
+
             print("Enabling account")
             m = ldb.Message()
             m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
@@ -969,6 +986,174 @@ class dc_join(object):
 
         ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
 
+    def join_add_dns_records(ctx):
+        """Remotely Add a DNS record to the target DC.  We assume that if we
+           replicate DNS that the server holds the DNS roles and can accept
+           updates.
+
+           This avoids issues getting replication going after the DC
+           first starts as the rest of the domain does not have to
+           wait for samba_dnsupdate to run successfully.
+
+           Specifically, we add the records implied by the DsReplicaUpdateRefs
+           call above.
+
+           We do not just run samba_dnsupdate as we want to strictly
+           operate against the DC we just joined:
+            - We do not want to query another DNS server
+            - We do not want to obtain a Kerberos ticket
+              (as the KDC we select may not be the DC we just joined,
+              and so may not be in sync with the password we just set)
+            - We do not wish to set the _ldap records until we have started
+            - We do not wish to use NTLM (the --use-samba-tool mode forces
+              NTLM)
+
+        """
+
+        client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
+        record_type = dnsp.DNS_TYPE_A
+        select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
+                       dnsserver.DNS_RPC_VIEW_NO_CHILDREN
+
+        zone = ctx.dnsdomain
+        msdcs_zone = "_msdcs.%s" % ctx.dnsforest
+        name = ctx.myname
+        msdcs_cname = str(ctx.ntds_guid)
+        cname_target = "%s.%s" % (name, zone)
+        IPs = samba.interface_ips(ctx.lp, False)
+
+        ctx.logger.info("Adding remote DNS records for %s.%s" % (name, zone))
+
+        binding_options = "sign"
+        dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
+                                      ctx.lp, ctx.creds)
+
+
+        name_found = True
+
+        sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
+
+        change_owner_sd = security.descriptor()
+        change_owner_sd.owner_sid = ctx.new_dc_account_sid
+        change_owner_sd.group_sid = security.dom_sid("%s-%d" %
+                                                     (str(ctx.domsid),
+                                                      security.DOMAIN_RID_DCS))
+
+        # TODO: Remove any old records from the primary DNS name
+        try:
+            (buflen, res) \
+                = dns_conn.DnssrvEnumRecords2(client_version,
+                                              0,
+                                              ctx.server,
+                                              zone,
+                                              name,
+                                              None,
+                                              dnsp.DNS_TYPE_ALL,
+                                              select_flags,
+                                              None,
+                                              None)
+        except WERRORError as e:
+            if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
+                name_found = False
+                pass
+
+        if name_found:
+            for rec in res.rec:
+                for record in rec.records:
+                    if record.wType == dnsp.DNS_TYPE_A or \
+                       record.wType == dnsp.DNS_TYPE_AAAA:
+                        # delete record
+                        del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+                        del_rec_buf.rec = record
+                        try:
+                            dns_conn.DnssrvUpdateRecord2(client_version,
+                                                         0,
+                                                         ctx.server,
+                                                         zone,
+                                                         name,
+                                                         None,
+                                                         del_rec_buf)
+                        except WERRORError as e:
+                            if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
+                                pass
+                            else:
+                                raise
+
+        for IP in IPs:
+            if IP.find(':') != -1:
+                ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
+                                % (name, zone, IP))
+                rec = AAAARecord(IP)
+            else:
+                ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
+                                % (name, zone, IP))
+                rec = ARecord(IP)
+
+            # Add record
+            add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+            add_rec_buf.rec = rec
+            dns_conn.DnssrvUpdateRecord2(client_version,
+                                         0,
+                                         ctx.server,
+                                         zone,
+                                         name,
+                                         add_rec_buf,
+                                         None)
+
+        if (len(IPs) > 0):
+            domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
+            (ctx.dns_a_dn, ldap_record) \
+                = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
+                                       dns_partition=domaindns_zone_dn)
+
+            # Make the DC own the DNS record, not the administrator
+            sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
+                                      controls=["sd_flags:1:%d"
+                                                % (security.SECINFO_OWNER
+                                                   | security.SECINFO_GROUP)])
+
+
+            # Add record
+            ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
+                            % (msdcs_zone, msdcs_cname, cname_target))
+
+            add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+            rec = CNameRecord(cname_target)
+            add_rec_buf.rec = rec
+            dns_conn.DnssrvUpdateRecord2(client_version,
+                                         0,
+                                         ctx.server,
+                                         msdcs_zone,
+                                         msdcs_cname,
+                                         add_rec_buf,
+                                         None)
+
+            forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
+            (ctx.dns_cname_dn, ldap_record) \
+                = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
+                                       dns_partition=forestdns_zone_dn)
+
+            # Make the DC own the DNS record, not the administrator
+            sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
+                                      controls=["sd_flags:1:%d"
+                                                % (security.SECINFO_OWNER
+                                                   | security.SECINFO_GROUP)])
+
+        ctx.logger.info("All other DNS records (like _ldap SRV records) " +
+                        "will be created samba_dnsupdate on first startup")
+
+
+    def join_replicate_new_dns_records(ctx):
+        for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
+            if nc in ctx.nc_list:
+                print "Replicating new DNS records in %s" % (str(nc))
+                ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
+                                   ctx.ntds_guid, rodc=ctx.RODC,
+                                   replica_flags=ctx.replica_flags,
+                                   full_sync=False)
+
+
+
     def join_finalise(ctx):
         """Finalise the join, mark us synchronised and setup secrets db."""
 
@@ -1185,6 +1370,11 @@ class dc_join(object):
                 ctx.join_add_objects2()
                 ctx.join_provision_own_domain()
                 ctx.join_setup_trusts()
+
+            if not ctx.clone_only and ctx.dns_backend != "NONE":
+                ctx.join_add_dns_records()
+                ctx.join_replicate_new_dns_records()
+
             ctx.join_finalise()
         except:
             try:
-- 
2.11.0


From da1d9a6b7a1585b06cdc3e3ac380507844a66709 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Mon, 10 Apr 2017 16:06:13 +1200
Subject: [PATCH 06/13] pydsdb_dns: Use TypeError not LdbError for mismatched
 types

This avoids the samba-tool command handling code blowing up when trying to parse an LdbError

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/dns_server/dns_server.c       |  2 +-
 source4/dns_server/dnsserver_common.c | 17 ++++++++++++++---
 source4/dns_server/dnsserver_common.h |  7 +++++++
 source4/dns_server/pydns.c            | 15 +++------------
 4 files changed, 25 insertions(+), 16 deletions(-)

diff --git a/source4/dns_server/dns_server.c b/source4/dns_server/dns_server.c
index 5e9527d1f72..d4f5f27d0bb 100644
--- a/source4/dns_server/dns_server.c
+++ b/source4/dns_server/dns_server.c
@@ -764,7 +764,7 @@ static NTSTATUS dns_server_reload_zones(struct dns_server *dns)
 	struct dns_server_zone *new_list = NULL;
 	struct dns_server_zone *old_list = NULL;
 	struct dns_server_zone *old_zone;
-	status = dns_common_zones(dns->samdb, dns, &new_list);
+	status = dns_common_zones(dns->samdb, dns, NULL, &new_list);
 	if (!NT_STATUS_IS_OK(status)) {
 		return status;
 	}
diff --git a/source4/dns_server/dnsserver_common.c b/source4/dns_server/dnsserver_common.c
index 7aac7e22855..fbfa5fa4eae 100644
--- a/source4/dns_server/dnsserver_common.c
+++ b/source4/dns_server/dnsserver_common.c
@@ -560,6 +560,7 @@ static int dns_common_sort_zones(struct ldb_message **m1, struct ldb_message **m
 
 NTSTATUS dns_common_zones(struct ldb_context *samdb,
 			  TALLOC_CTX *mem_ctx,
+			  struct ldb_dn *base_dn,
 			  struct dns_server_zone **zones_ret)
 {
 	int ret;
@@ -569,9 +570,19 @@ NTSTATUS dns_common_zones(struct ldb_context *samdb,
 	struct dns_server_zone *new_list = NULL;
 	TALLOC_CTX *frame = talloc_stackframe();
 
-	/* TODO: this search does not work against windows */
-	ret = dsdb_search(samdb, frame, &res, NULL, LDB_SCOPE_SUBTREE,
-			  attrs, DSDB_SEARCH_SEARCH_ALL_PARTITIONS, "(objectClass=dnsZone)");
+	if (base_dn) {
+		/* This search will work against windows */
+		ret = dsdb_search(samdb, frame, &res,
+				  base_dn, LDB_SCOPE_SUBTREE,
+				  attrs, 0, "(objectClass=dnsZone)");
+	} else {
+		/* TODO: this search does not work against windows */
+		ret = dsdb_search(samdb, frame, &res, NULL,
+				  LDB_SCOPE_SUBTREE,
+				  attrs,
+				  DSDB_SEARCH_SEARCH_ALL_PARTITIONS,
+				  "(objectClass=dnsZone)");
+	}
 	if (ret != LDB_SUCCESS) {
 		TALLOC_FREE(frame);
 		return NT_STATUS_INTERNAL_DB_CORRUPTION;
diff --git a/source4/dns_server/dnsserver_common.h b/source4/dns_server/dnsserver_common.h
index 57d5d9f3c15..293831f0acb 100644
--- a/source4/dns_server/dnsserver_common.h
+++ b/source4/dns_server/dnsserver_common.h
@@ -62,7 +62,14 @@ WERROR dns_common_name2dn(struct ldb_context *samdb,
 			  TALLOC_CTX *mem_ctx,
 			  const char *name,
 			  struct ldb_dn **_dn);
+
+/*
+ * For this routine, base_dn is generally NULL.  The exception comes
+ * from the python bindings to support setting ACLs on DNS objects
+ * when joining Windows
+ */
 NTSTATUS dns_common_zones(struct ldb_context *samdb,
 			  TALLOC_CTX *mem_ctx,
+			  struct ldb_dn *base_dn,
 			  struct dns_server_zone **zones_ret);
 #endif /* __DNSSERVER_COMMON_H__ */
diff --git a/source4/dns_server/pydns.c b/source4/dns_server/pydns.c
index 3de9739f1f1..7fc8f0c8811 100644
--- a/source4/dns_server/pydns.c
+++ b/source4/dns_server/pydns.c
@@ -32,27 +32,18 @@
 /* FIXME: These should be in a header file somewhere */
 #define PyErr_LDB_OR_RAISE(py_ldb, ldb) \
 	if (!py_check_dcerpc_type(py_ldb, "ldb", "Ldb")) { \
-		PyErr_SetString(py_ldb_get_exception(), "Ldb connection object required"); \
+		PyErr_SetString(PyExc_TypeError, "Ldb connection object required"); \
 		return NULL; \
 	} \
 	ldb = pyldb_Ldb_AsLdbContext(py_ldb);
 
 #define PyErr_LDB_DN_OR_RAISE(py_ldb_dn, dn) \
 	if (!py_check_dcerpc_type(py_ldb_dn, "ldb", "Dn")) { \
-		PyErr_SetString(py_ldb_get_exception(), "ldb Dn object required"); \
+		PyErr_SetString(PyExc_TypeError, "ldb Dn object required"); \
 		return NULL; \
 	} \
 	dn = pyldb_Dn_AsDn(py_ldb_dn);
 
-static PyObject *py_ldb_get_exception(void)
-{
-	PyObject *mod = PyImport_ImportModule("ldb");
-	if (mod == NULL)
-		return NULL;
-
-	return PyObject_GetAttrString(mod, "LdbError");
-}
-
 static PyObject *py_dnsp_DnssrvRpcRecord_get_list(struct dnsp_DnssrvRpcRecord *records,
 						  uint16_t num_records)
 {
@@ -168,7 +159,7 @@ static PyObject *py_dsdb_dns_extract(PyObject *self, PyObject *args)
 	}
 
 	if (!py_check_dcerpc_type(py_dns_el, "ldb", "MessageElement")) {
-		PyErr_SetString(py_ldb_get_exception(),
+		PyErr_SetString(PyExc_TypeError,
 				"ldb MessageElement object required");
 		return NULL;
 	}
-- 
2.11.0


From 56fdec3011dd9a70f6ffe58227be281558569064 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Mon, 10 Apr 2017 16:08:39 +1200
Subject: [PATCH 07/13] pydsdb_dns: Allow the partition DN to be specified into
 py_dsdb_dns_lookup

This allows lookups to be confined to one partition, which in turn avoids issues
when running this against MS Windows, which does not match Samba behaviour
for dns_common_zones()

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/samdb.py      |  8 ++++++--
 source4/dns_server/pydns.c | 26 ++++++++++++++++++++------
 2 files changed, 26 insertions(+), 8 deletions(-)

diff --git a/python/samba/samdb.py b/python/samba/samdb.py
index 19dd8e9a6ad..b3a4b384926 100644
--- a/python/samba/samdb.py
+++ b/python/samba/samdb.py
@@ -927,9 +927,13 @@ accountExpires: %u
         res = self.search(base="", scope=ldb.SCOPE_BASE, attrs=["serverName"])
         return res[0]["serverName"][0]
 
-    def dns_lookup(self, dns_name):
+    def dns_lookup(self, dns_name, dns_partition=None):
         '''Do a DNS lookup in the database, returns the NDR database structures'''
-        return dsdb_dns.lookup(self, dns_name)
+        if dns_partition is None:
+            return dsdb_dns.lookup(self, dns_name)
+        else:
+            return dsdb_dns.lookup(self, dns_name,
+                                   dns_partition=dns_partition)
 
     def dns_extract(self, el):
         '''Return the NDR database structures from a dnsRecord element'''
diff --git a/source4/dns_server/pydns.c b/source4/dns_server/pydns.c
index 7fc8f0c8811..cb41faa1441 100644
--- a/source4/dns_server/pydns.c
+++ b/source4/dns_server/pydns.c
@@ -93,27 +93,40 @@ static int py_dnsp_DnssrvRpcRecord_get_array(PyObject *value,
 	return 0;
 }
 
-static PyObject *py_dsdb_dns_lookup(PyObject *self, PyObject *args)
+static PyObject *py_dsdb_dns_lookup(PyObject *self,
+				    PyObject *args, PyObject *kwargs)
 {
 	struct ldb_context *samdb;
 	PyObject *py_ldb, *ret, *pydn;
+	PyObject *py_dns_partition = NULL;
 	char *dns_name;
 	TALLOC_CTX *frame;
 	NTSTATUS status;
 	WERROR werr;
 	struct dns_server_zone *zones_list;
-	struct ldb_dn *dn;
+	struct ldb_dn *dn, *dns_partition = NULL;
 	struct dnsp_DnssrvRpcRecord *records;
 	uint16_t num_records;
+	const char * const kwnames[] = { "ldb", "dns_name",
+					 "dns_partition", NULL };
 
-	if (!PyArg_ParseTuple(args, "Os", &py_ldb, &dns_name)) {
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "Os|O",
+					 discard_const_p(char *, kwnames),
+					 &py_ldb, &dns_name,
+					 &py_dns_partition)) {
 		return NULL;
 	}
 	PyErr_LDB_OR_RAISE(py_ldb, samdb);
 
+	if (py_dns_partition) {
+		PyErr_LDB_DN_OR_RAISE(py_dns_partition,
+				      dns_partition);
+	}
+
 	frame = talloc_stackframe();
 
-	status = dns_common_zones(samdb, frame, &zones_list);
+	status = dns_common_zones(samdb, frame, dns_partition,
+				  &zones_list);
 	if (!NT_STATUS_IS_OK(status)) {
 		talloc_free(frame);
 		PyErr_SetNTSTATUS(status);
@@ -210,7 +223,7 @@ static PyObject *py_dsdb_dns_replace(PyObject *self, PyObject *args)
 
 	frame = talloc_stackframe();
 
-	status = dns_common_zones(samdb, frame, &zones_list);
+	status = dns_common_zones(samdb, frame, NULL, &zones_list);
 	if (!NT_STATUS_IS_OK(status)) {
 		PyErr_SetNTSTATUS(status);
 		talloc_free(frame);
@@ -305,7 +318,8 @@ static PyObject *py_dsdb_dns_replace_by_dn(PyObject *self, PyObject *args)
 static PyMethodDef py_dsdb_dns_methods[] = {
 
 	{ "lookup", (PyCFunction)py_dsdb_dns_lookup,
-		METH_VARARGS, "Get the DNS database entries for a DNS name"},
+	        METH_VARARGS|METH_KEYWORDS,
+	        "Get the DNS database entries for a DNS name"},
 	{ "replace", (PyCFunction)py_dsdb_dns_replace,
 		METH_VARARGS, "Replace the DNS database entries for a DNS name"},
 	{ "replace_by_dn", (PyCFunction)py_dsdb_dns_replace_by_dn,
-- 
2.11.0


From 3f1dd6a1af0b90ae3871754d6c9967d99eadc641 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Mon, 10 Apr 2017 16:10:00 +1200
Subject: [PATCH 08/13] join.py: Do not expose the old machine password over
 NTLM if -k yes was set

This makes the test for a valid machine account stricter (as a kerberos error could
cause this to fail and so skip the validation), but we never wish to use NTLM
if the administrator disabled it on the command line

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/join.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/python/samba/join.py b/python/samba/join.py
index 9e8e00f2348..25eaf6be8d4 100644
--- a/python/samba/join.py
+++ b/python/samba/join.py
@@ -215,6 +215,7 @@ class dc_join(object):
         creds.guess(ctx.lp)
         try:
             creds.set_machine_account(ctx.lp)
+            creds.set_kerberos_state(ctx.creds.get_kerberos_state())
             machine_samdb = SamDB(url="ldap://%s" % ctx.server,
                                   session_info=system_session(),
                                 credentials=creds, lp=ctx.lp)
-- 
2.11.0


From a4f7270c9e0da81cb4823f114b947d859de4ab27 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Mon, 10 Apr 2017 17:10:27 +1200
Subject: [PATCH 09/13] samba_dnsupdate: Make nsupdate use the server given by
 the SOA record

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/scripting/bin/samba_dnsupdate | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/source4/scripting/bin/samba_dnsupdate b/source4/scripting/bin/samba_dnsupdate
index ba167da2876..80a5a6f484d 100755
--- a/source4/scripting/bin/samba_dnsupdate
+++ b/source4/scripting/bin/samba_dnsupdate
@@ -237,7 +237,7 @@ def hostname_match(h1, h2):
     h2 = str(h2)
     return h1.lower().rstrip('.') == h2.lower().rstrip('.')
 
-def check_one_dns_name(name, name_type, d=None):
+def get_resolver(d=None):
     resolv_conf = os.getenv('RESOLV_CONF')
     if not resolv_conf:
         resolv_conf = '/etc/resolv.conf'
@@ -245,7 +245,12 @@ def check_one_dns_name(name, name_type, d=None):
 
     if d is not None and d.nameservers != []:
         resolver.nameservers = d.nameservers
-    elif d is not None:
+
+    return resolver
+
+def check_one_dns_name(name, name_type, d=None):
+    resolver = get_resolver(d)
+    if d is not None and len(d.nameservers) == 0:
         d.nameservers = resolver.nameservers
 
     ans = resolver.query(name, name_type)
@@ -438,10 +443,18 @@ def call_nsupdate(d, op="add"):
     # NS record may point to, even as we get a ticket to that other
     # server.
     #
-    # Therefore we must not set this in production.
+    # Therefore we must not set this in production, instead we want
+    # to find the name of a SOA for the zone and use that server.
 
     if os.getenv('RESOLV_CONF') and d.nameservers != []:
         f.write('server %s\n' % d.nameservers[0])
+    else:
+        resolver = get_resolver(d)
+        zone = dns.resolver.zone_for_name(normalised_name,
+                                          resolver=resolver)
+        soa = resolver.query(zone, "SOA")
+
+        f.write('server %s\n' % soa[0].mname)
 
     if d.type == "A":
         f.write("update %s %s %u A %s\n" % (op, normalised_name, default_ttl, d.ip))
-- 
2.11.0


From 5d4fbcad42ac28d565e3b7768f823cafb01b253b Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Mon, 10 Apr 2017 17:13:46 +1200
Subject: [PATCH 10/13] samba_dnsupate: Try to get ticket to the SOA, not the
 NS servers

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/scripting/bin/samba_dnsupdate | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/source4/scripting/bin/samba_dnsupdate b/source4/scripting/bin/samba_dnsupdate
index 80a5a6f484d..28343bf17d5 100755
--- a/source4/scripting/bin/samba_dnsupdate
+++ b/source4/scripting/bin/samba_dnsupdate
@@ -137,10 +137,12 @@ def get_credentials(lp):
         if opts.use_file is not None:
             return
 
-        # Now confirm we can get a ticket to a DNS server
-        ans = check_one_dns_name(sub_vars['DNSDOMAIN'] + '.', 'NS')
+        # Now confirm we can get a ticket to the DNS server
+        ans = check_one_dns_name(sub_vars['DNSDOMAIN'] + '.', 'SOA')
+
+        # Actually there is only one
         for i in range(len(ans)):
-            target_hostname = str(ans[i].target).rstrip('.')
+            target_hostname = str(ans[i].mname).rstrip('.')
             settings = {}
             settings["lp_ctx"] = lp
             settings["target_hostname"] = target_hostname
-- 
2.11.0


From ee72ca51d6282f656fa77b16ca1e3e56c981bb2f Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 11 Apr 2017 12:43:22 +1200
Subject: [PATCH 11/13] dns_server: clobber MNAME in the SOA

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 python/samba/samdb.py                 |  2 +-
 python/samba/tests/dns.py             | 18 ++++++++++++
 source4/dns_server/dlz_bind9.c        |  2 +-
 source4/dns_server/dnsserver_common.c | 53 +++++++++++++++++++++++++++++++++--
 source4/dns_server/dnsserver_common.h |  3 +-
 source4/dns_server/pydns.c            |  8 ++++--
 6 files changed, 78 insertions(+), 8 deletions(-)

diff --git a/python/samba/samdb.py b/python/samba/samdb.py
index b3a4b384926..e0021563a23 100644
--- a/python/samba/samdb.py
+++ b/python/samba/samdb.py
@@ -937,7 +937,7 @@ accountExpires: %u
 
     def dns_extract(self, el):
         '''Return the NDR database structures from a dnsRecord element'''
-        return dsdb_dns.extract(el)
+        return dsdb_dns.extract(self, el)
 
     def dns_replace(self, dns_name, new_records):
         '''Do a DNS modification on the database, sets the NDR database
diff --git a/python/samba/tests/dns.py b/python/samba/tests/dns.py
index b8a2481ae36..93a7a7a2b32 100644
--- a/python/samba/tests/dns.py
+++ b/python/samba/tests/dns.py
@@ -235,6 +235,24 @@ class TestSimpleQueries(DNSTest):
         self.assertEquals(response.answers[0].rdata,
                           self.server_ip)
 
+    def test_one_SOA_query(self):
+        "create a query packet containing one query record for the SOA"
+        p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
+        questions = []
+
+        name = "%s" % (self.get_dns_domain())
+        q = self.make_name_question(name, dns.DNS_QTYPE_SOA, dns.DNS_QCLASS_IN)
+        print "asking for ", q.name
+        questions.append(q)
+
+        self.finish_name_packet(p, questions)
+        response = self.dns_transaction_udp(p)
+        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())
+
     def test_one_a_query_tcp(self):
         "create a query packet containing one query record via TCP"
         p = self.make_name_packet(dns.DNS_OPCODE_QUERY)
diff --git a/source4/dns_server/dlz_bind9.c b/source4/dns_server/dlz_bind9.c
index 897699a6317..7096f4749b2 100644
--- a/source4/dns_server/dlz_bind9.c
+++ b/source4/dns_server/dlz_bind9.c
@@ -997,7 +997,7 @@ _PUBLIC_ isc_result_t dlz_allnodes(const char *zone, void *dbdata,
 			return ISC_R_NOMEMORY;
 		}
 
-		werr = dns_common_extract(el, el_ctx, &recs, &num_recs);
+		werr = dns_common_extract(state->samdb, el, el_ctx, &recs, &num_recs);
 		if (!W_ERROR_IS_OK(werr)) {
 			state->log(ISC_LOG_ERROR, "samba_dlz: failed to parse dnsRecord for %s, %s",
 				   ldb_dn_get_linearized(dn), win_errstr(werr));
diff --git a/source4/dns_server/dnsserver_common.c b/source4/dns_server/dnsserver_common.c
index fbfa5fa4eae..d0c0a2fdbb4 100644
--- a/source4/dns_server/dnsserver_common.c
+++ b/source4/dns_server/dnsserver_common.c
@@ -69,7 +69,8 @@ uint8_t werr_to_dns_err(WERROR werr)
 	return DNS_RCODE_SERVFAIL;
 }
 
-WERROR dns_common_extract(const struct ldb_message_element *el,
+WERROR dns_common_extract(struct ldb_context *samdb,
+			  const struct ldb_message_element *el,
 			  TALLOC_CTX *mem_ctx,
 			  struct dnsp_DnssrvRpcRecord **records,
 			  uint16_t *num_records)
@@ -86,9 +87,13 @@ WERROR dns_common_extract(const struct ldb_message_element *el,
 		return WERR_NOT_ENOUGH_MEMORY;
 	}
 	for (ri = 0; ri < el->num_values; ri++) {
+		bool am_rodc;
+		int ret;
+		const char *attrs[] = { "dnsHostName", NULL };
+		const char *dnsHostName;
 		struct ldb_val *v = &el->values[ri];
 		enum ndr_err_code ndr_err;
-
+		struct ldb_result *res = NULL;
 		ndr_err = ndr_pull_struct_blob(v, recs, &recs[ri],
 				(ndr_pull_flags_fn_t)ndr_pull_dnsp_DnssrvRpcRecord);
 		if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
@@ -96,7 +101,49 @@ WERROR dns_common_extract(const struct ldb_message_element *el,
 			DEBUG(0, ("Failed to grab dnsp_DnssrvRpcRecord\n"));
 			return DNS_ERR(SERVER_FAILURE);
 		}
+
+		/*
+		 * In AD, except on an RODC (where we should list a random RWDC,
+		 * we should over-stamp the MNAME with our own hostname
+		 */
+		if (recs[ri].wType != DNS_TYPE_SOA) {
+			continue;
+		}
+
+		ret = samdb_rodc(samdb, &am_rodc);
+		if (ret != LDB_SUCCESS) {
+			DEBUG(0, ("Failed to confirm we are not an RODC: %s\n",
+				  ldb_errstring(samdb)));
+			return DNS_ERR(SERVER_FAILURE);
+		}
+
+		if (am_rodc) {
+			continue;
+		}
+
+		ret = dsdb_search_dn(samdb, mem_ctx, &res, NULL,
+				     attrs, 0);
+
+		if (res->count != 1 || ret != LDB_SUCCESS) {
+			DEBUG(0, ("Failed to get rootDSE for dnsHostName: %s",
+				  ldb_errstring(samdb)));
+			return DNS_ERR(SERVER_FAILURE);
+		}
+
+		dnsHostName
+			= ldb_msg_find_attr_as_string(res->msgs[0],
+						      "dnsHostName",
+						      NULL);
+
+		if (dnsHostName == NULL) {
+			DEBUG(0, ("Failed to get dnsHostName from rootDSE"));
+			return DNS_ERR(SERVER_FAILURE);
+		}
+
+		recs[ri].data.soa.mname
+			= talloc_steal(recs, dnsHostName);
 	}
+
 	*records = recs;
 	*num_records = el->num_values;
 	return WERR_OK;
@@ -189,7 +236,7 @@ WERROR dns_common_lookup(struct ldb_context *samdb,
 		}
 	}
 
-	werr = dns_common_extract(el, mem_ctx, records, num_records);
+	werr = dns_common_extract(samdb, el, mem_ctx, records, num_records);
 	TALLOC_FREE(msg);
 	if (!W_ERROR_IS_OK(werr)) {
 		return werr;
diff --git a/source4/dns_server/dnsserver_common.h b/source4/dns_server/dnsserver_common.h
index 293831f0acb..b615e2dcfae 100644
--- a/source4/dns_server/dnsserver_common.h
+++ b/source4/dns_server/dnsserver_common.h
@@ -35,7 +35,8 @@ struct dns_server_zone {
 	struct ldb_dn *dn;
 };
 
-WERROR dns_common_extract(const struct ldb_message_element *el,
+WERROR dns_common_extract(struct ldb_context *samdb,
+			  const struct ldb_message_element *el,
 			  TALLOC_CTX *mem_ctx,
 			  struct dnsp_DnssrvRpcRecord **records,
 			  uint16_t *num_records);
diff --git a/source4/dns_server/pydns.c b/source4/dns_server/pydns.c
index cb41faa1441..63fa80e92b3 100644
--- a/source4/dns_server/pydns.c
+++ b/source4/dns_server/pydns.c
@@ -160,17 +160,21 @@ static PyObject *py_dsdb_dns_lookup(PyObject *self,
 
 static PyObject *py_dsdb_dns_extract(PyObject *self, PyObject *args)
 {
+	struct ldb_context *samdb;
 	PyObject *py_dns_el, *ret;
+	PyObject *py_ldb = NULL;
 	TALLOC_CTX *frame;
 	WERROR werr;
 	struct ldb_message_element *dns_el;
 	struct dnsp_DnssrvRpcRecord *records;
 	uint16_t num_records;
 
-	if (!PyArg_ParseTuple(args, "O", &py_dns_el)) {
+	if (!PyArg_ParseTuple(args, "OO", &py_ldb, &py_dns_el)) {
 		return NULL;
 	}
 
+	PyErr_LDB_OR_RAISE(py_ldb, samdb);
+
 	if (!py_check_dcerpc_type(py_dns_el, "ldb", "MessageElement")) {
 		PyErr_SetString(PyExc_TypeError,
 				"ldb MessageElement object required");
@@ -180,7 +184,7 @@ static PyObject *py_dsdb_dns_extract(PyObject *self, PyObject *args)
 
 	frame = talloc_stackframe();
 
-	werr = dns_common_extract(dns_el,
+	werr = dns_common_extract(samdb, dns_el,
 				  frame,
 				  &records,
 				  &num_records);
-- 
2.11.0


From 47ba8f8f2158f1c87bdf47faeaeed82e3386e5a4 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 11 Apr 2017 14:14:15 +1200
Subject: [PATCH 12/13] samba_dnsupdate: Extend possible server list to all NS
 servers for the zone

This should eventually be removed, but for now this unblocks samba_dnsupdate operation
in existing domains that have lost the original Samba DC

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/scripting/bin/samba_dnsupdate | 98 ++++++++++++++++++++++++-----------
 1 file changed, 69 insertions(+), 29 deletions(-)

diff --git a/source4/scripting/bin/samba_dnsupdate b/source4/scripting/bin/samba_dnsupdate
index 28343bf17d5..eb6d4c2ad86 100755
--- a/source4/scripting/bin/samba_dnsupdate
+++ b/source4/scripting/bin/samba_dnsupdate
@@ -121,6 +121,64 @@ for i in IPs:
 if opts.verbose:
     print "IPs: %s" % IPs
 
+def get_possible_rw_dns_server(creds, domain):
+    """Get a list of possible read-write DNS servers, starting with
+       the SOA.  The SOA is the correct answer, but old Samba domains
+       (4.6 and prior) do not maintain this value, so add NS servers
+       as well"""
+
+    hostnames = []
+    ans_soa = check_one_dns_name(domain, 'SOA')
+
+    # Actually there is only one
+    for i in range(len(ans_soa)):
+        hostnames.append(str(ans_soa[i].mname).rstrip('.'))
+
+    # This is not strictly legit, but old Samba domains may have an
+    # unmaintained SOA record, so go for any NS that we can get a
+    # ticket to.
+    ans_ns = check_one_dns_name(domain, 'NS')
+
+    # Actually there is only one
+    for i in range(len(ans_ns)):
+        hostnames.append(str(ans_ns[i].target).rstrip('.'))
+
+    return hostnames
+
+def get_krb5_rw_dns_server(creds, domain):
+    """Get a list of read-write DNS servers that we can obtain a ticket
+       for, starting with the SOA.  The SOA is the correct answer, but
+       old Samba domains (4.6 and prior) do not maintain this value,
+       so continue with the NS servers as well until we get one that
+       the KDC will issue a ticket to.
+    """
+
+    rw_dns_servers = get_possible_rw_dns_server(creds, domain)
+    # Actually there is only one
+    for i in range(len(rw_dns_servers)):
+        target_hostname = str(rw_dns_servers[i])
+        settings = {}
+        settings["lp_ctx"] = lp
+        settings["target_hostname"] = target_hostname
+
+        gensec_client = gensec.Security.start_client(settings)
+        gensec_client.set_credentials(creds)
+        gensec_client.set_target_service("DNS")
+        gensec_client.set_target_hostname(target_hostname)
+        gensec_client.want_feature(gensec.FEATURE_SEAL)
+        gensec_client.start_mech_by_sasl_name("GSSAPI")
+        server_to_client = ""
+        try:
+            (client_finished, client_to_server) = gensec_client.update(server_to_client)
+            if opts.verbose:
+                print "Successfully obtained Kerberos ticket to DNS/%s as %s" \
+                    % (target_hostname, creds.get_username())
+            return target_hostname
+        except RuntimeError:
+            # Only raise an exception if they all failed
+            if i != len(rw_dns_servers) - 1:
+                pass
+            raise
 
 def get_credentials(lp):
     """# get credentials if we haven't got them already."""
@@ -138,33 +196,8 @@ def get_credentials(lp):
             return
 
         # Now confirm we can get a ticket to the DNS server
-        ans = check_one_dns_name(sub_vars['DNSDOMAIN'] + '.', 'SOA')
-
-        # Actually there is only one
-        for i in range(len(ans)):
-            target_hostname = str(ans[i].mname).rstrip('.')
-            settings = {}
-            settings["lp_ctx"] = lp
-            settings["target_hostname"] = target_hostname
-
-            gensec_client = gensec.Security.start_client(settings)
-            gensec_client.set_credentials(creds)
-            gensec_client.set_target_service("DNS")
-            gensec_client.set_target_hostname(target_hostname)
-            gensec_client.want_feature(gensec.FEATURE_SEAL)
-            gensec_client.start_mech_by_sasl_name("GSSAPI")
-            server_to_client = ""
-            try:
-                (client_finished, client_to_server) = gensec_client.update(server_to_client)
-                if opts.verbose:
-                    print "Successfully obtained Kerberos ticket to DNS/%s as %s" \
-                            % (target_hostname, creds.get_username())
-                return
-            except RuntimeError:
-                # Only raise an exception if they all failed
-                if i != len(ans) - 1:
-                    pass
-                raise
+        get_krb5_rw_dns_server(creds, sub_vars['DNSDOMAIN'] + '.')
+        return creds
 
     except RuntimeError as e:
         os.unlink(ccachename)
@@ -452,11 +485,18 @@ def call_nsupdate(d, op="add"):
         f.write('server %s\n' % d.nameservers[0])
     else:
         resolver = get_resolver(d)
+
+        # Local the zone for this name
         zone = dns.resolver.zone_for_name(normalised_name,
                                           resolver=resolver)
-        soa = resolver.query(zone, "SOA")
 
-        f.write('server %s\n' % soa[0].mname)
+        # Now find the SOA, or if we can't get a ticket to the SOA,
+        # any server with an NS record we can get a ticket for.
+        #
+        # Thanks to the Kerberos Crednetials cache this is not
+        # expensive inside the loop
+        server = get_krb5_rw_dns_server(creds, zone)
+        f.write('server %s\n' % server)
 
     if d.type == "A":
         f.write("update %s %s %u A %s\n" % (op, normalised_name, default_ttl, d.ip))
-- 
2.11.0


From 2ddee7207fbdc93bd0cf77fdce016541b9031ec7 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Tue, 11 Apr 2017 14:23:49 +1200
Subject: [PATCH 13/13] samba_dnsupdate: fix "samba-tool" fallback error
 handling

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/scripting/bin/samba_dnsupdate | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source4/scripting/bin/samba_dnsupdate b/source4/scripting/bin/samba_dnsupdate
index eb6d4c2ad86..d9948a6f9b8 100755
--- a/source4/scripting/bin/samba_dnsupdate
+++ b/source4/scripting/bin/samba_dnsupdate
@@ -626,7 +626,7 @@ def call_samba_tool(d, op="add", zone=None):
                 sys.exit(1)
             error_count = error_count + 1
             if opts.verbose:
-                print("Failed 'samba-tool dns' based update: %s" % (str(d)))
+                print("Failed 'samba-tool dns' based update of %s" % (str(d)))
     except Exception, estr:
         if opts.fail_immediately:
             sys.exit(1)
-- 
2.11.0



More information about the samba-technical mailing list