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