provision: Add support for BIND 9.12.x
[samba.git] / python / samba / provision / sambadns.py
1 # Unix SMB/CIFS implementation.
2 # backend code for provisioning DNS for a Samba4 server
3 #
4 # Copyright (C) Kai Blin <kai@samba.org> 2011
5 # Copyright (C) Amitay Isaacs <amitay@gmail.com> 2011
6 #
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
11 #
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16 #
17 # You should have received a copy of the GNU General Public License
18 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
19 #
20
21 """DNS-related provisioning"""
22
23 import os
24 import uuid
25 import shutil
26 import time
27 import ldb
28 from base64 import b64encode
29 import subprocess
30 import samba
31 from samba.tdb_util import tdb_copy
32 from samba.mdb_util import mdb_copy
33 from samba.ndr import ndr_pack, ndr_unpack
34 from samba import setup_file
35 from samba.dcerpc import dnsp, misc, security
36 from samba.dsdb import (
37     DS_DOMAIN_FUNCTION_2000,
38     DS_DOMAIN_FUNCTION_2003,
39     DS_DOMAIN_FUNCTION_2008_R2,
40     DS_DOMAIN_FUNCTION_2012_R2,
41     DS_DOMAIN_FUNCTION_2016
42     )
43 from samba.descriptor import (
44     get_domain_descriptor,
45     get_domain_delete_protected1_descriptor,
46     get_domain_delete_protected2_descriptor,
47     get_dns_partition_descriptor,
48     get_dns_forest_microsoft_dns_descriptor,
49     get_dns_domain_microsoft_dns_descriptor
50     )
51 from samba.provision.common import (
52     setup_path,
53     setup_add_ldif,
54     setup_modify_ldif,
55     setup_ldb,
56     FILL_FULL,
57     FILL_SUBDOMAIN,
58     FILL_NT4SYNC,
59     FILL_DRS,
60     )
61
62 from samba.samdb import get_default_backend_store
63
64 def get_domainguid(samdb, domaindn):
65     res = samdb.search(base=domaindn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
66     domainguid =  str(ndr_unpack(misc.GUID, res[0]["objectGUID"][0]))
67     return domainguid
68
69
70 def get_dnsadmins_sid(samdb, domaindn):
71     res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % domaindn, scope=ldb.SCOPE_BASE,
72                        attrs=["objectSid"])
73     dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
74     return dnsadmins_sid
75
76
77 class ARecord(dnsp.DnssrvRpcRecord):
78
79     def __init__(self, ip_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
80         super(ARecord, self).__init__()
81         self.wType = dnsp.DNS_TYPE_A
82         self.rank = rank
83         self.dwSerial = serial
84         self.dwTtlSeconds = ttl
85         self.data = ip_addr
86
87
88 class AAAARecord(dnsp.DnssrvRpcRecord):
89
90     def __init__(self, ip6_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
91         super(AAAARecord, self).__init__()
92         self.wType = dnsp.DNS_TYPE_AAAA
93         self.rank = rank
94         self.dwSerial = serial
95         self.dwTtlSeconds = ttl
96         self.data = ip6_addr
97
98
99 class CNameRecord(dnsp.DnssrvRpcRecord):
100
101     def __init__(self, cname, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
102         super(CNameRecord, self).__init__()
103         self.wType = dnsp.DNS_TYPE_CNAME
104         self.rank = rank
105         self.dwSerial = serial
106         self.dwTtlSeconds = ttl
107         self.data = cname
108
109
110 class NSRecord(dnsp.DnssrvRpcRecord):
111
112     def __init__(self, dns_server, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
113         super(NSRecord, self).__init__()
114         self.wType = dnsp.DNS_TYPE_NS
115         self.rank = rank
116         self.dwSerial = serial
117         self.dwTtlSeconds = ttl
118         self.data = dns_server
119
120
121 class SOARecord(dnsp.DnssrvRpcRecord):
122
123     def __init__(self, mname, rname, serial=1, refresh=900, retry=600,
124                  expire=86400, minimum=3600, ttl=3600, rank=dnsp.DNS_RANK_ZONE):
125         super(SOARecord, self).__init__()
126         self.wType = dnsp.DNS_TYPE_SOA
127         self.rank = rank
128         self.dwSerial = serial
129         self.dwTtlSeconds = ttl
130         soa = dnsp.soa()
131         soa.serial = serial
132         soa.refresh = refresh
133         soa.retry = retry
134         soa.expire = expire
135         soa.mname = mname
136         soa.rname = rname
137         soa.minimum = minimum
138         self.data = soa
139
140
141 class SRVRecord(dnsp.DnssrvRpcRecord):
142
143     def __init__(self, target, port, priority=0, weight=100, serial=1, ttl=900,
144                 rank=dnsp.DNS_RANK_ZONE):
145         super(SRVRecord, self).__init__()
146         self.wType = dnsp.DNS_TYPE_SRV
147         self.rank = rank
148         self.dwSerial = serial
149         self.dwTtlSeconds = ttl
150         srv = dnsp.srv()
151         srv.nameTarget = target
152         srv.wPort = port
153         srv.wPriority = priority
154         srv.wWeight = weight
155         self.data = srv
156
157
158 class TXTRecord(dnsp.DnssrvRpcRecord):
159
160     def __init__(self, slist, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
161         super(TXTRecord, self).__init__()
162         self.wType = dnsp.DNS_TYPE_TXT
163         self.rank = rank
164         self.dwSerial = serial
165         self.dwTtlSeconds = ttl
166         stringlist = dnsp.string_list()
167         stringlist.count = len(slist)
168         stringlist.str = slist
169         self.data = stringlist
170
171
172 class TypeProperty(dnsp.DnsProperty):
173
174     def __init__(self, zone_type=dnsp.DNS_ZONE_TYPE_PRIMARY):
175         super(TypeProperty, self).__init__()
176         self.wDataLength = 1
177         self.version = 1
178         self.id = dnsp.DSPROPERTY_ZONE_TYPE
179         self.data = zone_type
180
181
182 class AllowUpdateProperty(dnsp.DnsProperty):
183
184     def __init__(self, allow_update=dnsp.DNS_ZONE_UPDATE_SECURE):
185         super(AllowUpdateProperty, self).__init__()
186         self.wDataLength = 1
187         self.version = 1
188         self.id = dnsp.DSPROPERTY_ZONE_ALLOW_UPDATE
189         self.data = allow_update
190
191
192 class SecureTimeProperty(dnsp.DnsProperty):
193
194     def __init__(self, secure_time=0):
195         super(SecureTimeProperty, self).__init__()
196         self.wDataLength = 1
197         self.version = 1
198         self.id = dnsp.DSPROPERTY_ZONE_SECURE_TIME
199         self.data = secure_time
200
201
202 class NorefreshIntervalProperty(dnsp.DnsProperty):
203
204     def __init__(self, norefresh_interval=0):
205         super(NorefreshIntervalProperty, self).__init__()
206         self.wDataLength = 1
207         self.version = 1
208         self.id = dnsp.DSPROPERTY_ZONE_NOREFRESH_INTERVAL
209         self.data = norefresh_interval
210
211
212 class RefreshIntervalProperty(dnsp.DnsProperty):
213
214     def __init__(self, refresh_interval=0):
215         super(RefreshIntervalProperty, self).__init__()
216         self.wDataLength = 1
217         self.version = 1
218         self.id = dnsp.DSPROPERTY_ZONE_REFRESH_INTERVAL
219         self.data = refresh_interval
220
221
222 class AgingStateProperty(dnsp.DnsProperty):
223
224     def __init__(self, aging_enabled=0):
225         super(AgingStateProperty, self).__init__()
226         self.wDataLength = 1
227         self.version = 1
228         self.id = dnsp.DSPROPERTY_ZONE_AGING_STATE
229         self.data = aging_enabled
230
231
232 class AgingEnabledTimeProperty(dnsp.DnsProperty):
233
234     def __init__(self, next_cycle_hours=0):
235         super(AgingEnabledTimeProperty, self).__init__()
236         self.wDataLength = 1
237         self.version = 1;
238         self.id = dnsp.DSPROPERTY_ZONE_AGING_ENABLED_TIME
239         self.data = next_cycle_hours
240
241
242 def setup_dns_partitions(samdb, domainsid, domaindn, forestdn, configdn,
243                          serverdn, fill_level):
244     domainzone_dn = "DC=DomainDnsZones,%s" % domaindn
245     forestzone_dn = "DC=ForestDnsZones,%s" % forestdn
246     descriptor = get_dns_partition_descriptor(domainsid)
247
248     setup_add_ldif(samdb, setup_path("provision_dnszones_partitions.ldif"), {
249         "ZONE_DN": domainzone_dn,
250         "SECDESC"      : b64encode(descriptor).decode('utf8')
251         })
252     if fill_level != FILL_SUBDOMAIN:
253         setup_add_ldif(samdb, setup_path("provision_dnszones_partitions.ldif"), {
254             "ZONE_DN": forestzone_dn,
255             "SECDESC"      : b64encode(descriptor).decode('utf8')
256         })
257
258     domainzone_guid = get_domainguid(samdb, domainzone_dn)
259     domainzone_guid = str(uuid.uuid4())
260     domainzone_dns = ldb.Dn(samdb, domainzone_dn).canonical_ex_str().strip()
261
262     protected1_desc = get_domain_delete_protected1_descriptor(domainsid)
263     protected2_desc = get_domain_delete_protected2_descriptor(domainsid)
264     setup_add_ldif(samdb, setup_path("provision_dnszones_add.ldif"), {
265         "ZONE_DN": domainzone_dn,
266         "ZONE_GUID": domainzone_guid,
267         "ZONE_DNS": domainzone_dns,
268         "CONFIGDN": configdn,
269         "SERVERDN": serverdn,
270         "LOSTANDFOUND_DESCRIPTOR": b64encode(protected2_desc).decode('utf8'),
271         "INFRASTRUCTURE_DESCRIPTOR": b64encode(protected1_desc).decode('utf8'),
272         })
273     setup_modify_ldif(samdb, setup_path("provision_dnszones_modify.ldif"), {
274         "CONFIGDN": configdn,
275         "SERVERDN": serverdn,
276         "ZONE_DN": domainzone_dn,
277     })
278
279     if fill_level != FILL_SUBDOMAIN:
280         forestzone_guid = get_domainguid(samdb, forestzone_dn)
281         forestzone_guid = str(uuid.uuid4())
282         forestzone_dns = ldb.Dn(samdb, forestzone_dn).canonical_ex_str().strip()
283
284         setup_add_ldif(samdb, setup_path("provision_dnszones_add.ldif"), {
285             "ZONE_DN": forestzone_dn,
286             "ZONE_GUID": forestzone_guid,
287             "ZONE_DNS": forestzone_dns,
288             "CONFIGDN": configdn,
289             "SERVERDN": serverdn,
290             "LOSTANDFOUND_DESCRIPTOR": b64encode(protected2_desc).decode('utf8'),
291             "INFRASTRUCTURE_DESCRIPTOR": b64encode(protected1_desc).decode('utf8'),
292         })
293         setup_modify_ldif(samdb, setup_path("provision_dnszones_modify.ldif"), {
294             "CONFIGDN": configdn,
295             "SERVERDN": serverdn,
296             "ZONE_DN": forestzone_dn,
297         })
298
299
300 def add_dns_accounts(samdb, domaindn):
301     setup_add_ldif(samdb, setup_path("provision_dns_accounts_add.ldif"), {
302         "DOMAINDN": domaindn,
303         })
304
305
306 def add_dns_container(samdb, domaindn, prefix, domain_sid, dnsadmins_sid, forest=False):
307     name_map = {'DnsAdmins': str(dnsadmins_sid)}
308     if forest is True:
309         sd_val = get_dns_forest_microsoft_dns_descriptor(domain_sid,
310                                                          name_map=name_map)
311     else:
312         sd_val = get_dns_domain_microsoft_dns_descriptor(domain_sid,
313                                                          name_map=name_map)
314     # CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
315     msg = ldb.Message(ldb.Dn(samdb, "CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)))
316     msg["objectClass"] = ["top", "container"]
317     msg["nTSecurityDescriptor"] = ldb.MessageElement(sd_val, ldb.FLAG_MOD_ADD,
318         "nTSecurityDescriptor")
319     samdb.add(msg)
320
321
322 def add_rootservers(samdb, domaindn, prefix):
323     # https://www.internic.net/zones/named.root
324     rootservers = {}
325     rootservers["a.root-servers.net"] = "198.41.0.4"
326     rootservers["b.root-servers.net"] = "192.228.79.201"
327     rootservers["c.root-servers.net"] = "192.33.4.12"
328     rootservers["d.root-servers.net"] = "199.7.91.13"
329     rootservers["e.root-servers.net"] = "192.203.230.10"
330     rootservers["f.root-servers.net"] = "192.5.5.241"
331     rootservers["g.root-servers.net"] = "192.112.36.4"
332     rootservers["h.root-servers.net"] = "198.97.190.53"
333     rootservers["i.root-servers.net"] = "192.36.148.17"
334     rootservers["j.root-servers.net"] = "192.58.128.30"
335     rootservers["k.root-servers.net"] = "193.0.14.129"
336     rootservers["l.root-servers.net"] = "199.7.83.42"
337     rootservers["m.root-servers.net"] = "202.12.27.33"
338
339     rootservers_v6 = {}
340     rootservers_v6["a.root-servers.net"] = "2001:503:ba3e::2:30"
341     rootservers_v6["b.root-servers.net"] = "2001:500:84::b"
342     rootservers_v6["c.root-servers.net"] = "2001:500:2::c"
343     rootservers_v6["d.root-servers.net"] = "2001:500:2d::d"
344     rootservers_v6["e.root-servers.net"] = "2001:500:a8::e"
345     rootservers_v6["f.root-servers.net"] = "2001:500:2f::f"
346     rootservers_v6["g.root-servers.net"] = "2001:500:12::d0d"
347     rootservers_v6["h.root-servers.net"] = "2001:500:1::53"
348     rootservers_v6["i.root-servers.net"] = "2001:7fe::53"
349     rootservers_v6["j.root-servers.net"] = "2001:503:c27::2:30"
350     rootservers_v6["k.root-servers.net"] = "2001:7fd::1"
351     rootservers_v6["l.root-servers.net"] = "2001:500:9f::42"
352     rootservers_v6["m.root-servers.net"] = "2001:dc3::35"
353
354     container_dn = "DC=RootDNSServers,CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)
355
356     # Add DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
357     msg = ldb.Message(ldb.Dn(samdb, container_dn))
358     props = []
359     props.append(ndr_pack(TypeProperty(zone_type=dnsp.DNS_ZONE_TYPE_CACHE)))
360     props.append(ndr_pack(AllowUpdateProperty(allow_update=dnsp.DNS_ZONE_UPDATE_OFF)))
361     props.append(ndr_pack(SecureTimeProperty()))
362     props.append(ndr_pack(NorefreshIntervalProperty()))
363     props.append(ndr_pack(RefreshIntervalProperty()))
364     props.append(ndr_pack(AgingStateProperty()))
365     props.append(ndr_pack(AgingEnabledTimeProperty()))
366     msg["objectClass"] = ["top", "dnsZone"]
367     msg["cn"] = ldb.MessageElement("Zone", ldb.FLAG_MOD_ADD, "cn")
368     msg["dNSProperty"] = ldb.MessageElement(props, ldb.FLAG_MOD_ADD, "dNSProperty")
369     samdb.add(msg)
370
371     # Add DC=@,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
372     record = []
373     for rserver in rootservers:
374         record.append(ndr_pack(NSRecord(rserver, serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT)))
375
376     msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
377     msg["objectClass"] = ["top", "dnsNode"]
378     msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
379     samdb.add(msg)
380
381     # Add DC=<rootserver>,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
382     for rserver in rootservers:
383         record = [ndr_pack(ARecord(rootservers[rserver], serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT))]
384         # Add AAAA record as well (How does W2K* add IPv6 records?)
385         #if rserver in rootservers_v6:
386         #    record.append(ndr_pack(AAAARecord(rootservers_v6[rserver], serial=0, ttl=0)))
387         msg = ldb.Message(ldb.Dn(samdb, "DC=%s,%s" % (rserver, container_dn)))
388         msg["objectClass"] = ["top", "dnsNode"]
389         msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
390         samdb.add(msg)
391
392 def add_at_record(samdb, container_dn, prefix, hostname, dnsdomain, hostip, hostip6):
393
394     fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
395
396     at_records = []
397
398     # SOA record
399     at_soa_record = SOARecord(fqdn_hostname, "hostmaster.%s" % dnsdomain)
400     at_records.append(ndr_pack(at_soa_record))
401
402     # NS record
403     at_ns_record = NSRecord(fqdn_hostname)
404     at_records.append(ndr_pack(at_ns_record))
405
406     if hostip is not None:
407         # A record
408         at_a_record = ARecord(hostip)
409         at_records.append(ndr_pack(at_a_record))
410
411     if hostip6 is not None:
412         # AAAA record
413         at_aaaa_record = AAAARecord(hostip6)
414         at_records.append(ndr_pack(at_aaaa_record))
415
416     msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
417     msg["objectClass"] = ["top", "dnsNode"]
418     msg["dnsRecord"] = ldb.MessageElement(at_records, ldb.FLAG_MOD_ADD, "dnsRecord")
419     samdb.add(msg)
420
421
422 def add_srv_record(samdb, container_dn, prefix, host, port):
423     srv_record = SRVRecord(host, port)
424     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
425     msg["objectClass"] = ["top", "dnsNode"]
426     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(srv_record), ldb.FLAG_MOD_ADD, "dnsRecord")
427     samdb.add(msg)
428
429
430 def add_ns_record(samdb, container_dn, prefix, host):
431     ns_record = NSRecord(host)
432     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
433     msg["objectClass"] = ["top", "dnsNode"]
434     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
435     samdb.add(msg)
436
437
438 def add_ns_glue_record(samdb, container_dn, prefix, host):
439     ns_record = NSRecord(host, rank=dnsp.DNS_RANK_NS_GLUE)
440     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
441     msg["objectClass"] = ["top", "dnsNode"]
442     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
443     samdb.add(msg)
444
445
446 def add_cname_record(samdb, container_dn, prefix, host):
447     cname_record = CNameRecord(host)
448     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
449     msg["objectClass"] = ["top", "dnsNode"]
450     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(cname_record), ldb.FLAG_MOD_ADD, "dnsRecord")
451     samdb.add(msg)
452
453
454 def add_host_record(samdb, container_dn, prefix, hostip, hostip6):
455     host_records = []
456     if hostip:
457         a_record = ARecord(hostip)
458         host_records.append(ndr_pack(a_record))
459     if hostip6:
460         aaaa_record = AAAARecord(hostip6)
461         host_records.append(ndr_pack(aaaa_record))
462     if host_records:
463         msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
464         msg["objectClass"] = ["top", "dnsNode"]
465         msg["dnsRecord"] = ldb.MessageElement(host_records, ldb.FLAG_MOD_ADD, "dnsRecord")
466         samdb.add(msg)
467
468
469 def add_domain_record(samdb, domaindn, prefix, dnsdomain, domainsid, dnsadmins_sid):
470     # DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
471     sddl = "O:SYG:BAD:AI" \
472     "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)" \
473     "(A;;CC;;;AU)" \
474     "(A;;RPLCLORC;;;WD)" \
475     "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
476     "(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
477     "(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;%s)" \
478     "(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
479     "(OA;CIID;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
480     "(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
481     "(A;CIID;LC;;;RU)" \
482     "(A;CIID;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
483     "S:AI" % dnsadmins_sid
484     sec = security.descriptor.from_sddl(sddl, domainsid)
485     props = []
486     props.append(ndr_pack(TypeProperty()))
487     props.append(ndr_pack(AllowUpdateProperty()))
488     props.append(ndr_pack(SecureTimeProperty()))
489     props.append(ndr_pack(NorefreshIntervalProperty(norefresh_interval=168)))
490     props.append(ndr_pack(RefreshIntervalProperty(refresh_interval=168)))
491     props.append(ndr_pack(AgingStateProperty()))
492     props.append(ndr_pack(AgingEnabledTimeProperty()))
493     msg = ldb.Message(ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" % (dnsdomain, prefix, domaindn)))
494     msg["objectClass"] = ["top", "dnsZone"]
495     msg["ntSecurityDescriptor"] = ldb.MessageElement(ndr_pack(sec), ldb.FLAG_MOD_ADD,
496         "nTSecurityDescriptor")
497     msg["dNSProperty"] = ldb.MessageElement(props, ldb.FLAG_MOD_ADD, "dNSProperty")
498     samdb.add(msg)
499
500
501 def add_msdcs_record(samdb, forestdn, prefix, dnsforest):
502     # DC=_msdcs.<DNSFOREST>,CN=MicrosoftDNS,<PREFIX>,<FORESTDN>
503     msg = ldb.Message(ldb.Dn(samdb, "DC=_msdcs.%s,CN=MicrosoftDNS,%s,%s" %
504                                     (dnsforest, prefix, forestdn)))
505     msg["objectClass"] = ["top", "dnsZone"]
506     samdb.add(msg)
507
508
509 def add_dc_domain_records(samdb, domaindn, prefix, site, dnsdomain, hostname,
510         hostip, hostip6):
511
512     fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
513
514     # Set up domain container - DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
515     domain_container_dn = ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" %
516                                     (dnsdomain, prefix, domaindn))
517
518     # DC=@ record
519     add_at_record(samdb, domain_container_dn, "DC=@", hostname, dnsdomain,
520             hostip, hostip6)
521
522     # DC=<HOSTNAME> record
523     add_host_record(samdb, domain_container_dn, "DC=%s" % hostname, hostip,
524             hostip6)
525
526     # DC=_kerberos._tcp record
527     add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp",
528             fqdn_hostname, 88)
529
530     # DC=_kerberos._tcp.<SITENAME>._sites record
531     add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp.%s._sites" %
532             site, fqdn_hostname, 88)
533
534     # DC=_kerberos._udp record
535     add_srv_record(samdb, domain_container_dn, "DC=_kerberos._udp",
536             fqdn_hostname, 88)
537
538     # DC=_kpasswd._tcp record
539     add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._tcp",
540             fqdn_hostname, 464)
541
542     # DC=_kpasswd._udp record
543     add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._udp",
544             fqdn_hostname, 464)
545
546     # DC=_ldap._tcp record
547     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp", fqdn_hostname,
548             389)
549
550     # DC=_ldap._tcp.<SITENAME>._sites record
551     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites" %
552             site, fqdn_hostname, 389)
553
554     # FIXME: The number of SRV records depend on the various roles this DC has.
555     #        _gc and _msdcs records are added if the we are the forest dc and not subdomain dc
556     #
557     # Assumption: current DC is GC and add all the entries
558
559     # DC=_gc._tcp record
560     add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp", fqdn_hostname,
561             3268)
562
563     # DC=_gc._tcp.<SITENAME>,_sites record
564     add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp.%s._sites" % site,
565             fqdn_hostname, 3268)
566
567     # DC=_msdcs record
568     add_ns_glue_record(samdb, domain_container_dn, "DC=_msdcs", fqdn_hostname)
569
570     # FIXME: Following entries are added only if DomainDnsZones and ForestDnsZones partitions
571     #        are created
572     #
573     # Assumption: Additional entries won't hurt on os_level = 2000
574
575     # DC=_ldap._tcp.<SITENAME>._sites.DomainDnsZones
576     add_srv_record(samdb, domain_container_dn,
577             "DC=_ldap._tcp.%s._sites.DomainDnsZones" % site, fqdn_hostname,
578             389)
579
580     # DC=_ldap._tcp.<SITENAME>._sites.ForestDnsZones
581     add_srv_record(samdb, domain_container_dn,
582             "DC=_ldap._tcp.%s._sites.ForestDnsZones" % site, fqdn_hostname,
583             389)
584
585     # DC=_ldap._tcp.DomainDnsZones
586     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.DomainDnsZones",
587                     fqdn_hostname, 389)
588
589     # DC=_ldap._tcp.ForestDnsZones
590     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.ForestDnsZones",
591                     fqdn_hostname, 389)
592
593     # DC=DomainDnsZones
594     add_host_record(samdb, domain_container_dn, "DC=DomainDnsZones", hostip,
595             hostip6)
596
597     # DC=ForestDnsZones
598     add_host_record(samdb, domain_container_dn, "DC=ForestDnsZones", hostip,
599             hostip6)
600
601
602 def add_dc_msdcs_records(samdb, forestdn, prefix, site, dnsforest, hostname,
603                             hostip, hostip6, domainguid, ntdsguid):
604
605     fqdn_hostname = "%s.%s" % (hostname, dnsforest)
606
607     # Set up forest container - DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
608     forest_container_dn = ldb.Dn(samdb, "DC=_msdcs.%s,CN=MicrosoftDNS,%s,%s" %
609                                     (dnsforest, prefix, forestdn))
610
611     # DC=@ record
612     add_at_record(samdb, forest_container_dn, "DC=@", hostname, dnsforest,
613             None, None)
614
615     # DC=_kerberos._tcp.dc record
616     add_srv_record(samdb, forest_container_dn, "DC=_kerberos._tcp.dc",
617             fqdn_hostname, 88)
618
619     # DC=_kerberos._tcp.<SITENAME>._sites.dc record
620     add_srv_record(samdb, forest_container_dn,
621             "DC=_kerberos._tcp.%s._sites.dc" % site, fqdn_hostname, 88)
622
623     # DC=_ldap._tcp.dc record
624     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.dc",
625             fqdn_hostname, 389)
626
627     # DC=_ldap._tcp.<SITENAME>._sites.dc record
628     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.dc" %
629             site, fqdn_hostname, 389)
630
631     # DC=_ldap._tcp.<SITENAME>._sites.gc record
632     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.gc" %
633             site, fqdn_hostname, 3268)
634
635     # DC=_ldap._tcp.gc record
636     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.gc",
637             fqdn_hostname, 3268)
638
639     # DC=_ldap._tcp.pdc record
640     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.pdc",
641             fqdn_hostname, 389)
642
643     # DC=gc record
644     add_host_record(samdb, forest_container_dn, "DC=gc", hostip, hostip6)
645
646     # DC=_ldap._tcp.<DOMAINGUID>.domains record
647     add_srv_record(samdb, forest_container_dn,
648             "DC=_ldap._tcp.%s.domains" % domainguid, fqdn_hostname, 389)
649
650     # DC=<NTDSGUID>
651     add_cname_record(samdb, forest_container_dn, "DC=%s" % ntdsguid,
652             fqdn_hostname)
653
654
655 def secretsdb_setup_dns(secretsdb, names, private_dir, binddns_dir, realm,
656                         dnsdomain, dns_keytab_path, dnspass, key_version_number):
657     """Add DNS specific bits to a secrets database.
658
659     :param secretsdb: Ldb Handle to the secrets database
660     :param names: Names shortcut
661     :param machinepass: Machine password
662     """
663     try:
664         os.unlink(os.path.join(private_dir, dns_keytab_path))
665         os.unlink(os.path.join(binddns_dir, dns_keytab_path))
666     except OSError:
667         pass
668
669     if key_version_number is None:
670         key_version_number = 1
671
672     # This will create the dns.keytab file in the private_dir when it is
673     # commited!
674     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
675             "REALM": realm,
676             "DNSDOMAIN": dnsdomain,
677             "DNS_KEYTAB": dns_keytab_path,
678             "DNSPASS_B64": b64encode(dnspass.encode('utf-8')).decode('utf8'),
679             "KEY_VERSION_NUMBER": str(key_version_number),
680             "HOSTNAME": names.hostname,
681             "DNSNAME" : '%s.%s' % (
682                 names.netbiosname.lower(), names.dnsdomain.lower())
683             })
684
685
686 def create_dns_dir(logger, paths):
687     """Write out a DNS zone file, from the info in the current database.
688
689     :param logger: Logger object
690     :param paths: paths object
691     """
692     dns_dir = os.path.dirname(paths.dns)
693
694     try:
695         shutil.rmtree(dns_dir, True)
696     except OSError:
697         pass
698
699     os.mkdir(dns_dir, 0o770)
700
701     if paths.bind_gid is not None:
702         try:
703             os.chown(dns_dir, -1, paths.bind_gid)
704             # chmod needed to cope with umask
705             os.chmod(dns_dir, 0o770)
706         except OSError:
707             if not os.environ.has_key('SAMBA_SELFTEST'):
708                 logger.error("Failed to chown %s to bind gid %u" % (
709                     dns_dir, paths.bind_gid))
710
711
712 def create_zone_file(lp, logger, paths, targetdir, dnsdomain,
713                      hostip, hostip6, hostname, realm, domainguid,
714                      ntdsguid, site):
715     """Write out a DNS zone file, from the info in the current database.
716
717     :param paths: paths object
718     :param dnsdomain: DNS Domain name
719     :param domaindn: DN of the Domain
720     :param hostip: Local IPv4 IP
721     :param hostip6: Local IPv6 IP
722     :param hostname: Local hostname
723     :param realm: Realm name
724     :param domainguid: GUID of the domain.
725     :param ntdsguid: GUID of the hosts nTDSDSA record.
726     """
727     assert isinstance(domainguid, str)
728
729     if hostip6 is not None:
730         hostip6_base_line = "            IN AAAA    " + hostip6
731         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
732         gc_msdcs_ip6_line = "gc._msdcs               IN AAAA    " + hostip6
733     else:
734         hostip6_base_line = ""
735         hostip6_host_line = ""
736         gc_msdcs_ip6_line = ""
737
738     if hostip is not None:
739         hostip_base_line = "            IN A    " + hostip
740         hostip_host_line = hostname + "        IN A    " + hostip
741         gc_msdcs_ip_line = "gc._msdcs               IN A    " + hostip
742     else:
743         hostip_base_line = ""
744         hostip_host_line = ""
745         gc_msdcs_ip_line = ""
746
747     # we need to freeze the zone while we update the contents
748     if targetdir is None:
749         rndc = ' '.join(lp.get("rndc command"))
750         os.system(rndc + " freeze " + lp.get("realm"))
751
752     setup_file(setup_path("provision.zone"), paths.dns, {
753             "HOSTNAME": hostname,
754             "DNSDOMAIN": dnsdomain,
755             "REALM": realm,
756             "HOSTIP_BASE_LINE": hostip_base_line,
757             "HOSTIP_HOST_LINE": hostip_host_line,
758             "DOMAINGUID": domainguid,
759             "DATESTRING": time.strftime("%Y%m%d%H"),
760             "DEFAULTSITE": site,
761             "NTDSGUID": ntdsguid,
762             "HOSTIP6_BASE_LINE": hostip6_base_line,
763             "HOSTIP6_HOST_LINE": hostip6_host_line,
764             "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
765             "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
766         })
767
768     if paths.bind_gid is not None:
769         try:
770             os.chown(paths.dns, -1, paths.bind_gid)
771             # chmod needed to cope with umask
772             os.chmod(paths.dns, 0o664)
773         except OSError:
774             if not os.environ.has_key('SAMBA_SELFTEST'):
775                 logger.error("Failed to chown %s to bind gid %u" % (
776                     paths.dns, paths.bind_gid))
777
778     if targetdir is None:
779         os.system(rndc + " unfreeze " + lp.get("realm"))
780
781
782 def create_samdb_copy(samdb, logger, paths, names, domainsid, domainguid):
783     """Create a copy of samdb and give write permissions to named for dns partitions
784     """
785     private_dir = paths.private_dir
786     samldb_dir = os.path.join(private_dir, "sam.ldb.d")
787     dns_dir = os.path.dirname(paths.dns)
788     dns_samldb_dir = os.path.join(dns_dir, "sam.ldb.d")
789
790     # Find the partitions and corresponding filenames
791     partfile = {}
792     res = samdb.search(base="@PARTITION",
793                        scope=ldb.SCOPE_BASE,
794                        attrs=["partition", "backendStore"])
795     for tmp in res[0]["partition"]:
796         (nc, fname) = tmp.split(':')
797         partfile[nc.upper()] = fname
798
799     backend_store = get_default_backend_store()
800     if "backendStore" in res[0]:
801         backend_store = res[0]["backendStore"][0]
802
803     # Create empty domain partition
804
805     domaindn = names.domaindn.upper()
806     domainpart_file = os.path.join(dns_dir, partfile[domaindn])
807     try:
808         os.mkdir(dns_samldb_dir)
809         open(domainpart_file, 'w').close()
810
811         # Fill the basedn and @OPTION records in domain partition
812         dom_url = "%s://%s" % (backend_store, domainpart_file)
813         dom_ldb = samba.Ldb(dom_url)
814
815         # We need the dummy main-domain DB to have the correct @INDEXLIST
816         index_res = samdb.search(base="@INDEXLIST", scope=ldb.SCOPE_BASE)
817         dom_ldb.add(index_res[0])
818
819         domainguid_line = "objectGUID: %s\n-" % domainguid
820         descr = b64encode(get_domain_descriptor(domainsid)).decode('utf8')
821         setup_add_ldif(dom_ldb, setup_path("provision_basedn.ldif"), {
822             "DOMAINDN" : names.domaindn,
823             "DOMAINGUID" : domainguid_line,
824             "DOMAINSID" : str(domainsid),
825             "DESCRIPTOR" : descr})
826         setup_add_ldif(dom_ldb,
827             setup_path("provision_basedn_options.ldif"), None)
828
829     except:
830         logger.error(
831             "Failed to setup database for BIND, AD based DNS cannot be used")
832         raise
833
834     # This line is critical to the security of the whole scheme.
835     # We assume there is no secret data in the (to be left out of
836     # date and essentially read-only) config, schema and metadata partitions.
837     #
838     # Only the stub of the domain partition is created above.
839     #
840     # That way, things like the krbtgt key do not leak.
841     del partfile[domaindn]
842
843     # Link dns partitions and metadata
844     domainzonedn = "DC=DOMAINDNSZONES,%s" % names.domaindn.upper()
845     forestzonedn = "DC=FORESTDNSZONES,%s" % names.rootdn.upper()
846
847     domainzone_file = partfile[domainzonedn]
848     forestzone_file = partfile.get(forestzonedn)
849
850     metadata_file = "metadata.tdb"
851     try:
852         os.link(os.path.join(samldb_dir, metadata_file),
853             os.path.join(dns_samldb_dir, metadata_file))
854         os.link(os.path.join(private_dir, domainzone_file),
855             os.path.join(dns_dir, domainzone_file))
856         if forestzone_file:
857             os.link(os.path.join(private_dir, forestzone_file),
858                     os.path.join(dns_dir, forestzone_file))
859     except OSError:
860         logger.error(
861             "Failed to setup database for BIND, AD based DNS cannot be used")
862         raise
863     del partfile[domainzonedn]
864     if forestzone_file:
865         del partfile[forestzonedn]
866
867     # Copy root, config, schema partitions (and any other if any)
868     # Since samdb is open in the current process, copy them in a child process
869     try:
870         tdb_copy(os.path.join(private_dir, "sam.ldb"),
871                  os.path.join(dns_dir, "sam.ldb"))
872         for nc in partfile:
873             pfile = partfile[nc]
874             if backend_store == "mdb":
875                 mdb_copy(os.path.join(private_dir, pfile),
876                         os.path.join(dns_dir, pfile))
877             else:
878                 tdb_copy(os.path.join(private_dir, pfile),
879                         os.path.join(dns_dir, pfile))
880     except:
881         logger.error(
882             "Failed to setup database for BIND, AD based DNS cannot be used")
883         raise
884
885     # Give bind read/write permissions dns partitions
886     if paths.bind_gid is not None:
887         try:
888             for dirname, dirs, files in os.walk(dns_dir):
889                 for d in dirs:
890                     dpath = os.path.join(dirname, d)
891                     os.chown(dpath, -1, paths.bind_gid)
892                     os.chmod(dpath, 0o770)
893                 for f in files:
894                     if f.endswith(('.ldb', '.tdb', 'ldb-lock')):
895                         fpath = os.path.join(dirname, f)
896                         os.chown(fpath, -1, paths.bind_gid)
897                         os.chmod(fpath, 0o660)
898         except OSError:
899             if not os.environ.has_key('SAMBA_SELFTEST'):
900                 logger.error(
901                     "Failed to set permissions to sam.ldb* files, fix manually")
902     else:
903         if not os.environ.has_key('SAMBA_SELFTEST'):
904             logger.warning("""Unable to find group id for BIND,
905                 set permissions to sam.ldb* files manually""")
906
907
908 def create_dns_update_list(lp, logger, paths):
909     """Write out a dns_update_list file"""
910     # note that we use no variable substitution on this file
911     # the substitution is done at runtime by samba_dnsupdate, samba_spnupdate
912     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
913     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
914
915
916 def create_named_conf(paths, realm, dnsdomain, dns_backend, logger):
917     """Write out a file containing zone statements suitable for inclusion in a
918     named.conf file (including GSS-TSIG configuration).
919
920     :param paths: all paths
921     :param realm: Realm name
922     :param dnsdomain: DNS Domain name
923     :param dns_backend: DNS backend type
924     :param keytab_name: File name of DNS keytab file
925     :param logger: Logger object
926     """
927
928     # TODO: This really should have been done as a top level import.
929     # It is done here to avoid a depencency loop.  That is, we move
930     # ProvisioningError to another file, and have all the provision
931     # scripts import it from there.
932
933     from samba.provision import ProvisioningError
934
935     if dns_backend == "BIND9_FLATFILE":
936         setup_file(setup_path("named.conf"), paths.namedconf, {
937                     "DNSDOMAIN": dnsdomain,
938                     "REALM": realm,
939                     "ZONE_FILE": paths.dns,
940                     "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
941                     "NAMED_CONF": paths.namedconf,
942                     "NAMED_CONF_UPDATE": paths.namedconf_update
943                     })
944
945         setup_file(setup_path("named.conf.update"), paths.namedconf_update)
946
947     elif dns_backend == "BIND9_DLZ":
948         bind_info = subprocess.Popen(['named -V'], shell=True,
949                                      stdout=subprocess.PIPE,
950                                      stderr=subprocess.STDOUT,
951                                      cwd='.').communicate()[0]
952         bind9_8 = '#'
953         bind9_9 = '#'
954         bind9_10 = '#'
955         bind9_11 = '#'
956         bind9_12 = '#'
957         if bind_info.upper().find('BIND 9.8') != -1:
958             bind9_8 = ''
959         elif bind_info.upper().find('BIND 9.9') != -1:
960             bind9_9 = ''
961         elif bind_info.upper().find('BIND 9.10') != -1:
962             bind9_10 = ''
963         elif bind_info.upper().find('BIND 9.11') != -1:
964             bind9_11 = ''
965         elif bind_info.upper().find('BIND 9.12') != -1:
966             bind9_12 = ''
967         elif bind_info.upper().find('BIND 9.7') != -1:
968             raise ProvisioningError("DLZ option incompatible with BIND 9.7.")
969         else:
970             logger.warning("BIND version unknown, please modify %s manually." % paths.namedconf)
971         setup_file(setup_path("named.conf.dlz"), paths.namedconf, {
972                     "NAMED_CONF": paths.namedconf,
973                     "MODULESDIR" : samba.param.modules_dir(),
974                     "BIND9_8" : bind9_8,
975                     "BIND9_9" : bind9_9,
976                     "BIND9_10" : bind9_10,
977                     "BIND9_11" : bind9_11,
978                     "BIND9_12" : bind9_12
979                     })
980
981
982 def create_named_txt(path, realm, dnsdomain, dnsname, binddns_dir,
983     keytab_name):
984     """Write out a file containing zone statements suitable for inclusion in a
985     named.conf file (including GSS-TSIG configuration).
986
987     :param path: Path of the new named.conf file.
988     :param realm: Realm name
989     :param dnsdomain: DNS Domain name
990     :param binddns_dir: Path to bind dns directory
991     :param keytab_name: File name of DNS keytab file
992     """
993     setup_file(setup_path("named.txt"), path, {
994             "DNSDOMAIN": dnsdomain,
995             "DNSNAME" : dnsname,
996             "REALM": realm,
997             "DNS_KEYTAB": keytab_name,
998             "DNS_KEYTAB_ABS": os.path.join(binddns_dir, keytab_name),
999             "PRIVATE_DIR": binddns_dir
1000         })
1001
1002
1003 def is_valid_dns_backend(dns_backend):
1004     return dns_backend in ("BIND9_FLATFILE", "BIND9_DLZ", "SAMBA_INTERNAL", "NONE")
1005
1006
1007 def is_valid_os_level(os_level):
1008     return DS_DOMAIN_FUNCTION_2000 <= os_level <= DS_DOMAIN_FUNCTION_2016
1009
1010
1011 def create_dns_legacy(samdb, domainsid, forestdn, dnsadmins_sid):
1012     # Set up MicrosoftDNS container
1013     add_dns_container(samdb, forestdn, "CN=System", domainsid, dnsadmins_sid)
1014     # Add root servers
1015     add_rootservers(samdb, forestdn, "CN=System")
1016
1017
1018 def fill_dns_data_legacy(samdb, domainsid, forestdn, dnsdomain, site, hostname,
1019                          hostip, hostip6, dnsadmins_sid):
1020     # Add domain record
1021     add_domain_record(samdb, forestdn, "CN=System", dnsdomain, domainsid,
1022                       dnsadmins_sid)
1023
1024     # Add DNS records for a DC in domain
1025     add_dc_domain_records(samdb, forestdn, "CN=System", site, dnsdomain,
1026                           hostname, hostip, hostip6)
1027
1028
1029 def create_dns_partitions(samdb, domainsid, names, domaindn, forestdn,
1030                           dnsadmins_sid, fill_level):
1031     # Set up additional partitions (DomainDnsZones, ForstDnsZones)
1032     setup_dns_partitions(samdb, domainsid, domaindn, forestdn,
1033                         names.configdn, names.serverdn, fill_level)
1034
1035     # Set up MicrosoftDNS containers
1036     add_dns_container(samdb, domaindn, "DC=DomainDnsZones", domainsid,
1037                       dnsadmins_sid)
1038     if fill_level != FILL_SUBDOMAIN:
1039         add_dns_container(samdb, forestdn, "DC=ForestDnsZones", domainsid,
1040                           dnsadmins_sid, forest=True)
1041
1042
1043 def fill_dns_data_partitions(samdb, domainsid, site, domaindn, forestdn,
1044                              dnsdomain, dnsforest, hostname, hostip, hostip6,
1045                              domainguid, ntdsguid, dnsadmins_sid, autofill=True,
1046                              fill_level=FILL_FULL, add_root=True):
1047     """Fill data in various AD partitions
1048
1049     :param samdb: LDB object connected to sam.ldb file
1050     :param domainsid: Domain SID (as dom_sid object)
1051     :param site: Site name to create hostnames in
1052     :param domaindn: DN of the domain
1053     :param forestdn: DN of the forest
1054     :param dnsdomain: DNS name of the domain
1055     :param dnsforest: DNS name of the forest
1056     :param hostname: Host name of this DC
1057     :param hostip: IPv4 addresses
1058     :param hostip6: IPv6 addresses
1059     :param domainguid: Domain GUID
1060     :param ntdsguid: NTDS GUID
1061     :param dnsadmins_sid: SID for DnsAdmins group
1062     :param autofill: Create DNS records (using fixed template)
1063     """
1064
1065     ##### Set up DC=DomainDnsZones,<DOMAINDN>
1066     # Add rootserver records
1067     if add_root:
1068         add_rootservers(samdb, domaindn, "DC=DomainDnsZones")
1069
1070     # Add domain record
1071     add_domain_record(samdb, domaindn, "DC=DomainDnsZones", dnsdomain,
1072                       domainsid, dnsadmins_sid)
1073
1074     # Add DNS records for a DC in domain
1075     if autofill:
1076         add_dc_domain_records(samdb, domaindn, "DC=DomainDnsZones", site,
1077                               dnsdomain, hostname, hostip, hostip6)
1078
1079     if fill_level != FILL_SUBDOMAIN:
1080         ##### Set up DC=ForestDnsZones,<FORESTDN>
1081         # Add _msdcs record
1082         add_msdcs_record(samdb, forestdn, "DC=ForestDnsZones", dnsforest)
1083
1084         # Add DNS records for a DC in forest
1085         if autofill:
1086             add_dc_msdcs_records(samdb, forestdn, "DC=ForestDnsZones", site,
1087                                  dnsforest, hostname, hostip, hostip6,
1088                                  domainguid, ntdsguid)
1089
1090
1091 def setup_ad_dns(samdb, secretsdb, names, paths, lp, logger,
1092         dns_backend, os_level, dnspass=None, hostip=None, hostip6=None,
1093         targetdir=None, fill_level=FILL_FULL, backend_store=None):
1094     """Provision DNS information (assuming GC role)
1095
1096     :param samdb: LDB object connected to sam.ldb file
1097     :param secretsdb: LDB object connected to secrets.ldb file
1098     :param names: Names shortcut
1099     :param paths: Paths shortcut
1100     :param lp: Loadparm object
1101     :param logger: Logger object
1102     :param dns_backend: Type of DNS backend
1103     :param os_level: Functional level (treated as os level)
1104     :param dnspass: Password for bind's DNS account
1105     :param hostip: IPv4 address
1106     :param hostip6: IPv6 address
1107     :param targetdir: Target directory for creating DNS-related files for BIND9
1108     """
1109
1110     if not is_valid_dns_backend(dns_backend):
1111         raise Exception("Invalid dns backend: %r" % dns_backend)
1112
1113     if not is_valid_os_level(os_level):
1114         raise Exception("Invalid os level: %r" % os_level)
1115
1116     if dns_backend == "NONE":
1117         logger.info("No DNS backend set, not configuring DNS")
1118         return
1119
1120     # Add dns accounts (DnsAdmins, DnsUpdateProxy) in domain
1121     logger.info("Adding DNS accounts")
1122     add_dns_accounts(samdb, names.domaindn)
1123
1124     # If dns_backend is BIND9_FLATFILE
1125     #   Populate only CN=MicrosoftDNS,CN=System,<DOMAINDN>
1126     #
1127     # If dns_backend is SAMBA_INTERNAL or BIND9_DLZ
1128     #   Populate DNS partitions
1129
1130     # If os_level < 2003 (DS_DOMAIN_FUNCTION_2000)
1131     #   All dns records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
1132     #
1133     # If os_level >= 2003 (DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008,
1134     #                        DS_DOMAIN_FUNCTION_2008_R2)
1135     #   Root server records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
1136     #   Domain records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
1137     #   Domain records are in CN=MicrosoftDNS,DC=DomainDnsZones,<DOMAINDN>
1138     #   Forest records are in CN=MicrosoftDNS,DC=ForestDnsZones,<FORESTDN>
1139     domaindn = names.domaindn
1140     forestdn = samdb.get_root_basedn().get_linearized()
1141
1142     dnsdomain = names.dnsdomain.lower()
1143     dnsforest = dnsdomain
1144
1145     site = names.sitename
1146
1147     hostname = names.netbiosname.lower()
1148
1149     dnsadmins_sid = get_dnsadmins_sid(samdb, domaindn)
1150     domainguid = get_domainguid(samdb, domaindn)
1151
1152     samdb.transaction_start()
1153     try:
1154         # Create CN=System
1155         logger.info("Creating CN=MicrosoftDNS,CN=System,%s" % domaindn)
1156         create_dns_legacy(samdb, names.domainsid, domaindn, dnsadmins_sid)
1157
1158         if os_level == DS_DOMAIN_FUNCTION_2000:
1159             # Populating legacy dns
1160             logger.info("Populating CN=MicrosoftDNS,CN=System,%s" % domaindn)
1161             fill_dns_data_legacy(samdb, names.domainsid, domaindn, dnsdomain, site,
1162                                  hostname, hostip, hostip6, dnsadmins_sid)
1163
1164         elif dns_backend in ("SAMBA_INTERNAL", "BIND9_DLZ") and \
1165                 os_level >= DS_DOMAIN_FUNCTION_2003:
1166
1167             # Create DNS partitions
1168             logger.info("Creating DomainDnsZones and ForestDnsZones partitions")
1169             create_dns_partitions(samdb, names.domainsid, names, domaindn, forestdn,
1170                                   dnsadmins_sid, fill_level)
1171
1172             # Populating dns partitions
1173             logger.info("Populating DomainDnsZones and ForestDnsZones partitions")
1174             fill_dns_data_partitions(samdb, names.domainsid, site, domaindn, forestdn,
1175                                      dnsdomain, dnsforest, hostname, hostip, hostip6,
1176                                      domainguid, names.ntdsguid, dnsadmins_sid,
1177                                      fill_level=fill_level)
1178
1179     except:
1180         samdb.transaction_cancel()
1181         raise
1182     else:
1183         samdb.transaction_commit()
1184
1185     if dns_backend.startswith("BIND9_"):
1186         setup_bind9_dns(samdb, secretsdb, names, paths, lp, logger,
1187                         dns_backend, os_level, site=site, dnspass=dnspass, hostip=hostip,
1188                         hostip6=hostip6, targetdir=targetdir,
1189                         backend_store=backend_store)
1190
1191
1192 def setup_bind9_dns(samdb, secretsdb, names, paths, lp, logger,
1193         dns_backend, os_level, site=None, dnspass=None, hostip=None,
1194         hostip6=None, targetdir=None, key_version_number=None,
1195         backend_store=None):
1196     """Provision DNS information (assuming BIND9 backend in DC role)
1197
1198     :param samdb: LDB object connected to sam.ldb file
1199     :param secretsdb: LDB object connected to secrets.ldb file
1200     :param names: Names shortcut
1201     :param paths: Paths shortcut
1202     :param lp: Loadparm object
1203     :param logger: Logger object
1204     :param dns_backend: Type of DNS backend
1205     :param os_level: Functional level (treated as os level)
1206     :param site: Site to create hostnames in
1207     :param dnspass: Password for bind's DNS account
1208     :param hostip: IPv4 address
1209     :param hostip6: IPv6 address
1210     :param targetdir: Target directory for creating DNS-related files for BIND9
1211     """
1212
1213     if (not is_valid_dns_backend(dns_backend) or
1214         not dns_backend.startswith("BIND9_")):
1215         raise Exception("Invalid dns backend: %r" % dns_backend)
1216
1217     if not is_valid_os_level(os_level):
1218         raise Exception("Invalid os level: %r" % os_level)
1219
1220     domaindn = names.domaindn
1221
1222     domainguid = get_domainguid(samdb, domaindn)
1223
1224     secretsdb_setup_dns(secretsdb, names,
1225                         paths.private_dir,
1226                         paths.binddns_dir,
1227                         realm=names.realm,
1228                         dnsdomain=names.dnsdomain,
1229                         dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1230                         key_version_number=key_version_number)
1231
1232     create_dns_dir(logger, paths)
1233
1234     if dns_backend == "BIND9_FLATFILE":
1235         create_zone_file(lp, logger, paths, targetdir, site=site,
1236                          dnsdomain=names.dnsdomain, hostip=hostip,
1237                          hostip6=hostip6, hostname=names.hostname,
1238                          realm=names.realm, domainguid=domainguid,
1239                          ntdsguid=names.ntdsguid)
1240
1241     if dns_backend == "BIND9_DLZ" and os_level >= DS_DOMAIN_FUNCTION_2003:
1242         create_samdb_copy(samdb, logger, paths,
1243                           names, names.domainsid, domainguid)
1244
1245     create_named_conf(paths, realm=names.realm,
1246                       dnsdomain=names.dnsdomain, dns_backend=dns_backend,
1247                       logger=logger)
1248
1249     create_named_txt(paths.namedtxt,
1250                      realm=names.realm, dnsdomain=names.dnsdomain,
1251                      dnsname = "%s.%s" % (names.hostname, names.dnsdomain),
1252                      binddns_dir=paths.binddns_dir,
1253                      keytab_name=paths.dns_keytab)
1254     logger.info("See %s for an example configuration include file for BIND",
1255                 paths.namedconf)
1256     logger.info("and %s for further documentation required for secure DNS "
1257                 "updates", paths.namedtxt)