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