s4-provision: Reworked DNS provisioning to support AD DNS schema
authorAmitay Isaacs <amitay@gmail.com>
Mon, 5 Sep 2011 06:19:02 +0000 (16:19 +1000)
committerAndrew Bartlett <abartlet@samba.org>
Wed, 7 Sep 2011 22:35:37 +0000 (00:35 +0200)
This changes configure DNS partitions used by AD DNS and populate
with relevant entries. This has an advantage that Windows can
replicate these partitions and set up dns server using them.

In addition, these partitions are used by bind9_dlz module to query
zone information directly and do not need to create text database
for dynamic zones.

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
source4/scripting/python/samba/provision/sambadns.py

index 6b8561e1b53ecb25ee1ad848fdc916c4c53a045c..8e7661b6621b6a83bc69a856044e2e876513d57b 100644 (file)
@@ -2,6 +2,7 @@
 # backend code for provisioning DNS for a Samba4 server
 #
 # Copyright (C) Kai Blin <kai@samba.org> 2011
+# Copyright (C) Amitay Isaacs <amitay@gmail.com> 2011
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
 """DNS-related provisioning"""
 
 import os
+import uuid
 import ldb
 import samba
-from samba.ndr import ndr_pack
+from samba.ndr import ndr_pack, ndr_unpack
 from samba import read_and_sub_file
-from samba.dcerpc import dnsp
+from samba.dcerpc import dnsp, misc
+from samba.dsdb import (
+    DS_DOMAIN_FUNCTION_2000,
+    DS_DOMAIN_FUNCTION_2003,
+    DS_DOMAIN_FUNCTION_2008,
+    DS_DOMAIN_FUNCTION_2008_R2
+    )
+
+
+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 = "CN=Configuration,%s" % domaindn
+
+    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
+
 
 class ARecord(dnsp.DnssrvRpcRecord):
     def __init__(self, ip_addr, serial=1, ttl=3600):
@@ -42,6 +80,14 @@ class AAAARecord(dnsp.DnssrvRpcRecord):
         self.dwTtlSeconds = ttl
         self.data = ip6_addr
 
+class CNameRecord(dnsp.DnssrvRpcRecord):
+    def __init__(self, cname, serial=1, ttl=900):
+        super(CNameRecord, self).__init__()
+        self.wType = dnsp.DNS_TYPE_CNAME
+        self.dwSerial = serial
+        self.dwTtlSeconds = ttl
+        self.data = cname
+
 class NSRecord(dnsp.DnssrvRpcRecord):
     def __init__(self, dns_server, serial=1, ttl=3600):
         super(NSRecord, self).__init__()
@@ -50,6 +96,15 @@ class NSRecord(dnsp.DnssrvRpcRecord):
         self.dwTtlSeconds = ttl
         self.data = dns_server
 
+class RootNSRecord(dnsp.DnssrvRpcRecord):
+    def __init__(self, dns_server, serial=1, ttl=3600):
+        super(RootNSRecord, self).__init__()
+        self.wType = dnsp.DNS_TYPE_NS
+        self.dwSerial = serial
+        self.dwTtlSeconds = ttl
+        self.data = dns_server
+        self.rank = dnsp.DNS_RANK_ROOT_HINT
+
 class SOARecord(dnsp.DnssrvRpcRecord):
     def __init__(self, mname, rname, serial=1, refresh=900, retry=600,
                  expire=86400, minimum=3600, ttl=3600):
@@ -79,153 +134,419 @@ class SRVRecord(dnsp.DnssrvRpcRecord):
         srv.wWeight = weight
         self.data = srv
 
-def setup_ad_dns(samdb, names, hostip=None, hostip6=None):
-    domaindn = names.domaindn
-    dnsdomain = names.dnsdomain.lower()
-    hostname = names.netbiosname.lower()
-    dnsname = "%s.%s" % (hostname, dnsdomain)
-    site = names.sitename
 
-    dns_ldif = os.path.join(samba.param.setup_dir(), "provision_dns_add.ldif")
+def setup_dns_partitions(samdb, domaindn, forestdn, configdn, serverdn):
+
+    # FIXME: Default security descriptor for Domain-DNS objectCategory is different in
+    #        our documentation from windows
+
+    domainzone_dn = "DC=DomainDnsZones,%s" % domaindn
+    forestzone_dn = "DC=ForestDnsZones,%s" % forestdn
+
+    add_ldif(samdb, "provision_dnszones_partitions.ldif", {
+        "DOMAINZONE_DN": domainzone_dn,
+        "FORESTZONE_DN": forestzone_dn,
+        })
 
-    dns_data = read_and_sub_file(dns_ldif, {
-              "DOMAINDN": domaindn,
-              "DNSDOMAIN" : dnsdomain
-              })
-    samdb.add_ldif(dns_data, ["relax:0"])
+    domainzone_guid = get_domainguid(samdb, domainzone_dn)
+    forestzone_guid = get_domainguid(samdb, forestzone_dn)
+
+    domainzone_guid = str(uuid.uuid4())
+    forestzone_guid = str(uuid.uuid4())
+
+    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", {
+        "DOMAINZONE_DN": domainzone_dn,
+        "FORESTZONE_DN": forestzone_dn,
+        "DOMAINZONE_GUID": domainzone_guid,
+        "FORESTZONE_GUID": forestzone_guid,
+        "DOMAINZONE_DNS": domainzone_dns,
+        "FORESTZONE_DNS": forestzone_dns,
+        "CONFIGDN": configdn,
+        })
+
+    modify_ldif(samdb, "provision_dnszones_modify.ldif", {
+        "CONFIGDN": configdn,
+        "SERVERDN": serverdn,
+        "DOMAINZONE_DN": domainzone_dn,
+        "FORESTZONE_DN": forestzone_dn,
+    })
+
+
+def add_dns_accounts(samdb, domaindn):
+    add_ldif(samdb, "provision_dns_accounts_add.ldif", {
+        "DOMAINDN": domaindn,
+        })
+
+def add_dns_container(samdb, domaindn, prefix):
+    # CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+    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")
+    samdb.add(msg)
 
-    soa_subrecords = []
-    dns_records = []
 
-    # @ entry for the domain
-    at_soa_record = SOARecord(dnsname, "hostmaster.%s" % dnsdomain)
-    soa_subrecords.append(ndr_pack(at_soa_record))
+def add_rootservers(samdb, domaindn, prefix):
+    rootservers = {}
+    rootservers["a.root-servers.net"] = "198.41.0.4"
+    rootservers["b.root-servers.net"] = "192.228.79.201"
+    rootservers["c.root-servers.net"] = "192.33.4.12"
+    rootservers["d.root-servers.net"] = "128.8.10.90"
+    rootservers["e.root-servers.net"] = "192.203.230.10"
+    rootservers["f.root-servers.net"] = "192.5.5.241"
+    rootservers["g.root-servers.net"] = "192.112.36.4"
+    rootservers["h.root-servers.net"] = "128.63.2.53"
+    rootservers["i.root-servers.net"] = "192.36.148.17"
+    rootservers["j.root-servers.net"] = "192.58.128.30"
+    rootservers["k.root-servers.net"] = "193.0.14.129"
+    rootservers["l.root-servers.net"] = "199.7.83.42"
+    rootservers["m.root-servers.net"] = "202.12.27.33"
 
-    at_ns_record = NSRecord(dnsname)
-    soa_subrecords.append(ndr_pack(at_ns_record))
+    rootservers_v6 = {}
+    rootservers_v6["a.root-servers.net"] = "2001:503:ba3e::2:30"
+    rootservers_v6["f.root-servers.net"] = "2001:500:2f::f"
+    rootservers_v6["h.root-servers.net"] = "2001:500:1::803f:235"
+    rootservers_v6["j.root-servers.net"] = "2001:503:c27::2:30"
+    rootservers_v6["k.root-servers.net"] = "2001:7fd::1"
+    rootservers_v6["m.root-servers.net"] = "2001:dc3::35"
+
+    container_dn = "DC=RootDNSServers,CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)
+
+    # Add DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+    msg = ldb.Message(ldb.Dn(samdb, container_dn))
+    msg["objectClass"] = ["top", "dnsZone"]
+    samdb.add(msg)
+
+    # Add DC=@,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+    record = []
+    for rserver in rootservers:
+        record.append(ndr_pack(RootNSRecord(rserver, serial=0, ttl=0)))
+
+    msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
+    msg["objectClass"] = ["top", "dnsNode"]
+    msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
+    samdb.add(msg)
+
+    # Add DC=<rootserver>,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+    for rserver in rootservers:
+        record = [ndr_pack(ARecord(rootservers[rserver], serial=0, ttl=0))]
+        # Add AAAA record as well (How does W2K* add IPv6 records?)
+        #if rserver in rootservers_v6:
+        #    record.append(ndr_pack(AAAARecord(rootservers_v6[rserver], serial=0, ttl=0)))
+        msg = ldb.Message(ldb.Dn(samdb, "DC=%s,%s" % (rserver, container_dn)))
+        msg["objectClass"] = ["top", "dnsNode"]
+        msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
+        samdb.add(msg)
+
+def add_at_record(samdb, container_dn, prefix, hostname, dnsdomain, hostip, hostip6):
+
+    fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
+
+    at_records = []
+
+    # SOA record
+    at_soa_record = SOARecord(fqdn_hostname, "hostmaster.%s" % dnsdomain)
+    at_records.append(ndr_pack(at_soa_record))
+
+    # NS record
+    at_ns_record = NSRecord(fqdn_hostname)
+    at_records.append(ndr_pack(at_ns_record))
 
     if hostip is not None:
         # A record
         at_a_record = ARecord(hostip)
-        dns_records.append(ndr_pack(at_a_record))
+        at_records.append(ndr_pack(at_a_record))
 
     if hostip6 is not None:
+        # AAAA record
         at_aaaa_record = AAAARecord(hostip6)
-        dns_records.append(ndr_pack(at_aaaa_record))
+        at_records.append(ndr_pack(at_aaaa_record))
 
-    msg = ldb.Message(ldb.Dn(samdb, "DC=@,DC=%s,CN=MicrosoftDNS,CN=System,%s" %\
-                                    (dnsdomain, domaindn )))
+    msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
     msg["objectClass"] = ["top", "dnsNode"]
-    msg["dnsRecord"] = ldb.MessageElement(soa_subrecords + dns_records,
-                                          ldb.FLAG_MOD_ADD, "dnsRecord")
+    msg["dnsRecord"] = ldb.MessageElement(at_records, ldb.FLAG_MOD_ADD, "dnsRecord")
     samdb.add(msg)
 
-    # _gc._tcp record
-    gc_tcp_record = SRVRecord(dnsname, 3268)
-    msg = ldb.Message(ldb.Dn(samdb,
-            "DC=_gc._tcp,DC=%s,CN=MicrosoftDNS,CN=System,%s" % \
-                    (dnsdomain, domaindn)))
+def add_srv_record(samdb, container_dn, prefix, host, port):
+    srv_record = SRVRecord(host, port)
+    msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
     msg["objectClass"] = ["top", "dnsNode"]
-    msg["dnsRecord"] = [ndr_pack(gc_tcp_record)]
+    msg["dnsRecord"] = ldb.MessageElement(ndr_pack(srv_record), ldb.FLAG_MOD_ADD, "dnsRecord")
     samdb.add(msg)
 
-    # _gc._tcp.sitename._site record
-    msg = ldb.Message(ldb.Dn(samdb,
-            "DC=_gc._tcp.%s._sites,DC=%s,CN=MicrosoftDNS,CN=System,%s" % \
-                    (names.sitename, dnsdomain, domaindn)))
+def add_ns_record(samdb, container_dn, prefix, host):
+    ns_record = NSRecord(host)
+    msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
     msg["objectClass"] = ["top", "dnsNode"]
-    msg["dnsRecord"] = [ndr_pack(gc_tcp_record)]
+    msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
     samdb.add(msg)
 
-    # _kerberos._tcp record
-    kerberos_record = SRVRecord(dnsname, 88)
-    msg = ldb.Message(ldb.Dn(samdb,
-            "DC=_kerberos._tcp,DC=%s,CN=MicrosoftDNS,CN=System,%s" % \
-                    (dnsdomain, domaindn)))
+def add_cname_record(samdb, container_dn, prefix, host):
+    cname_record = CNameRecord(host)
+    msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
     msg["objectClass"] = ["top", "dnsNode"]
-    msg["dnsRecord"] = [ndr_pack(kerberos_record)]
+    msg["dnsRecord"] = ldb.MessageElement(ndr_pack(cname_record), ldb.FLAG_MOD_ADD, "dnsRecord")
     samdb.add(msg)
 
-    # _kerberos._tcp.sitename._site record
-    msg = ldb.Message(ldb.Dn(samdb,
-            "DC=_kerberos._tcp.%s._sites,DC=%s,CN=MicrosoftDNS,CN=System,%s" % \
-                    (site, dnsdomain, domaindn)))
-    msg["objectClass"] = ["top", "dnsNode"]
-    msg["dnsRecord"] = [ndr_pack(kerberos_record)]
-    samdb.add(msg)
+def add_host_record(samdb, container_dn, prefix, hostip, hostip6):
+    host_records = []
+    if hostip:
+        a_record = ARecord(hostip)
+        host_records.append(ndr_pack(a_record))
+    if hostip6:
+        aaaa_record = AAAARecord(hostip6)
+        host_records.append(ndr_pack(aaaa_record))
+    if host_records:
+        msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
+        msg["objectClass"] = ["top", "dnsNode"]
+        msg["dnsRecord"] = ldb.MessageElement(host_records, ldb.FLAG_MOD_ADD, "dnsRecord")
+        samdb.add(msg)
 
-    # _kerberos._udp record
-    msg = ldb.Message(ldb.Dn(samdb,
-            "DC=_kerberos._udp,DC=%s,CN=MicrosoftDNS,CN=System,%s" % \
-                    (dnsdomain, domaindn)))
-    msg["objectClass"] = ["top", "dnsNode"]
-    msg["dnsRecord"] = [ndr_pack(kerberos_record)]
+def add_domain_record(samdb, domaindn, prefix, dnsdomain):
+    # DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+    msg = ldb.Message(ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" % (dnsdomain, prefix, domaindn)))
+    msg["objectClass"] = ["top", "dnsZone"]
     samdb.add(msg)
 
-    # _kpasswd._tcp record
-    kpasswd_record = SRVRecord(dnsname, 464)
-    msg = ldb.Message(ldb.Dn(samdb,
-            "DC=_kpasswd._tcp,DC=%s,CN=MicrosoftDNS,CN=System,%s" % \
-                    (dnsdomain, domaindn)))
-    msg["objectClass"] = ["top", "dnsNode"]
-    msg["dnsRecord"] = [ndr_pack(kpasswd_record)]
+def add_msdcs_record(samdb, forestdn, prefix, dnsforest):
+    # DC=_msdcs.<DNSFOREST>,CN=MicrosoftDNS,<PREFIX>,<FORESTDN>
+    msg = ldb.Message(ldb.Dn(samdb, "DC=_msdcs.%s,CN=MicrosoftDNS,%s,%s" %
+                                    (dnsforest, prefix, forestdn)))
+    msg["objectClass"] = ["top", "dnsZone"]
     samdb.add(msg)
 
-    # _kpasswd._udp record
-    msg = ldb.Message(ldb.Dn(samdb,
-            "DC=_kpasswd._udp,DC=%s,CN=MicrosoftDNS,CN=System,%s" % \
-                    (dnsdomain, domaindn)))
-    msg["objectClass"] = ["top", "dnsNode"]
-    msg["dnsRecord"] = [ndr_pack(kpasswd_record)]
-    samdb.add(msg)
 
-    # _ldap._tcp record
-    ldap_record = SRVRecord(dnsname, 389)
-    msg = ldb.Message(ldb.Dn(samdb,
-            "DC=_ldap._tcp,DC=%s,CN=MicrosoftDNS,CN=System,%s" % \
-                    (dnsdomain, domaindn)))
-    msg["objectClass"] = ["top", "dnsNode"]
-    msg["dnsRecord"] = [ndr_pack(ldap_record)]
-    samdb.add(msg)
+def add_dc_domain_records(samdb, domaindn, prefix, site, dnsdomain, hostname, hostip, hostip6):
 
-    # _ldap._tcp.sitename._site record
-    msg = ldb.Message(ldb.Dn(samdb,
-            "DC=_ldap._tcp.%s._site,DC=%s,CN=MicrosoftDNS,CN=System,%s" % \
-                    (site, dnsdomain, domaindn)))
-    msg["objectClass"] = ["top", "dnsNode"]
-    msg["dnsRecord"] = [ndr_pack(ldap_record)]
-    samdb.add(msg)
+    fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
 
-    # _msdcs record
-    msdcs_record = NSRecord(dnsname)
-    msg = ldb.Message(ldb.Dn(samdb,
-            "DC=_msdcs,DC=%s,CN=MicrosoftDNS,CN=System,%s" % \
-                    (dnsdomain, domaindn)))
-    msg["objectClass"] = ["top", "dnsNode"]
-    msg["dnsRecord"] = [ndr_pack(msdcs_record)]
-    samdb.add(msg)
+    # Set up domain container - DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+    domain_container_dn = ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" %
+                                    (dnsdomain, prefix, domaindn))
 
-    # the host's own record
-    # Only do this if there's IP addresses to set up.
-    # This is a bit weird, but the samba4.blackbox.provision.py test apparently
-    # doesn't set up any IPs
-    if len(dns_records) > 0:
-        msg = ldb.Message(ldb.Dn(samdb,
-                "DC=%s,DC=%s,CN=MicrosoftDNS,CN=System,%s" % \
-                        (hostname, dnsdomain, domaindn)))
-        msg["objectClass"] = ["top", "dnsNode"]
-        msg["dnsRecord"] = ldb.MessageElement(dns_records,
-                                              ldb.FLAG_MOD_ADD, "dnsRecord")
-        samdb.add(msg)
+    # DC=@ record
+    add_at_record(samdb, domain_container_dn, "DC=@", hostname, dnsdomain, hostip, hostip6)
 
-        # DomainDnsZones record
-        msg = ldb.Message(ldb.Dn(samdb,
-                "DC=DomainDnsZones,DC=%s,CN=MicrosoftDNS,CN=System,%s" % \
-                        (dnsdomain, domaindn)))
-        msg["objectClass"] = ["top", "dnsNode"]
-        msg["dnsRecord"] = ldb.MessageElement(dns_records,
-                                              ldb.FLAG_MOD_ADD, "dnsRecord")
+    # DC=<HOSTNAME> record
+    add_host_record(samdb, domain_container_dn, "DC=%s" % hostname, hostip, hostip6)
 
-        samdb.add(msg)
+    # DC=_kerberos._tcp record
+    add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp", fqdn_hostname, 88)
+
+    # DC=_kerberos._tcp.<SITENAME>._sites record
+    add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp.%s._sites" % site,
+                    fqdn_hostname, 88)
+
+    # DC=_kerberos._udp record
+    add_srv_record(samdb, domain_container_dn, "DC=_kerberos._udp", fqdn_hostname, 88)
+
+    # DC=_kpasswd._tcp record
+    add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._tcp", fqdn_hostname, 464)
+
+    # DC=_kpasswd._udp record
+    add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._udp", fqdn_hostname, 464)
+
+    # DC=_ldap._tcp record
+    add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp", fqdn_hostname, 389)
+
+    # DC=_ldap._tcp.<SITENAME>._sites record
+    add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites" % site,
+                    fqdn_hostname, 389)
+
+    # FIXME: The number of SRV records depend on the various roles this DC has.
+    #        _gc and _msdcs records are added if the we are the forest dc and not subdomain dc
+    #
+    # Assumption: current DC is GC and add all the entries
+
+    # DC=_gc._tcp record
+    add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp", fqdn_hostname, 3268)
+
+    # DC=_gc._tcp.<SITENAME>,_sites record
+    add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp.%s._sites" % site, fqdn_hostname, 3268)
+
+    # DC=_msdcs record
+    add_ns_record(samdb, domain_container_dn, "DC=_msdcs", fqdn_hostname)
+
+    # FIXME: Following entries are added only if DomainDnsZones and ForestDnsZones partitions
+    #        are created
+    #
+    # Assumption: Additional entries won't hurt on os_level = 2000
+
+    # DC=_ldap._tcp.<SITENAME>._sites.DomainDnsZones
+    add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites.DomainDnsZones" % site,
+                    fqdn_hostname, 389)
+
+    # DC=_ldap._tcp.<SITENAME>._sites.ForestDnsZones
+    add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites.ForestDnsZones" % site,
+                    fqdn_hostname, 389)
+
+    # DC=_ldap._tcp.DomainDnsZones
+    add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.DomainDnsZones",
+                    fqdn_hostname, 389)
+
+    # DC=_ldap._tcp.ForestDnsZones
+    add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.ForestDnsZones",
+                    fqdn_hostname, 389)
+
+    # DC=DomainDnsZones
+    add_host_record(samdb, domain_container_dn, "DC=DomainDnsZones", hostip, hostip6)
+
+    # DC=ForestDnsZones
+    add_host_record(samdb, domain_container_dn, "DC=ForestDnsZones", hostip, hostip6)
+
+
+def add_dc_msdcs_records(samdb, forestdn, prefix, site, dnsforest, hostname,
+                            hostip, hostip6, domainguid, ntdsguid):
+
+    fqdn_hostname = "%s.%s" % (hostname, dnsforest)
+
+    # Set up forest container - DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
+    forest_container_dn = ldb.Dn(samdb, "DC=_msdcs.%s,CN=MicrosoftDNS,%s,%s" %
+                                    (dnsforest, prefix, forestdn))
+
+    # DC=@ record
+    add_at_record(samdb, forest_container_dn, "DC=@", hostname, dnsforest, None, None)
+
+    # DC=_kerberos._tcp.dc record
+    add_srv_record(samdb, forest_container_dn, "DC=_kerberos._tcp.dc", fqdn_hostname, 88)
+
+    # DC=_kerberos._tcp.<SITENAME>._sites.dc record
+    add_srv_record(samdb, forest_container_dn, "DC=_kerberos._tcp.%s._sites.dc" % site,
+                    fqdn_hostname, 88)
+
+    # DC=_ldap._tcp.dc record
+    add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.dc", fqdn_hostname, 389)
+
+    # DC=_ldap._tcp.<SITENAME>._sites.dc record
+    add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.dc" % site,
+                    fqdn_hostname, 389)
+
+    # DC=_ldap._tcp.<SITENAME>._sites.gc record
+    add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.gc" % site,
+                    fqdn_hostname, 3268)
+
+    # DC=_ldap._tcp.gc record
+    add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.gc", fqdn_hostname, 3268)
+
+    # DC=_ldap._tcp.pdc record
+    add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.pdc", fqdn_hostname, 389)
+
+    # DC=gc record
+    add_host_record(samdb, forest_container_dn, "DC=gc", hostip, hostip6)
+
+    # DC=_ldap._tcp.<DOMAINGUID>.domains record
+    add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s.domains" % domainguid,
+                    fqdn_hostname, 389)
+
+    # DC=<NTDSGUID>
+    add_cname_record(samdb, forest_container_dn, "DC=%s" % ntdsguid, fqdn_hostname)
+
+
+def setup_ad_dns(samdb, names, logger, hostip=None, hostip6=None, dns_backend=None,
+                os_level=None):
+    """Provision DNS information (assuming GC role)
+
+    :param samdb: LDB object connected to sam.ldb file
+    :param names: Names shortcut
+    :param logger: Logger object
+    :param hostip: IPv4 address
+    :param hostip6: IPv6 address
+    :param dns_backend: Type of DNS backend
+    :param os_level: Functional level (treated as os level)
+    """
+
+    if dns_backend is None:
+        dns_backend = "BIND9"
+        logger.info("Assuming bind9 DNS server backend")
+
+    # If dns_backend is BIND9
+    #   Populate only CN=MicrosoftDNS,CN=System,<DOMAINDN>
+    #
+    # If dns_backend is SAMBA or BIND9_DLZ 
+    #   Populate DNS partitions
+
+    if os_level is None:
+        os_level = DS_DOMAIN_FUNCTION_2003
+
+    # If os_level < 2003 (DS_DOMAIN_FUNCTION_2000)
+    #   All dns records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
+    #
+    # 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>
+    #   Domain records are in CN=MicrosoftDNS,DC=DomainDnsZones,<DOMAINDN>
+    #   Forest records are in CN=MicrosoftDNS,DC=ForestDnsZones,<DOMAINDN>
+
+    domaindn = names.domaindn
+    forestdn = samdb.get_root_basedn().get_linearized()
+
+    dnsdomain = names.dnsdomain.lower()
+    dnsforest = dnsdomain
+
+    hostname = names.netbiosname.lower()
+    site = names.sitename
+
+    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=System,%s" % domaindn)
+
+    # Set up MicrosoftDNS container
+    add_dns_container(samdb, domaindn, "CN=System")
+
+    # Add root servers
+    add_rootservers(samdb, domaindn, "CN=System")
+
+    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)
+
+    elif (dns_backend == "SAMBA" or dns_backend == "BIND9_DLZ") and (
+            os_level == DS_DOMAIN_FUNCTION_2003 or
+            os_level == DS_DOMAIN_FUNCTION_2008 or
+            os_level == DS_DOMAIN_FUNCTION_2008_R2):
+
+        # Set up additional partitions (DomainDnsZones, ForstDnsZones)
+        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)