99372e364f8e8058bd1c75d6b6a0574cc75c2f54
[nivanova/samba-autobuild/.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"] = \
318         ldb.MessageElement(sd_val, ldb.FLAG_MOD_ADD,
319                            "nTSecurityDescriptor")
320     samdb.add(msg)
321
322
323 def add_rootservers(samdb, domaindn, prefix):
324     # https://www.internic.net/zones/named.root
325     rootservers = {}
326     rootservers["a.root-servers.net"] = "198.41.0.4"
327     rootservers["b.root-servers.net"] = "192.228.79.201"
328     rootservers["c.root-servers.net"] = "192.33.4.12"
329     rootservers["d.root-servers.net"] = "199.7.91.13"
330     rootservers["e.root-servers.net"] = "192.203.230.10"
331     rootservers["f.root-servers.net"] = "192.5.5.241"
332     rootservers["g.root-servers.net"] = "192.112.36.4"
333     rootservers["h.root-servers.net"] = "198.97.190.53"
334     rootservers["i.root-servers.net"] = "192.36.148.17"
335     rootservers["j.root-servers.net"] = "192.58.128.30"
336     rootservers["k.root-servers.net"] = "193.0.14.129"
337     rootservers["l.root-servers.net"] = "199.7.83.42"
338     rootservers["m.root-servers.net"] = "202.12.27.33"
339
340     rootservers_v6 = {}
341     rootservers_v6["a.root-servers.net"] = "2001:503:ba3e::2:30"
342     rootservers_v6["b.root-servers.net"] = "2001:500:84::b"
343     rootservers_v6["c.root-servers.net"] = "2001:500:2::c"
344     rootservers_v6["d.root-servers.net"] = "2001:500:2d::d"
345     rootservers_v6["e.root-servers.net"] = "2001:500:a8::e"
346     rootservers_v6["f.root-servers.net"] = "2001:500:2f::f"
347     rootservers_v6["g.root-servers.net"] = "2001:500:12::d0d"
348     rootservers_v6["h.root-servers.net"] = "2001:500:1::53"
349     rootservers_v6["i.root-servers.net"] = "2001:7fe::53"
350     rootservers_v6["j.root-servers.net"] = "2001:503:c27::2:30"
351     rootservers_v6["k.root-servers.net"] = "2001:7fd::1"
352     rootservers_v6["l.root-servers.net"] = "2001:500:9f::42"
353     rootservers_v6["m.root-servers.net"] = "2001:dc3::35"
354
355     container_dn = "DC=RootDNSServers,CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)
356
357     # Add DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
358     msg = ldb.Message(ldb.Dn(samdb, container_dn))
359     props = []
360     props.append(ndr_pack(TypeProperty(zone_type=dnsp.DNS_ZONE_TYPE_CACHE)))
361     props.append(ndr_pack(AllowUpdateProperty(allow_update=dnsp.DNS_ZONE_UPDATE_OFF)))
362     props.append(ndr_pack(SecureTimeProperty()))
363     props.append(ndr_pack(NorefreshIntervalProperty()))
364     props.append(ndr_pack(RefreshIntervalProperty()))
365     props.append(ndr_pack(AgingStateProperty()))
366     props.append(ndr_pack(AgingEnabledTimeProperty()))
367     msg["objectClass"] = ["top", "dnsZone"]
368     msg["cn"] = ldb.MessageElement("Zone", ldb.FLAG_MOD_ADD, "cn")
369     msg["dNSProperty"] = ldb.MessageElement(props, ldb.FLAG_MOD_ADD, "dNSProperty")
370     samdb.add(msg)
371
372     # Add DC=@,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
373     record = []
374     for rserver in rootservers:
375         record.append(ndr_pack(NSRecord(rserver, serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT)))
376
377     msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
378     msg["objectClass"] = ["top", "dnsNode"]
379     msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
380     samdb.add(msg)
381
382     # Add DC=<rootserver>,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
383     for rserver in rootservers:
384         record = [ndr_pack(ARecord(rootservers[rserver], serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT))]
385         # Add AAAA record as well (How does W2K* add IPv6 records?)
386         #if rserver in rootservers_v6:
387         #    record.append(ndr_pack(AAAARecord(rootservers_v6[rserver], serial=0, ttl=0)))
388         msg = ldb.Message(ldb.Dn(samdb, "DC=%s,%s" % (rserver, container_dn)))
389         msg["objectClass"] = ["top", "dnsNode"]
390         msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
391         samdb.add(msg)
392
393 def add_at_record(samdb, container_dn, prefix, hostname, dnsdomain, hostip, hostip6):
394
395     fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
396
397     at_records = []
398
399     # SOA record
400     at_soa_record = SOARecord(fqdn_hostname, "hostmaster.%s" % dnsdomain)
401     at_records.append(ndr_pack(at_soa_record))
402
403     # NS record
404     at_ns_record = NSRecord(fqdn_hostname)
405     at_records.append(ndr_pack(at_ns_record))
406
407     if hostip is not None:
408         # A record
409         at_a_record = ARecord(hostip)
410         at_records.append(ndr_pack(at_a_record))
411
412     if hostip6 is not None:
413         # AAAA record
414         at_aaaa_record = AAAARecord(hostip6)
415         at_records.append(ndr_pack(at_aaaa_record))
416
417     msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
418     msg["objectClass"] = ["top", "dnsNode"]
419     msg["dnsRecord"] = ldb.MessageElement(at_records, ldb.FLAG_MOD_ADD, "dnsRecord")
420     samdb.add(msg)
421
422
423 def add_srv_record(samdb, container_dn, prefix, host, port):
424     srv_record = SRVRecord(host, port)
425     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
426     msg["objectClass"] = ["top", "dnsNode"]
427     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(srv_record), ldb.FLAG_MOD_ADD, "dnsRecord")
428     samdb.add(msg)
429
430
431 def add_ns_record(samdb, container_dn, prefix, host):
432     ns_record = NSRecord(host)
433     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
434     msg["objectClass"] = ["top", "dnsNode"]
435     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
436     samdb.add(msg)
437
438
439 def add_ns_glue_record(samdb, container_dn, prefix, host):
440     ns_record = NSRecord(host, rank=dnsp.DNS_RANK_NS_GLUE)
441     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
442     msg["objectClass"] = ["top", "dnsNode"]
443     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
444     samdb.add(msg)
445
446
447 def add_cname_record(samdb, container_dn, prefix, host):
448     cname_record = CNameRecord(host)
449     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
450     msg["objectClass"] = ["top", "dnsNode"]
451     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(cname_record), ldb.FLAG_MOD_ADD, "dnsRecord")
452     samdb.add(msg)
453
454
455 def add_host_record(samdb, container_dn, prefix, hostip, hostip6):
456     host_records = []
457     if hostip:
458         a_record = ARecord(hostip)
459         host_records.append(ndr_pack(a_record))
460     if hostip6:
461         aaaa_record = AAAARecord(hostip6)
462         host_records.append(ndr_pack(aaaa_record))
463     if host_records:
464         msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
465         msg["objectClass"] = ["top", "dnsNode"]
466         msg["dnsRecord"] = ldb.MessageElement(host_records, ldb.FLAG_MOD_ADD, "dnsRecord")
467         samdb.add(msg)
468
469
470 def add_domain_record(samdb, domaindn, prefix, dnsdomain, domainsid, dnsadmins_sid):
471     # DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
472     sddl = "O:SYG:BAD:AI" \
473         "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)" \
474         "(A;;CC;;;AU)" \
475         "(A;;RPLCLORC;;;WD)" \
476         "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
477         "(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
478         "(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;%s)" \
479         "(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
480         "(OA;CIID;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
481         "(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
482         "(A;CIID;LC;;;RU)" \
483         "(A;CIID;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
484         "S:AI" % dnsadmins_sid
485     sec = security.descriptor.from_sddl(sddl, domainsid)
486     props = []
487     props.append(ndr_pack(TypeProperty()))
488     props.append(ndr_pack(AllowUpdateProperty()))
489     props.append(ndr_pack(SecureTimeProperty()))
490     props.append(ndr_pack(NorefreshIntervalProperty(norefresh_interval=168)))
491     props.append(ndr_pack(RefreshIntervalProperty(refresh_interval=168)))
492     props.append(ndr_pack(AgingStateProperty()))
493     props.append(ndr_pack(AgingEnabledTimeProperty()))
494     msg = ldb.Message(ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" % (dnsdomain, prefix, domaindn)))
495     msg["objectClass"] = ["top", "dnsZone"]
496     msg["ntSecurityDescriptor"] = \
497         ldb.MessageElement(ndr_pack(sec),
498                            ldb.FLAG_MOD_ADD,
499                            "nTSecurityDescriptor")
500     msg["dNSProperty"] = ldb.MessageElement(props, ldb.FLAG_MOD_ADD, "dNSProperty")
501     samdb.add(msg)
502
503
504 def add_msdcs_record(samdb, forestdn, prefix, dnsforest):
505     # DC=_msdcs.<DNSFOREST>,CN=MicrosoftDNS,<PREFIX>,<FORESTDN>
506     msg = ldb.Message(ldb.Dn(samdb, "DC=_msdcs.%s,CN=MicrosoftDNS,%s,%s" %
507                                     (dnsforest, prefix, forestdn)))
508     msg["objectClass"] = ["top", "dnsZone"]
509     samdb.add(msg)
510
511
512 def add_dc_domain_records(samdb, domaindn, prefix, site, dnsdomain, hostname,
513                           hostip, hostip6):
514
515     fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
516
517     # Set up domain container - DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
518     domain_container_dn = ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" %
519                                  (dnsdomain, prefix, domaindn))
520
521     # DC=@ record
522     add_at_record(samdb, domain_container_dn, "DC=@", hostname, dnsdomain,
523                   hostip, hostip6)
524
525     # DC=<HOSTNAME> record
526     add_host_record(samdb, domain_container_dn, "DC=%s" % hostname, hostip,
527                     hostip6)
528
529     # DC=_kerberos._tcp record
530     add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp",
531                    fqdn_hostname, 88)
532
533     # DC=_kerberos._tcp.<SITENAME>._sites record
534     add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp.%s._sites" %
535                    site, fqdn_hostname, 88)
536
537     # DC=_kerberos._udp record
538     add_srv_record(samdb, domain_container_dn, "DC=_kerberos._udp",
539                    fqdn_hostname, 88)
540
541     # DC=_kpasswd._tcp record
542     add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._tcp",
543                    fqdn_hostname, 464)
544
545     # DC=_kpasswd._udp record
546     add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._udp",
547                    fqdn_hostname, 464)
548
549     # DC=_ldap._tcp record
550     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp", fqdn_hostname,
551                    389)
552
553     # DC=_ldap._tcp.<SITENAME>._sites record
554     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites" %
555                    site, fqdn_hostname, 389)
556
557     # FIXME: The number of SRV records depend on the various roles this DC has.
558     #        _gc and _msdcs records are added if the we are the forest dc and not subdomain dc
559     #
560     # Assumption: current DC is GC and add all the entries
561
562     # DC=_gc._tcp record
563     add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp", fqdn_hostname,
564                    3268)
565
566     # DC=_gc._tcp.<SITENAME>,_sites record
567     add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp.%s._sites" % site,
568                    fqdn_hostname, 3268)
569
570     # DC=_msdcs record
571     add_ns_glue_record(samdb, domain_container_dn, "DC=_msdcs", fqdn_hostname)
572
573     # FIXME: Following entries are added only if DomainDnsZones and ForestDnsZones partitions
574     #        are created
575     #
576     # Assumption: Additional entries won't hurt on os_level = 2000
577
578     # DC=_ldap._tcp.<SITENAME>._sites.DomainDnsZones
579     add_srv_record(samdb, domain_container_dn,
580                    "DC=_ldap._tcp.%s._sites.DomainDnsZones" % site, fqdn_hostname,
581                    389)
582
583     # DC=_ldap._tcp.<SITENAME>._sites.ForestDnsZones
584     add_srv_record(samdb, domain_container_dn,
585                    "DC=_ldap._tcp.%s._sites.ForestDnsZones" % site, fqdn_hostname,
586                    389)
587
588     # DC=_ldap._tcp.DomainDnsZones
589     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.DomainDnsZones",
590                    fqdn_hostname, 389)
591
592     # DC=_ldap._tcp.ForestDnsZones
593     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.ForestDnsZones",
594                    fqdn_hostname, 389)
595
596     # DC=DomainDnsZones
597     add_host_record(samdb, domain_container_dn, "DC=DomainDnsZones", hostip,
598                     hostip6)
599
600     # DC=ForestDnsZones
601     add_host_record(samdb, domain_container_dn, "DC=ForestDnsZones", hostip,
602                     hostip6)
603
604
605 def add_dc_msdcs_records(samdb, forestdn, prefix, site, dnsforest, hostname,
606                          hostip, hostip6, domainguid, ntdsguid):
607
608     fqdn_hostname = "%s.%s" % (hostname, dnsforest)
609
610     # Set up forest container - DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
611     forest_container_dn = ldb.Dn(samdb, "DC=_msdcs.%s,CN=MicrosoftDNS,%s,%s" %
612                                  (dnsforest, prefix, forestdn))
613
614     # DC=@ record
615     add_at_record(samdb, forest_container_dn, "DC=@", hostname, dnsforest,
616                   None, None)
617
618     # DC=_kerberos._tcp.dc record
619     add_srv_record(samdb, forest_container_dn, "DC=_kerberos._tcp.dc",
620                    fqdn_hostname, 88)
621
622     # DC=_kerberos._tcp.<SITENAME>._sites.dc record
623     add_srv_record(samdb, forest_container_dn,
624                    "DC=_kerberos._tcp.%s._sites.dc" % site, fqdn_hostname, 88)
625
626     # DC=_ldap._tcp.dc record
627     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.dc",
628                    fqdn_hostname, 389)
629
630     # DC=_ldap._tcp.<SITENAME>._sites.dc record
631     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.dc" %
632                    site, fqdn_hostname, 389)
633
634     # DC=_ldap._tcp.<SITENAME>._sites.gc record
635     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.gc" %
636                    site, fqdn_hostname, 3268)
637
638     # DC=_ldap._tcp.gc record
639     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.gc",
640                    fqdn_hostname, 3268)
641
642     # DC=_ldap._tcp.pdc record
643     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.pdc",
644                    fqdn_hostname, 389)
645
646     # DC=gc record
647     add_host_record(samdb, forest_container_dn, "DC=gc", hostip, hostip6)
648
649     # DC=_ldap._tcp.<DOMAINGUID>.domains record
650     add_srv_record(samdb, forest_container_dn,
651                    "DC=_ldap._tcp.%s.domains" % domainguid, fqdn_hostname, 389)
652
653     # DC=<NTDSGUID>
654     add_cname_record(samdb, forest_container_dn, "DC=%s" % ntdsguid,
655                      fqdn_hostname)
656
657
658 def secretsdb_setup_dns(secretsdb, names, private_dir, binddns_dir, realm,
659                         dnsdomain, dns_keytab_path, dnspass, key_version_number):
660     """Add DNS specific bits to a secrets database.
661
662     :param secretsdb: Ldb Handle to the secrets database
663     :param names: Names shortcut
664     :param machinepass: Machine password
665     """
666     try:
667         os.unlink(os.path.join(private_dir, dns_keytab_path))
668         os.unlink(os.path.join(binddns_dir, dns_keytab_path))
669     except OSError:
670         pass
671
672     if key_version_number is None:
673         key_version_number = 1
674
675     # This will create the dns.keytab file in the private_dir when it is
676     # commited!
677     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
678             "REALM": realm,
679             "DNSDOMAIN": dnsdomain,
680             "DNS_KEYTAB": dns_keytab_path,
681             "DNSPASS_B64": b64encode(dnspass.encode('utf-8')).decode('utf8'),
682             "KEY_VERSION_NUMBER": str(key_version_number),
683             "HOSTNAME": names.hostname,
684             "DNSNAME" : '%s.%s' % (
685                 names.netbiosname.lower(), names.dnsdomain.lower())
686             })
687
688
689 def create_dns_dir(logger, paths):
690     """Write out a DNS zone file, from the info in the current database.
691
692     :param logger: Logger object
693     :param paths: paths object
694     """
695     dns_dir = os.path.dirname(paths.dns)
696
697     try:
698         shutil.rmtree(dns_dir, True)
699     except OSError:
700         pass
701
702     os.mkdir(dns_dir, 0o770)
703
704     if paths.bind_gid is not None:
705         try:
706             os.chown(dns_dir, -1, paths.bind_gid)
707             # chmod needed to cope with umask
708             os.chmod(dns_dir, 0o770)
709         except OSError:
710             if 'SAMBA_SELFTEST' not in os.environ:
711                 logger.error("Failed to chown %s to bind gid %u" % (
712                     dns_dir, paths.bind_gid))
713
714
715 def create_zone_file(lp, logger, paths, targetdir, dnsdomain,
716                      hostip, hostip6, hostname, realm, domainguid,
717                      ntdsguid, site):
718     """Write out a DNS zone file, from the info in the current database.
719
720     :param paths: paths object
721     :param dnsdomain: DNS Domain name
722     :param domaindn: DN of the Domain
723     :param hostip: Local IPv4 IP
724     :param hostip6: Local IPv6 IP
725     :param hostname: Local hostname
726     :param realm: Realm name
727     :param domainguid: GUID of the domain.
728     :param ntdsguid: GUID of the hosts nTDSDSA record.
729     """
730     assert isinstance(domainguid, str)
731
732     if hostip6 is not None:
733         hostip6_base_line = "            IN AAAA    " + hostip6
734         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
735         gc_msdcs_ip6_line = "gc._msdcs               IN AAAA    " + hostip6
736     else:
737         hostip6_base_line = ""
738         hostip6_host_line = ""
739         gc_msdcs_ip6_line = ""
740
741     if hostip is not None:
742         hostip_base_line = "            IN A    " + hostip
743         hostip_host_line = hostname + "        IN A    " + hostip
744         gc_msdcs_ip_line = "gc._msdcs               IN A    " + hostip
745     else:
746         hostip_base_line = ""
747         hostip_host_line = ""
748         gc_msdcs_ip_line = ""
749
750     # we need to freeze the zone while we update the contents
751     if targetdir is None:
752         rndc = ' '.join(lp.get("rndc command"))
753         os.system(rndc + " freeze " + lp.get("realm"))
754
755     setup_file(setup_path("provision.zone"), paths.dns, {
756             "HOSTNAME": hostname,
757             "DNSDOMAIN": dnsdomain,
758             "REALM": realm,
759             "HOSTIP_BASE_LINE": hostip_base_line,
760             "HOSTIP_HOST_LINE": hostip_host_line,
761             "DOMAINGUID": domainguid,
762             "DATESTRING": time.strftime("%Y%m%d%H"),
763             "DEFAULTSITE": site,
764             "NTDSGUID": ntdsguid,
765             "HOSTIP6_BASE_LINE": hostip6_base_line,
766             "HOSTIP6_HOST_LINE": hostip6_host_line,
767             "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
768             "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
769     })
770
771     if paths.bind_gid is not None:
772         try:
773             os.chown(paths.dns, -1, paths.bind_gid)
774             # chmod needed to cope with umask
775             os.chmod(paths.dns, 0o664)
776         except OSError:
777             if 'SAMBA_SELFTEST' not in os.environ:
778                 logger.error("Failed to chown %s to bind gid %u" % (
779                     paths.dns, paths.bind_gid))
780
781     if targetdir is None:
782         os.system(rndc + " unfreeze " + lp.get("realm"))
783
784
785 def create_samdb_copy(samdb, logger, paths, names, domainsid, domainguid):
786     """Create a copy of samdb and give write permissions to named for dns partitions
787     """
788     private_dir = paths.private_dir
789     samldb_dir = os.path.join(private_dir, "sam.ldb.d")
790     dns_dir = os.path.dirname(paths.dns)
791     dns_samldb_dir = os.path.join(dns_dir, "sam.ldb.d")
792
793     # Find the partitions and corresponding filenames
794     partfile = {}
795     res = samdb.search(base="@PARTITION",
796                        scope=ldb.SCOPE_BASE,
797                        attrs=["partition", "backendStore"])
798     for tmp in res[0]["partition"]:
799         (nc, fname) = tmp.split(':')
800         partfile[nc.upper()] = fname
801
802     backend_store = get_default_backend_store()
803     if "backendStore" in res[0]:
804         backend_store = res[0]["backendStore"][0]
805
806     # Create empty domain partition
807
808     domaindn = names.domaindn.upper()
809     domainpart_file = os.path.join(dns_dir, partfile[domaindn])
810     try:
811         os.mkdir(dns_samldb_dir)
812         open(domainpart_file, 'w').close()
813
814         # Fill the basedn and @OPTION records in domain partition
815         dom_url = "%s://%s" % (backend_store, domainpart_file)
816         dom_ldb = samba.Ldb(dom_url)
817
818         # We need the dummy main-domain DB to have the correct @INDEXLIST
819         index_res = samdb.search(base="@INDEXLIST", scope=ldb.SCOPE_BASE)
820         dom_ldb.add(index_res[0])
821
822         domainguid_line = "objectGUID: %s\n-" % domainguid
823         descr = b64encode(get_domain_descriptor(domainsid)).decode('utf8')
824         setup_add_ldif(dom_ldb, setup_path("provision_basedn.ldif"), {
825             "DOMAINDN" : names.domaindn,
826             "DOMAINGUID" : domainguid_line,
827             "DOMAINSID" : str(domainsid),
828             "DESCRIPTOR" : descr})
829         setup_add_ldif(dom_ldb,
830                        setup_path("provision_basedn_options.ldif"), None)
831
832     except:
833         logger.error(
834             "Failed to setup database for BIND, AD based DNS cannot be used")
835         raise
836
837     # This line is critical to the security of the whole scheme.
838     # We assume there is no secret data in the (to be left out of
839     # date and essentially read-only) config, schema and metadata partitions.
840     #
841     # Only the stub of the domain partition is created above.
842     #
843     # That way, things like the krbtgt key do not leak.
844     del partfile[domaindn]
845
846     # Link dns partitions and metadata
847     domainzonedn = "DC=DOMAINDNSZONES,%s" % names.domaindn.upper()
848     forestzonedn = "DC=FORESTDNSZONES,%s" % names.rootdn.upper()
849
850     domainzone_file = partfile[domainzonedn]
851     forestzone_file = partfile.get(forestzonedn)
852
853     metadata_file = "metadata.tdb"
854     try:
855         os.link(os.path.join(samldb_dir, metadata_file),
856                 os.path.join(dns_samldb_dir, metadata_file))
857         os.link(os.path.join(private_dir, domainzone_file),
858                 os.path.join(dns_dir, domainzone_file))
859         if forestzone_file:
860             os.link(os.path.join(private_dir, forestzone_file),
861                     os.path.join(dns_dir, forestzone_file))
862     except OSError:
863         logger.error(
864             "Failed to setup database for BIND, AD based DNS cannot be used")
865         raise
866     del partfile[domainzonedn]
867     if forestzone_file:
868         del partfile[forestzonedn]
869
870     # Copy root, config, schema partitions (and any other if any)
871     # Since samdb is open in the current process, copy them in a child process
872     try:
873         tdb_copy(os.path.join(private_dir, "sam.ldb"),
874                  os.path.join(dns_dir, "sam.ldb"))
875         for nc in partfile:
876             pfile = partfile[nc]
877             if backend_store == "mdb":
878                 mdb_copy(os.path.join(private_dir, pfile),
879                          os.path.join(dns_dir, pfile))
880             else:
881                 tdb_copy(os.path.join(private_dir, pfile),
882                          os.path.join(dns_dir, pfile))
883     except:
884         logger.error(
885             "Failed to setup database for BIND, AD based DNS cannot be used")
886         raise
887
888     # Give bind read/write permissions dns partitions
889     if paths.bind_gid is not None:
890         try:
891             for dirname, dirs, files in os.walk(dns_dir):
892                 for d in dirs:
893                     dpath = os.path.join(dirname, d)
894                     os.chown(dpath, -1, paths.bind_gid)
895                     os.chmod(dpath, 0o770)
896                 for f in files:
897                     if f.endswith(('.ldb', '.tdb', 'ldb-lock')):
898                         fpath = os.path.join(dirname, f)
899                         os.chown(fpath, -1, paths.bind_gid)
900                         os.chmod(fpath, 0o660)
901         except OSError:
902             if 'SAMBA_SELFTEST' not in os.environ:
903                 logger.error(
904                     "Failed to set permissions to sam.ldb* files, fix manually")
905     else:
906         if 'SAMBA_SELFTEST' not in os.environ:
907             logger.warning("""Unable to find group id for BIND,
908                 set permissions to sam.ldb* files manually""")
909
910
911 def create_dns_update_list(lp, logger, paths):
912     """Write out a dns_update_list file"""
913     # note that we use no variable substitution on this file
914     # the substitution is done at runtime by samba_dnsupdate, samba_spnupdate
915     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
916     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
917
918
919 def create_named_conf(paths, realm, dnsdomain, dns_backend, logger):
920     """Write out a file containing zone statements suitable for inclusion in a
921     named.conf file (including GSS-TSIG configuration).
922
923     :param paths: all paths
924     :param realm: Realm name
925     :param dnsdomain: DNS Domain name
926     :param dns_backend: DNS backend type
927     :param keytab_name: File name of DNS keytab file
928     :param logger: Logger object
929     """
930
931     # TODO: This really should have been done as a top level import.
932     # It is done here to avoid a depencency loop.  That is, we move
933     # ProvisioningError to another file, and have all the provision
934     # scripts import it from there.
935
936     from samba.provision import ProvisioningError
937
938     if dns_backend == "BIND9_FLATFILE":
939         setup_file(setup_path("named.conf"), paths.namedconf, {
940                     "DNSDOMAIN": dnsdomain,
941                     "REALM": realm,
942                     "ZONE_FILE": paths.dns,
943                     "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
944                     "NAMED_CONF": paths.namedconf,
945                     "NAMED_CONF_UPDATE": paths.namedconf_update
946                     })
947
948         setup_file(setup_path("named.conf.update"), paths.namedconf_update)
949
950     elif dns_backend == "BIND9_DLZ":
951         bind_info = subprocess.Popen(['named -V'], shell=True,
952                                      stdout=subprocess.PIPE,
953                                      stderr=subprocess.STDOUT,
954                                      cwd='.').communicate()[0]
955         bind9_8 = '#'
956         bind9_9 = '#'
957         bind9_10 = '#'
958         bind9_11 = '#'
959         bind9_12 = '#'
960         if bind_info.upper().find('BIND 9.8') != -1:
961             bind9_8 = ''
962         elif bind_info.upper().find('BIND 9.9') != -1:
963             bind9_9 = ''
964         elif bind_info.upper().find('BIND 9.10') != -1:
965             bind9_10 = ''
966         elif bind_info.upper().find('BIND 9.11') != -1:
967             bind9_11 = ''
968         elif bind_info.upper().find('BIND 9.12') != -1:
969             bind9_12 = ''
970         elif bind_info.upper().find('BIND 9.7') != -1:
971             raise ProvisioningError("DLZ option incompatible with BIND 9.7.")
972         else:
973             logger.warning("BIND version unknown, please modify %s manually." % paths.namedconf)
974         setup_file(setup_path("named.conf.dlz"), paths.namedconf, {
975                     "NAMED_CONF": paths.namedconf,
976                     "MODULESDIR" : samba.param.modules_dir(),
977                     "BIND9_8" : bind9_8,
978                     "BIND9_9" : bind9_9,
979                     "BIND9_10" : bind9_10,
980                     "BIND9_11" : bind9_11,
981                     "BIND9_12" : bind9_12
982                     })
983
984
985 def create_named_txt(path, realm, dnsdomain, dnsname, binddns_dir,
986                      keytab_name):
987     """Write out a file containing zone statements suitable for inclusion in a
988     named.conf file (including GSS-TSIG configuration).
989
990     :param path: Path of the new named.conf file.
991     :param realm: Realm name
992     :param dnsdomain: DNS Domain name
993     :param binddns_dir: Path to bind dns directory
994     :param keytab_name: File name of DNS keytab file
995     """
996     setup_file(setup_path("named.txt"), path, {
997             "DNSDOMAIN": dnsdomain,
998             "DNSNAME" : dnsname,
999             "REALM": realm,
1000             "DNS_KEYTAB": keytab_name,
1001             "DNS_KEYTAB_ABS": os.path.join(binddns_dir, keytab_name),
1002             "PRIVATE_DIR": binddns_dir
1003     })
1004
1005
1006 def is_valid_dns_backend(dns_backend):
1007     return dns_backend in ("BIND9_FLATFILE", "BIND9_DLZ", "SAMBA_INTERNAL", "NONE")
1008
1009
1010 def is_valid_os_level(os_level):
1011     return DS_DOMAIN_FUNCTION_2000 <= os_level <= DS_DOMAIN_FUNCTION_2016
1012
1013
1014 def create_dns_legacy(samdb, domainsid, forestdn, dnsadmins_sid):
1015     # Set up MicrosoftDNS container
1016     add_dns_container(samdb, forestdn, "CN=System", domainsid, dnsadmins_sid)
1017     # Add root servers
1018     add_rootservers(samdb, forestdn, "CN=System")
1019
1020
1021 def fill_dns_data_legacy(samdb, domainsid, forestdn, dnsdomain, site, hostname,
1022                          hostip, hostip6, dnsadmins_sid):
1023     # Add domain record
1024     add_domain_record(samdb, forestdn, "CN=System", dnsdomain, domainsid,
1025                       dnsadmins_sid)
1026
1027     # Add DNS records for a DC in domain
1028     add_dc_domain_records(samdb, forestdn, "CN=System", site, dnsdomain,
1029                           hostname, hostip, hostip6)
1030
1031
1032 def create_dns_partitions(samdb, domainsid, names, domaindn, forestdn,
1033                           dnsadmins_sid, fill_level):
1034     # Set up additional partitions (DomainDnsZones, ForstDnsZones)
1035     setup_dns_partitions(samdb, domainsid, domaindn, forestdn,
1036                          names.configdn, names.serverdn, fill_level)
1037
1038     # Set up MicrosoftDNS containers
1039     add_dns_container(samdb, domaindn, "DC=DomainDnsZones", domainsid,
1040                       dnsadmins_sid)
1041     if fill_level != FILL_SUBDOMAIN:
1042         add_dns_container(samdb, forestdn, "DC=ForestDnsZones", domainsid,
1043                           dnsadmins_sid, forest=True)
1044
1045
1046 def fill_dns_data_partitions(samdb, domainsid, site, domaindn, forestdn,
1047                              dnsdomain, dnsforest, hostname, hostip, hostip6,
1048                              domainguid, ntdsguid, dnsadmins_sid, autofill=True,
1049                              fill_level=FILL_FULL, add_root=True):
1050     """Fill data in various AD partitions
1051
1052     :param samdb: LDB object connected to sam.ldb file
1053     :param domainsid: Domain SID (as dom_sid object)
1054     :param site: Site name to create hostnames in
1055     :param domaindn: DN of the domain
1056     :param forestdn: DN of the forest
1057     :param dnsdomain: DNS name of the domain
1058     :param dnsforest: DNS name of the forest
1059     :param hostname: Host name of this DC
1060     :param hostip: IPv4 addresses
1061     :param hostip6: IPv6 addresses
1062     :param domainguid: Domain GUID
1063     :param ntdsguid: NTDS GUID
1064     :param dnsadmins_sid: SID for DnsAdmins group
1065     :param autofill: Create DNS records (using fixed template)
1066     """
1067
1068     ##### Set up DC=DomainDnsZones,<DOMAINDN>
1069     # Add rootserver records
1070     if add_root:
1071         add_rootservers(samdb, domaindn, "DC=DomainDnsZones")
1072
1073     # Add domain record
1074     add_domain_record(samdb, domaindn, "DC=DomainDnsZones", dnsdomain,
1075                       domainsid, dnsadmins_sid)
1076
1077     # Add DNS records for a DC in domain
1078     if autofill:
1079         add_dc_domain_records(samdb, domaindn, "DC=DomainDnsZones", site,
1080                               dnsdomain, hostname, hostip, hostip6)
1081
1082     if fill_level != FILL_SUBDOMAIN:
1083         ##### Set up DC=ForestDnsZones,<FORESTDN>
1084         # Add _msdcs record
1085         add_msdcs_record(samdb, forestdn, "DC=ForestDnsZones", dnsforest)
1086
1087         # Add DNS records for a DC in forest
1088         if autofill:
1089             add_dc_msdcs_records(samdb, forestdn, "DC=ForestDnsZones", site,
1090                                  dnsforest, hostname, hostip, hostip6,
1091                                  domainguid, ntdsguid)
1092
1093
1094 def setup_ad_dns(samdb, secretsdb, names, paths, lp, logger,
1095                  dns_backend, os_level, dnspass=None, hostip=None, hostip6=None,
1096                  targetdir=None, fill_level=FILL_FULL, backend_store=None):
1097     """Provision DNS information (assuming GC role)
1098
1099     :param samdb: LDB object connected to sam.ldb file
1100     :param secretsdb: LDB object connected to secrets.ldb file
1101     :param names: Names shortcut
1102     :param paths: Paths shortcut
1103     :param lp: Loadparm object
1104     :param logger: Logger object
1105     :param dns_backend: Type of DNS backend
1106     :param os_level: Functional level (treated as os level)
1107     :param dnspass: Password for bind's DNS account
1108     :param hostip: IPv4 address
1109     :param hostip6: IPv6 address
1110     :param targetdir: Target directory for creating DNS-related files for BIND9
1111     """
1112
1113     if not is_valid_dns_backend(dns_backend):
1114         raise Exception("Invalid dns backend: %r" % dns_backend)
1115
1116     if not is_valid_os_level(os_level):
1117         raise Exception("Invalid os level: %r" % os_level)
1118
1119     if dns_backend == "NONE":
1120         logger.info("No DNS backend set, not configuring DNS")
1121         return
1122
1123     # Add dns accounts (DnsAdmins, DnsUpdateProxy) in domain
1124     logger.info("Adding DNS accounts")
1125     add_dns_accounts(samdb, names.domaindn)
1126
1127     # If dns_backend is BIND9_FLATFILE
1128     #   Populate only CN=MicrosoftDNS,CN=System,<DOMAINDN>
1129     #
1130     # If dns_backend is SAMBA_INTERNAL or BIND9_DLZ
1131     #   Populate DNS partitions
1132
1133     # If os_level < 2003 (DS_DOMAIN_FUNCTION_2000)
1134     #   All dns records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
1135     #
1136     # If os_level >= 2003 (DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008,
1137     #                        DS_DOMAIN_FUNCTION_2008_R2)
1138     #   Root server records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
1139     #   Domain records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
1140     #   Domain records are in CN=MicrosoftDNS,DC=DomainDnsZones,<DOMAINDN>
1141     #   Forest records are in CN=MicrosoftDNS,DC=ForestDnsZones,<FORESTDN>
1142     domaindn = names.domaindn
1143     forestdn = samdb.get_root_basedn().get_linearized()
1144
1145     dnsdomain = names.dnsdomain.lower()
1146     dnsforest = dnsdomain
1147
1148     site = names.sitename
1149
1150     hostname = names.netbiosname.lower()
1151
1152     dnsadmins_sid = get_dnsadmins_sid(samdb, domaindn)
1153     domainguid = get_domainguid(samdb, domaindn)
1154
1155     samdb.transaction_start()
1156     try:
1157         # Create CN=System
1158         logger.info("Creating CN=MicrosoftDNS,CN=System,%s" % domaindn)
1159         create_dns_legacy(samdb, names.domainsid, domaindn, dnsadmins_sid)
1160
1161         if os_level == DS_DOMAIN_FUNCTION_2000:
1162             # Populating legacy dns
1163             logger.info("Populating CN=MicrosoftDNS,CN=System,%s" % domaindn)
1164             fill_dns_data_legacy(samdb, names.domainsid, domaindn, dnsdomain, site,
1165                                  hostname, hostip, hostip6, dnsadmins_sid)
1166
1167         elif dns_backend in ("SAMBA_INTERNAL", "BIND9_DLZ") and \
1168                 os_level >= DS_DOMAIN_FUNCTION_2003:
1169
1170             # Create DNS partitions
1171             logger.info("Creating DomainDnsZones and ForestDnsZones partitions")
1172             create_dns_partitions(samdb, names.domainsid, names, domaindn, forestdn,
1173                                   dnsadmins_sid, fill_level)
1174
1175             # Populating dns partitions
1176             logger.info("Populating DomainDnsZones and ForestDnsZones partitions")
1177             fill_dns_data_partitions(samdb, names.domainsid, site, domaindn, forestdn,
1178                                      dnsdomain, dnsforest, hostname, hostip, hostip6,
1179                                      domainguid, names.ntdsguid, dnsadmins_sid,
1180                                      fill_level=fill_level)
1181
1182     except:
1183         samdb.transaction_cancel()
1184         raise
1185     else:
1186         samdb.transaction_commit()
1187
1188     if dns_backend.startswith("BIND9_"):
1189         setup_bind9_dns(samdb, secretsdb, names, paths, lp, logger,
1190                         dns_backend, os_level, site=site, dnspass=dnspass, hostip=hostip,
1191                         hostip6=hostip6, targetdir=targetdir,
1192                         backend_store=backend_store)
1193
1194
1195 def setup_bind9_dns(samdb, secretsdb, names, paths, lp, logger,
1196                     dns_backend, os_level, site=None, dnspass=None, hostip=None,
1197                     hostip6=None, targetdir=None, key_version_number=None,
1198                     backend_store=None):
1199     """Provision DNS information (assuming BIND9 backend in DC role)
1200
1201     :param samdb: LDB object connected to sam.ldb file
1202     :param secretsdb: LDB object connected to secrets.ldb file
1203     :param names: Names shortcut
1204     :param paths: Paths shortcut
1205     :param lp: Loadparm object
1206     :param logger: Logger object
1207     :param dns_backend: Type of DNS backend
1208     :param os_level: Functional level (treated as os level)
1209     :param site: Site to create hostnames in
1210     :param dnspass: Password for bind's DNS account
1211     :param hostip: IPv4 address
1212     :param hostip6: IPv6 address
1213     :param targetdir: Target directory for creating DNS-related files for BIND9
1214     """
1215
1216     if (not is_valid_dns_backend(dns_backend) or
1217         not dns_backend.startswith("BIND9_")):
1218         raise Exception("Invalid dns backend: %r" % dns_backend)
1219
1220     if not is_valid_os_level(os_level):
1221         raise Exception("Invalid os level: %r" % os_level)
1222
1223     domaindn = names.domaindn
1224
1225     domainguid = get_domainguid(samdb, domaindn)
1226
1227     secretsdb_setup_dns(secretsdb, names,
1228                         paths.private_dir,
1229                         paths.binddns_dir,
1230                         realm=names.realm,
1231                         dnsdomain=names.dnsdomain,
1232                         dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1233                         key_version_number=key_version_number)
1234
1235     create_dns_dir(logger, paths)
1236
1237     if dns_backend == "BIND9_FLATFILE":
1238         create_zone_file(lp, logger, paths, targetdir, site=site,
1239                          dnsdomain=names.dnsdomain, hostip=hostip,
1240                          hostip6=hostip6, hostname=names.hostname,
1241                          realm=names.realm, domainguid=domainguid,
1242                          ntdsguid=names.ntdsguid)
1243
1244     if dns_backend == "BIND9_DLZ" and os_level >= DS_DOMAIN_FUNCTION_2003:
1245         create_samdb_copy(samdb, logger, paths,
1246                           names, names.domainsid, domainguid)
1247
1248     create_named_conf(paths, realm=names.realm,
1249                       dnsdomain=names.dnsdomain, dns_backend=dns_backend,
1250                       logger=logger)
1251
1252     create_named_txt(paths.namedtxt,
1253                      realm=names.realm, dnsdomain=names.dnsdomain,
1254                      dnsname = "%s.%s" % (names.hostname, names.dnsdomain),
1255                      binddns_dir=paths.binddns_dir,
1256                      keytab_name=paths.dns_keytab)
1257     logger.info("See %s for an example configuration include file for BIND",
1258                 paths.namedconf)
1259     logger.info("and %s for further documentation required for secure DNS "
1260                 "updates", paths.namedtxt)