s4-provision: dns: Do not re-calculate ntdsguid, use from names
[samba.git] / source4 / scripting / python / samba / provision / sambadns.py
index 6e58f07e18db73b664fed423cc5a3e234bcb9c00..468c86344dc049c33c8e8e7010880f7b9107e502 100644 (file)
 
 import os
 import uuid
+import shutil
+import time
 import ldb
+from base64 import b64encode
 import samba
 from samba.ndr import ndr_pack, ndr_unpack
-from samba import read_and_sub_file
-from samba.dcerpc import dnsp, misc
+from samba import read_and_sub_file, setup_file
+from samba.dcerpc import dnsp, misc, security
 from samba.dsdb import (
     DS_DOMAIN_FUNCTION_2000,
     DS_DOMAIN_FUNCTION_2003,
     DS_DOMAIN_FUNCTION_2008,
     DS_DOMAIN_FUNCTION_2008_R2
     )
+from base64 import b64encode
+from samba.provision.descriptor import (
+    get_domain_descriptor,
+    get_dns_partition_descriptor
+    )
+from samba.provision.common import (
+    setup_path,
+    setup_add_ldif,
+    setup_modify_ldif,
+    setup_ldb
+    )
 
 
-def add_ldif(ldb, ldif_file, subst_vars, controls=["relax:0"]):
-    ldif_file_path = os.path.join(samba.param.setup_dir(), ldif_file)
-    data = read_and_sub_file(ldif_file_path, subst_vars)
-    ldb.add_ldif(data, controls)
-
-def modify_ldif(ldb, ldif_file, subst_vars, controls=["relax:0"]):
-    ldif_file_path = os.path.join(samba.param.setup_dir(), ldif_file)
-    data = read_and_sub_file(ldif_file_path, subst_vars)
-    ldb.modify_ldif(data, controls)
-
 def get_domainguid(samdb, domaindn):
     res = samdb.search(base=domaindn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
     domainguid =  str(ndr_unpack(misc.GUID, res[0]["objectGUID"][0]))
     return domainguid
 
-def get_ntdsguid(samdb, domaindn):
-    configdn = samdb.get_config_basedn()
-
-    res1 = samdb.search(base="OU=Domain Controllers,%s" % domaindn, scope=ldb.SCOPE_ONELEVEL,
-                        attrs=["dNSHostName"])
-
-    res2 = samdb.search(expression="serverReference=%s" % res1[0].dn, base=configdn)
-
-    res3 = samdb.search(base="CN=NTDS Settings,%s" % res2[0].dn, scope=ldb.SCOPE_BASE,
-                        attrs=["objectGUID"])
-    ntdsguid = str(ndr_unpack(misc.GUID, res3[0]["objectGUID"][0]))
-    return ntdsguid
-
+def get_dnsadmins_sid(samdb, domaindn):
+    res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % domaindn, scope=ldb.SCOPE_BASE,
+                       attrs=["objectSid"])
+    dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
+    return dnsadmins_sid
 
 class ARecord(dnsp.DnssrvRpcRecord):
     def __init__(self, ip_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
@@ -132,18 +128,70 @@ class SRVRecord(dnsp.DnssrvRpcRecord):
         srv.wWeight = weight
         self.data = srv
 
-
-def setup_dns_partitions(samdb, domaindn, forestdn, configdn, serverdn):
-
-    # FIXME: Default security descriptor for Domain-DNS objectCategory is different in
-    #        our documentation from windows
-
+class TypeProperty(dnsp.DnsProperty):
+    def __init__(self, zone_type=dnsp.DNS_ZONE_TYPE_PRIMARY):
+        super(TypeProperty, self).__init__()
+        self.wDataLength = 1
+        self.version = 1
+        self.id = dnsp.DSPROPERTY_ZONE_TYPE
+        self.data = zone_type
+
+class AllowUpdateProperty(dnsp.DnsProperty):
+    def __init__(self, allow_update=dnsp.DNS_ZONE_UPDATE_SECURE):
+        super(AllowUpdateProperty, self).__init__()
+        self.wDataLength = 1
+        self.version = 1
+        self.id = dnsp.DSPROPERTY_ZONE_ALLOW_UPDATE
+        self.data = allow_update
+
+class SecureTimeProperty(dnsp.DnsProperty):
+    def __init__(self, secure_time=0):
+        super(SecureTimeProperty, self).__init__()
+        self.wDataLength = 1
+        self.version = 1
+        self.id = dnsp.DSPROPERTY_ZONE_SECURE_TIME
+        self.data = secure_time
+
+class NorefreshIntervalProperty(dnsp.DnsProperty):
+    def __init__(self, norefresh_interval=0):
+        super(NorefreshIntervalProperty, self).__init__()
+        self.wDataLength = 1
+        self.version = 1
+        self.id = dnsp.DSPROPERTY_ZONE_NOREFRESH_INTERVAL
+        self.data = norefresh_interval
+
+class RefreshIntervalProperty(dnsp.DnsProperty):
+    def __init__(self, refresh_interval=0):
+        super(RefreshIntervalProperty, self).__init__()
+        self.wDataLength = 1
+        self.version = 1
+        self.id = dnsp.DSPROPERTY_ZONE_REFRESH_INTERVAL
+        self.data = refresh_interval
+
+class AgingStateProperty(dnsp.DnsProperty):
+    def __init__(self, aging_enabled=0):
+        super(AgingStateProperty, self).__init__()
+        self.wDataLength = 1
+        self.version = 1
+        self.id = dnsp.DSPROPERTY_ZONE_AGING_STATE
+        self.data = aging_enabled
+
+class AgingEnabledTimeProperty(dnsp.DnsProperty):
+    def __init__(self, next_cycle_hours=0):
+        super(AgingEnabledTimeProperty, self).__init__()
+        self.wDataLength = 1
+        self.version = 1;
+        self.id = dnsp.DSPROPERTY_ZONE_AGING_ENABLED_TIME
+        self.data = next_cycle_hours
+
+def setup_dns_partitions(samdb, domainsid, domaindn, forestdn, configdn, serverdn):
     domainzone_dn = "DC=DomainDnsZones,%s" % domaindn
     forestzone_dn = "DC=ForestDnsZones,%s" % forestdn
-
-    add_ldif(samdb, "provision_dnszones_partitions.ldif", {
+    descriptor = get_dns_partition_descriptor(domainsid)
+    setup_add_ldif(samdb, setup_path("provision_dnszones_partitions.ldif"), {
         "DOMAINZONE_DN": domainzone_dn,
         "FORESTZONE_DN": forestzone_dn,
+        "SECDESC"      : b64encode(descriptor)
         })
 
     domainzone_guid = get_domainguid(samdb, domainzone_dn)
@@ -155,7 +203,7 @@ def setup_dns_partitions(samdb, domaindn, forestdn, configdn, serverdn):
     domainzone_dns = ldb.Dn(samdb, domainzone_dn).canonical_ex_str().strip()
     forestzone_dns = ldb.Dn(samdb, forestzone_dn).canonical_ex_str().strip()
 
-    add_ldif(samdb, "provision_dnszones_add.ldif", {
+    setup_add_ldif(samdb, setup_path("provision_dnszones_add.ldif"), {
         "DOMAINZONE_DN": domainzone_dn,
         "FORESTZONE_DN": forestzone_dn,
         "DOMAINZONE_GUID": domainzone_guid,
@@ -166,7 +214,7 @@ def setup_dns_partitions(samdb, domaindn, forestdn, configdn, serverdn):
         "SERVERDN": serverdn,
         })
 
-    modify_ldif(samdb, "provision_dnszones_modify.ldif", {
+    setup_modify_ldif(samdb, setup_path("provision_dnszones_modify.ldif"), {
         "CONFIGDN": configdn,
         "SERVERDN": serverdn,
         "DOMAINZONE_DN": domainzone_dn,
@@ -175,18 +223,25 @@ def setup_dns_partitions(samdb, domaindn, forestdn, configdn, serverdn):
 
 
 def add_dns_accounts(samdb, domaindn):
-    add_ldif(samdb, "provision_dns_accounts_add.ldif", {
+    setup_add_ldif(samdb, setup_path("provision_dns_accounts_add.ldif"), {
         "DOMAINDN": domaindn,
         })
 
-def add_dns_container(samdb, domaindn, prefix):
+def add_dns_container(samdb, domaindn, prefix, domainsid, dnsadmins_sid):
     # CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+    sddl = "O:SYG:SYD:AI" \
+    "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)" \
+    "(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;%s)" \
+    "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+    "(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
+    "S:AI" % dnsadmins_sid
+    sec = security.descriptor.from_sddl(sddl, domainsid)
     msg = ldb.Message(ldb.Dn(samdb, "CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)))
     msg["objectClass"] = ["top", "container"]
-    msg["displayName"] = ldb.MessageElement("DNS Servers", ldb.FLAG_MOD_ADD, "displayName")
+    msg["nTSecurityDescriptor"] = ldb.MessageElement(ndr_pack(sec), ldb.FLAG_MOD_ADD,
+        "nTSecurityDescriptor")
     samdb.add(msg)
 
-
 def add_rootservers(samdb, domaindn, prefix):
     rootservers = {}
     rootservers["a.root-servers.net"] = "198.41.0.4"
@@ -215,7 +270,17 @@ def add_rootservers(samdb, domaindn, prefix):
 
     # Add DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
     msg = ldb.Message(ldb.Dn(samdb, container_dn))
+    props = []
+    props.append(ndr_pack(TypeProperty(zone_type=dnsp.DNS_ZONE_TYPE_CACHE)))
+    props.append(ndr_pack(AllowUpdateProperty(allow_update=dnsp.DNS_ZONE_UPDATE_OFF)))
+    props.append(ndr_pack(SecureTimeProperty()))
+    props.append(ndr_pack(NorefreshIntervalProperty()))
+    props.append(ndr_pack(RefreshIntervalProperty()))
+    props.append(ndr_pack(AgingStateProperty()))
+    props.append(ndr_pack(AgingEnabledTimeProperty()))
     msg["objectClass"] = ["top", "dnsZone"]
+    msg["cn"] = ldb.MessageElement("Zone", ldb.FLAG_MOD_ADD, "cn")
+    msg["dNSProperty"] = ldb.MessageElement(props, ldb.FLAG_MOD_ADD, "dNSProperty")
     samdb.add(msg)
 
     # Add DC=@,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
@@ -310,10 +375,35 @@ def add_host_record(samdb, container_dn, prefix, hostip, hostip6):
         msg["dnsRecord"] = ldb.MessageElement(host_records, ldb.FLAG_MOD_ADD, "dnsRecord")
         samdb.add(msg)
 
-def add_domain_record(samdb, domaindn, prefix, dnsdomain):
+def add_domain_record(samdb, domaindn, prefix, dnsdomain, domainsid, dnsadmins_sid):
     # DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+    sddl = "O:SYG:BAD:AI" \
+    "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)" \
+    "(A;;CC;;;AU)" \
+    "(A;;RPLCLORC;;;WD)" \
+    "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
+    "(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
+    "(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;%s)" \
+    "(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
+    "(OA;CIID;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
+    "(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
+    "(A;CIID;LC;;;RU)" \
+    "(A;CIID;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
+    "S:AI" % dnsadmins_sid
+    sec = security.descriptor.from_sddl(sddl, domainsid)
+    props = []
+    props.append(ndr_pack(TypeProperty()))
+    props.append(ndr_pack(AllowUpdateProperty()))
+    props.append(ndr_pack(SecureTimeProperty()))
+    props.append(ndr_pack(NorefreshIntervalProperty(norefresh_interval=168)))
+    props.append(ndr_pack(RefreshIntervalProperty(refresh_interval=168)))
+    props.append(ndr_pack(AgingStateProperty()))
+    props.append(ndr_pack(AgingEnabledTimeProperty()))
     msg = ldb.Message(ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" % (dnsdomain, prefix, domaindn)))
     msg["objectClass"] = ["top", "dnsZone"]
+    msg["ntSecurityDescriptor"] = ldb.MessageElement(ndr_pack(sec), ldb.FLAG_MOD_ADD,
+        "nTSecurityDescriptor")
+    msg["dNSProperty"] = ldb.MessageElement(props, ldb.FLAG_MOD_ADD, "dNSProperty")
     samdb.add(msg)
 
 def add_msdcs_record(samdb, forestdn, prefix, dnsforest):
@@ -450,25 +540,400 @@ def add_dc_msdcs_records(samdb, forestdn, prefix, site, dnsforest, hostname,
     add_cname_record(samdb, forest_container_dn, "DC=%s" % ntdsguid, fqdn_hostname)
 
 
+def secretsdb_setup_dns(secretsdb, names, private_dir, realm,
+                        dnsdomain, dns_keytab_path, dnspass):
+    """Add DNS specific bits to a secrets database.
+
+    :param secretsdb: Ldb Handle to the secrets database
+    :param names: Names shortcut
+    :param machinepass: Machine password
+    """
+    try:
+        os.unlink(os.path.join(private_dir, dns_keytab_path))
+    except OSError:
+        pass
+
+    setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
+            "REALM": realm,
+            "DNSDOMAIN": dnsdomain,
+            "DNS_KEYTAB": dns_keytab_path,
+            "DNSPASS_B64": b64encode(dnspass),
+            "HOSTNAME": names.hostname,
+            "DNSNAME" : '%s.%s' % (
+                names.netbiosname.lower(), names.dnsdomain.lower())
+            })
+
+
+def create_dns_dir(logger, paths):
+    """Write out a DNS zone file, from the info in the current database.
+
+    :param logger: Logger object
+    :param paths: paths object
+    """
+    dns_dir = os.path.dirname(paths.dns)
+
+    try:
+        shutil.rmtree(dns_dir, True)
+    except OSError:
+        pass
+
+    os.mkdir(dns_dir, 0770)
+
+    if paths.bind_gid is not None:
+        try:
+            os.chown(dns_dir, -1, paths.bind_gid)
+            # chmod needed to cope with umask
+            os.chmod(dns_dir, 0770)
+        except OSError:
+            if not os.environ.has_key('SAMBA_SELFTEST'):
+                logger.error("Failed to chown %s to bind gid %u" % (
+                    dns_dir, paths.bind_gid))
+
+
+def create_zone_file(lp, logger, paths, targetdir, dnsdomain,
+                     hostip, hostip6, hostname, realm, domainguid,
+                     ntdsguid, site):
+    """Write out a DNS zone file, from the info in the current database.
+
+    :param paths: paths object
+    :param dnsdomain: DNS Domain name
+    :param domaindn: DN of the Domain
+    :param hostip: Local IPv4 IP
+    :param hostip6: Local IPv6 IP
+    :param hostname: Local hostname
+    :param realm: Realm name
+    :param domainguid: GUID of the domain.
+    :param ntdsguid: GUID of the hosts nTDSDSA record.
+    """
+    assert isinstance(domainguid, str)
+
+    if hostip6 is not None:
+        hostip6_base_line = "            IN AAAA    " + hostip6
+        hostip6_host_line = hostname + "        IN AAAA    " + hostip6
+        gc_msdcs_ip6_line = "gc._msdcs               IN AAAA    " + hostip6
+    else:
+        hostip6_base_line = ""
+        hostip6_host_line = ""
+        gc_msdcs_ip6_line = ""
+
+    if hostip is not None:
+        hostip_base_line = "            IN A    " + hostip
+        hostip_host_line = hostname + "        IN A    " + hostip
+        gc_msdcs_ip_line = "gc._msdcs               IN A    " + hostip
+    else:
+        hostip_base_line = ""
+        hostip_host_line = ""
+        gc_msdcs_ip_line = ""
+
+    # we need to freeze the zone while we update the contents
+    if targetdir is None:
+        rndc = ' '.join(lp.get("rndc command"))
+        os.system(rndc + " freeze " + lp.get("realm"))
+
+    setup_file(setup_path("provision.zone"), paths.dns, {
+            "HOSTNAME": hostname,
+            "DNSDOMAIN": dnsdomain,
+            "REALM": realm,
+            "HOSTIP_BASE_LINE": hostip_base_line,
+            "HOSTIP_HOST_LINE": hostip_host_line,
+            "DOMAINGUID": domainguid,
+            "DATESTRING": time.strftime("%Y%m%d%H"),
+            "DEFAULTSITE": site,
+            "NTDSGUID": ntdsguid,
+            "HOSTIP6_BASE_LINE": hostip6_base_line,
+            "HOSTIP6_HOST_LINE": hostip6_host_line,
+            "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
+            "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
+        })
+
+    if paths.bind_gid is not None:
+        try:
+            os.chown(paths.dns, -1, paths.bind_gid)
+            # chmod needed to cope with umask
+            os.chmod(paths.dns, 0664)
+        except OSError:
+            if not os.environ.has_key('SAMBA_SELFTEST'):
+                logger.error("Failed to chown %s to bind gid %u" % (
+                    paths.dns, paths.bind_gid))
+
+    if targetdir is None:
+        os.system(rndc + " unfreeze " + lp.get("realm"))
+
+def tdb_copy(logger, file1, file2):
+    """Copy tdb file using tdbbackup utility and rename it
+    """
+    # Find the location of tdbbackup tool
+    dirs = ["bin", samba.param.bin_dir()] + os.getenv('PATH').split(os.pathsep)
+    for d in dirs:
+        toolpath = os.path.join(d, "tdbbackup")
+        if os.path.exists(toolpath):
+            break
+    status = os.system("%s -s '.dns' %s" % (toolpath, file1))
+    if status == 0:
+        os.rename("%s.dns" % file1, file2)
+    else:
+        raise Exception("Error copying %s" % file1)
+
+def create_samdb_copy(samdb, logger, paths, names, domainsid, domainguid):
+    """Create a copy of samdb and give write permissions to named for dns partitions
+    """
+    private_dir = paths.private_dir
+    samldb_dir = os.path.join(private_dir, "sam.ldb.d")
+    dns_dir = os.path.dirname(paths.dns)
+    dns_samldb_dir = os.path.join(dns_dir, "sam.ldb.d")
+
+    # Find the partitions and corresponding filenames
+    partfile = {}
+    res = samdb.search(base="@PARTITION", scope=ldb.SCOPE_BASE, attrs=["partition"])
+    for tmp in res[0]["partition"]:
+        (nc, fname) = tmp.split(':')
+        partfile[nc.upper()] = fname
+
+    # Create empty domain partition
+    domaindn = names.domaindn.upper()
+    domainpart_file = os.path.join(dns_dir, partfile[domaindn])
+    try:
+        os.mkdir(dns_samldb_dir)
+        file(domainpart_file, 'w').close()
+
+        # Fill the basedn and @OPTION records in domain partition
+        dom_ldb = samba.Ldb(domainpart_file)
+        domainguid_line = "objectGUID: %s\n-" % domainguid
+        descr = b64encode(get_domain_descriptor(domainsid))
+        setup_add_ldif(dom_ldb, setup_path("provision_basedn.ldif"), {
+            "DOMAINDN" : names.domaindn,
+            "DOMAINGUID" : domainguid_line,
+            "DOMAINSID" : str(domainsid),
+            "DESCRIPTOR" : descr})
+        setup_add_ldif(dom_ldb, setup_path("provision_basedn_options.ldif"), None)
+    except Exception:
+        logger.error("Failed to setup database for BIND, AD based DNS cannot be used")
+        raise
+    del partfile[domaindn]
+
+    # Link dns partitions and metadata
+    domainzonedn = "DC=DOMAINDNSZONES,%s" % names.domaindn.upper()
+    forestzonedn = "DC=FORESTDNSZONES,%s" % names.rootdn.upper()
+    domainzone_file = partfile[domainzonedn]
+    forestzone_file = partfile[forestzonedn]
+    metadata_file = "metadata.tdb"
+    try:
+        os.link(os.path.join(samldb_dir, metadata_file),
+            os.path.join(dns_samldb_dir, metadata_file))
+        os.link(os.path.join(private_dir, domainzone_file),
+            os.path.join(dns_dir, domainzone_file))
+        os.link(os.path.join(private_dir, forestzone_file),
+            os.path.join(dns_dir, forestzone_file))
+    except OSError:
+        logger.error("Failed to setup database for BIND, AD based DNS cannot be used")
+        raise
+    del partfile[domainzonedn]
+    del partfile[forestzonedn]
+
+    # Copy root, config, schema partitions (and any other if any)
+    # Since samdb is open in the current process, copy them in a child process
+    try:
+        tdb_copy(logger,
+                 os.path.join(private_dir, "sam.ldb"),
+                 os.path.join(dns_dir, "sam.ldb"))
+        for nc in partfile:
+            pfile = partfile[nc]
+            tdb_copy(logger,
+                     os.path.join(private_dir, pfile),
+                     os.path.join(dns_dir, pfile))
+    except Exception:
+        logger.error("Failed to setup database for BIND, AD based DNS cannot be used")
+        raise
+
+    # Give bind read/write permissions dns partitions
+    if paths.bind_gid is not None:
+        try:
+            os.chown(samldb_dir, -1, paths.bind_gid)
+            os.chmod(samldb_dir, 0750)
+
+            for dirname, dirs, files in os.walk(dns_dir):
+                for d in dirs:
+                    dpath = os.path.join(dirname, d)
+                    os.chown(dpath, -1, paths.bind_gid)
+                    os.chmod(dpath, 0770)
+                for f in files:
+                    if f.endswith('.ldb') or f.endswith('.tdb'):
+                        fpath = os.path.join(dirname, f)
+                        os.chown(fpath, -1, paths.bind_gid)
+                        os.chmod(fpath, 0660)
+        except OSError:
+            if not os.environ.has_key('SAMBA_SELFTEST'):
+                logger.error("Failed to set permissions to sam.ldb* files, fix manually")
+    else:
+        if not os.environ.has_key('SAMBA_SELFTEST'):
+            logger.warning("""Unable to find group id for BIND,
+                set permissions to sam.ldb* files manually""")
+
+
+def create_dns_update_list(lp, logger, paths):
+    """Write out a dns_update_list file"""
+    # note that we use no variable substitution on this file
+    # the substitution is done at runtime by samba_dnsupdate, samba_spnupdate
+    setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
+    setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
+
+
+def create_named_conf(paths, realm, dnsdomain, dns_backend):
+    """Write out a file containing zone statements suitable for inclusion in a
+    named.conf file (including GSS-TSIG configuration).
+
+    :param paths: all paths
+    :param realm: Realm name
+    :param dnsdomain: DNS Domain name
+    :param dns_backend: DNS backend type
+    :param keytab_name: File name of DNS keytab file
+    """
+
+    if dns_backend == "BIND9_FLATFILE":
+        setup_file(setup_path("named.conf"), paths.namedconf, {
+                    "DNSDOMAIN": dnsdomain,
+                    "REALM": realm,
+                    "ZONE_FILE": paths.dns,
+                    "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
+                    "NAMED_CONF": paths.namedconf,
+                    "NAMED_CONF_UPDATE": paths.namedconf_update
+                    })
+
+        setup_file(setup_path("named.conf.update"), paths.namedconf_update)
+
+    elif dns_backend == "BIND9_DLZ":
+        dlz_module_path = os.path.join(samba.param.modules_dir(),
+                                        "bind9/dlz_bind9.so")
+        setup_file(setup_path("named.conf.dlz"), paths.namedconf, {
+                    "NAMED_CONF": paths.namedconf,
+                    "BIND9_DLZ_MODULE": dlz_module_path,
+                    })
+
+
+def create_named_txt(path, realm, dnsdomain, dnsname, private_dir,
+    keytab_name):
+    """Write out a file containing zone statements suitable for inclusion in a
+    named.conf file (including GSS-TSIG configuration).
+
+    :param path: Path of the new named.conf file.
+    :param realm: Realm name
+    :param dnsdomain: DNS Domain name
+    :param private_dir: Path to private directory
+    :param keytab_name: File name of DNS keytab file
+    """
+    setup_file(setup_path("named.txt"), path, {
+            "DNSDOMAIN": dnsdomain,
+            "DNSNAME" : dnsname,
+            "REALM": realm,
+            "DNS_KEYTAB": keytab_name,
+            "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
+            "PRIVATE_DIR": private_dir
+        })
+
+
 def is_valid_dns_backend(dns_backend):
-        return dns_backend in ("BIND9_FLATFILE", "BIND9_DLZ", "SAMBA_INTERNAL", "NONE")
+    return dns_backend in ("BIND9_FLATFILE", "BIND9_DLZ", "SAMBA_INTERNAL", "NONE")
 
 
 def is_valid_os_level(os_level):
     return DS_DOMAIN_FUNCTION_2000 <= os_level <= DS_DOMAIN_FUNCTION_2008_R2
 
 
-def setup_ad_dns(samdb, names, logger, dns_backend, os_level, hostip=None,
-                 hostip6=None,):
+def create_dns_legacy(samdb, domainsid, forestdn, dnsadmins_sid):
+    # Set up MicrosoftDNS container
+    add_dns_container(samdb, forestdn, "CN=System", domainsid, dnsadmins_sid)
+    # Add root servers
+    add_rootservers(samdb, forestdn, "CN=System")
+
+
+def fill_dns_data_legacy(samdb, domainsid, forestdn, dnsdomain, site, hostname,
+                         hostip, hostip6):
+    # Add domain record
+    add_domain_record(samdb, forestdn, "CN=System", dnsdomain, domainsid,
+                      dnsadmins_sid)
+
+    # Add DNS records for a DC in domain
+    add_dc_domain_records(samdb, forestdn, "CN=System", site, dnsdomain,
+                          hostname, hostip, hostip6)
+
+
+def create_dns_partitions(samdb, domainsid, names, domaindn, forestdn,
+                          dnsadmins_sid):
+    # Set up additional partitions (DomainDnsZones, ForstDnsZones)
+    setup_dns_partitions(samdb, domainsid, domaindn, forestdn,
+                        names.configdn, names.serverdn)
+
+    # Set up MicrosoftDNS containers
+    add_dns_container(samdb, domaindn, "DC=DomainDnsZones", domainsid,
+                      dnsadmins_sid)
+    add_dns_container(samdb, forestdn, "DC=ForestDnsZones", domainsid,
+                      dnsadmins_sid)
+
+
+def fill_dns_data_partitions(samdb, domainsid, site, domaindn, forestdn,
+                            dnsdomain, dnsforest, hostname, hostip, hostip6,
+                            domainguid, ntdsguid, dnsadmins_sid, autofill=True):
+    """Fill data in various AD partitions
+
+    :param samdb: LDB object connected to sam.ldb file
+    :param domainsid: Domain SID (as dom_sid object)
+    :param site: Site name to create hostnames in
+    :param domaindn: DN of the domain
+    :param forestdn: DN of the forest
+    :param dnsdomain: DNS name of the domain
+    :param dnsforest: DNS name of the forest
+    :param hostname: Host name of this DC
+    :param hostip: IPv4 addresses
+    :param hostip6: IPv6 addresses
+    :param domainguid: Domain GUID
+    :param ntdsguid: NTDS GUID
+    :param dnsadmins_sid: SID for DnsAdmins group
+    :param autofill: Create DNS records (using fixed template)
+    """
+
+    ##### Set up DC=DomainDnsZones,<DOMAINDN>
+    # Add rootserver records
+    add_rootservers(samdb, domaindn, "DC=DomainDnsZones")
+
+    # Add domain record
+    add_domain_record(samdb, domaindn, "DC=DomainDnsZones", dnsdomain,
+                      domainsid, dnsadmins_sid)
+
+    # Add DNS records for a DC in domain
+    if autofill:
+        add_dc_domain_records(samdb, domaindn, "DC=DomainDnsZones", site,
+                              dnsdomain, hostname, hostip, hostip6)
+
+    ##### Set up DC=ForestDnsZones,<DOMAINDN>
+    # Add _msdcs record
+    add_msdcs_record(samdb, forestdn, "DC=ForestDnsZones", dnsforest)
+
+    # Add DNS records for a DC in forest
+    if autofill:
+        add_dc_msdcs_records(samdb, forestdn, "DC=ForestDnsZones", site,
+                             dnsforest, hostname, hostip, hostip6,
+                             domainguid, ntdsguid)
+
+
+def setup_ad_dns(samdb, secretsdb, domainsid, names, paths, lp, logger, dns_backend,
+                 os_level, site, dnspass=None, hostip=None, hostip6=None,
+                 targetdir=None):
     """Provision DNS information (assuming GC role)
 
     :param samdb: LDB object connected to sam.ldb file
+    :param secretsdb: LDB object connected to secrets.ldb file
+    :param domainsid: Domain SID (as dom_sid object)
     :param names: Names shortcut
+    :param paths: Paths shortcut
+    :param lp: Loadparm object
     :param logger: Logger object
     :param dns_backend: Type of DNS backend
     :param os_level: Functional level (treated as os level)
+    :param site: Site to create hostnames in
+    :param dnspass: Password for bind's DNS account
     :param hostip: IPv4 address
     :param hostip6: IPv6 address
+    :param targetdir: Target directory for creating DNS-related files for BIND9
     """
 
     if not is_valid_dns_backend(dns_backend):
@@ -481,22 +946,25 @@ def setup_ad_dns(samdb, names, logger, dns_backend, os_level, hostip=None,
         logger.info("No DNS backend set, not configuring DNS")
         return
 
+    # Add dns accounts (DnsAdmins, DnsUpdateProxy) in domain
+    logger.info("Adding DNS accounts")
+    add_dns_accounts(samdb, names.domaindn)
+
     # If dns_backend is BIND9_FLATFILE
-    #   Populate only CN=MicrosoftDNS,CN=System,<DOMAINDN>
+    #   Populate only CN=MicrosoftDNS,CN=System,<FORESTDN>
     #
     # If dns_backend is SAMBA_INTERNAL or BIND9_DLZ
     #   Populate DNS partitions
 
     # If os_level < 2003 (DS_DOMAIN_FUNCTION_2000)
-    #   All dns records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
+    #   All dns records are in CN=MicrosoftDNS,CN=System,<FORESTDN>
     #
     # If os_level >= 2003 (DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008,
     #                        DS_DOMAIN_FUNCTION_2008_R2)
-    #   Root server records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
-    #   Domain records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
+    #   Root server records are in CN=MicrosoftDNS,CN=System,<FORESTDN>
+    #   Domain records are in CN=MicrosoftDNS,CN=System,<FORESTDN>
     #   Domain records are in CN=MicrosoftDNS,DC=DomainDnsZones,<DOMAINDN>
-    #   Forest records are in CN=MicrosoftDNS,DC=ForestDnsZones,<DOMAINDN>
-
+    #   Forest records are in CN=MicrosoftDNS,DC=ForestDnsZones,<FORESTDN>
     domaindn = names.domaindn
     forestdn = samdb.get_root_basedn().get_linearized()
 
@@ -504,64 +972,59 @@ def setup_ad_dns(samdb, names, logger, dns_backend, os_level, hostip=None,
     dnsforest = dnsdomain
 
     hostname = names.netbiosname.lower()
-    site = names.sitename
 
+    dnsadmins_sid = get_dnsadmins_sid(samdb, domaindn)
     domainguid = get_domainguid(samdb, domaindn)
-    ntdsguid = get_ntdsguid(samdb, domaindn)
-
-    # Add dns accounts (DnsAdmins, DnsUpdateProxy) in domain
-    logger.info("Adding DNS accounts")
-    add_dns_accounts(samdb, domaindn)
-
-    logger.info("Populating CN=MicrosoftDNS,CN=System,%s" % domaindn)
 
-    # Set up MicrosoftDNS container
-    add_dns_container(samdb, domaindn, "CN=System")
-
-    # Add root servers
-    add_rootservers(samdb, domaindn, "CN=System")
+    # Create CN=System
+    logger.info("Creating CN=MicrosoftDNS,CN=System,%s" % forestdn)
+    create_dns_legacy(samdb, domainsid, forestdn, dnsadmins_sid)
 
     if os_level == DS_DOMAIN_FUNCTION_2000:
-
-        # Add domain record
-        add_domain_record(samdb, domaindn, "CN=System", dnsdomain)
-
-        # Add DNS records for a DC in domain
-        add_dc_domain_records(samdb, domaindn, "CN=System", site, dnsdomain,
-                                hostname, hostip, hostip6)
+        # Populating legacy dns
+        logger.info("Populating CN=MicrosoftDNS,CN=System,%s" % forestdn)
+        fill_dns_data_legacy(samdb, domainsid, forestdn, dnsdoman, site,
+                             hostame, hostip, hostip6)
 
     elif dns_backend in ("SAMBA_INTERNAL", "BIND9_DLZ") and \
             os_level >= DS_DOMAIN_FUNCTION_2003:
 
-        # Set up additional partitions (DomainDnsZones, ForstDnsZones)
+        # Create DNS partitions
         logger.info("Creating DomainDnsZones and ForestDnsZones partitions")
-        setup_dns_partitions(samdb, domaindn, forestdn, names.configdn, names.serverdn)
-
-        ##### Set up DC=DomainDnsZones,<DOMAINDN>
-        logger.info("Populating DomainDnsZones partition")
-
-        # Set up MicrosoftDNS container
-        add_dns_container(samdb, domaindn, "DC=DomainDnsZones")
-
-        # Add rootserver records
-        add_rootservers(samdb, domaindn, "DC=DomainDnsZones")
-
-        # Add domain record
-        add_domain_record(samdb, domaindn, "DC=DomainDnsZones", dnsdomain)
-
-        # Add DNS records for a DC in domain
-        add_dc_domain_records(samdb, domaindn, "DC=DomainDnsZones", site, dnsdomain,
-                                hostname, hostip, hostip6)
-
-        ##### Set up DC=ForestDnsZones,<DOMAINDN>
-        logger.info("Populating ForestDnsZones partition")
-
-        # Set up MicrosoftDNS container
-        add_dns_container(samdb, forestdn, "DC=ForestDnsZones")
-
-        # Add _msdcs record
-        add_msdcs_record(samdb, forestdn, "DC=ForestDnsZones", dnsforest)
-
-        # Add DNS records for a DC in forest
-        add_dc_msdcs_records(samdb, forestdn, "DC=ForestDnsZones", site, dnsforest,
-                                hostname, hostip, hostip6, domainguid, ntdsguid)
+        create_dns_partitions(samdb, domainsid, names, domaindn, forestdn,
+                              dnsadmins_sid)
+
+        # Populating dns partitions
+        logger.info("Populating DomainDnsZones and ForestDnsZones partitions")
+        fill_dns_data_partitions(samdb, domainsid, site, domaindn, forestdn,
+                                dnsdomain, dnsforest, hostname, hostip, hostip6,
+                                domainguid, names.ntdsguid, dnsadmins_sid)
+
+    if dns_backend.startswith("BIND9_"):
+        secretsdb_setup_dns(secretsdb, names,
+                            paths.private_dir, realm=names.realm,
+                            dnsdomain=names.dnsdomain,
+                            dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
+
+        create_dns_dir(logger, paths)
+
+        if dns_backend == "BIND9_FLATFILE":
+            create_zone_file(lp, logger, paths, targetdir, site=site,
+                             dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
+                             hostname=names.hostname, realm=names.realm,
+                             domainguid=domainguid, ntdsguid=names.ntdsguid)
+
+        if dns_backend == "BIND9_DLZ" and os_level >= DS_DOMAIN_FUNCTION_2003:
+            create_samdb_copy(samdb, logger, paths, names, domainsid, domainguid)
+
+        create_named_conf(paths, realm=names.realm,
+                          dnsdomain=names.dnsdomain, dns_backend=dns_backend)
+
+        create_named_txt(paths.namedtxt,
+                         realm=names.realm, dnsdomain=names.dnsdomain,
+                         dnsname = "%s.%s" % (names.hostname, names.dnsdomain),
+                         private_dir=paths.private_dir,
+                         keytab_name=paths.dns_keytab)
+        logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
+        logger.info("and %s for further documentation required for secure DNS "
+                    "updates", paths.namedtxt)