provision: Set @INDEXLIST first when building dummy sam.ldb
[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
815         # We need the dummy main-domain DB to have the correct @INDEXLIST
816         index_res = samdb.search(base="@INDEXLIST", scope=ldb.SCOPE_BASE)
817         dom_ldb.add(index_res[0])
818
819         domainguid_line = "objectGUID: %s\n-" % domainguid
820         descr = b64encode(get_domain_descriptor(domainsid))
821         setup_add_ldif(dom_ldb, setup_path("provision_basedn.ldif"), {
822             "DOMAINDN" : names.domaindn,
823             "DOMAINGUID" : domainguid_line,
824             "DOMAINSID" : str(domainsid),
825             "DESCRIPTOR" : descr})
826         setup_add_ldif(dom_ldb,
827             setup_path("provision_basedn_options.ldif"), None)
828
829     except:
830         logger.error(
831             "Failed to setup database for BIND, AD based DNS cannot be used")
832         raise
833
834     # This line is critical to the security of the whole scheme.
835     # We assume there is no secret data in the (to be left out of
836     # date and essentially read-only) config, schema and metadata partitions.
837     #
838     # Only the stub of the domain partition is created above.
839     #
840     # That way, things like the krbtgt key do not leak.
841     del partfile[domaindn]
842
843     # Link dns partitions and metadata
844     domainzonedn = "DC=DOMAINDNSZONES,%s" % names.domaindn.upper()
845     forestzonedn = "DC=FORESTDNSZONES,%s" % names.rootdn.upper()
846
847     domainzone_file = partfile[domainzonedn]
848     forestzone_file = partfile.get(forestzonedn)
849
850     metadata_file = "metadata.tdb"
851     try:
852         os.link(os.path.join(samldb_dir, metadata_file),
853             os.path.join(dns_samldb_dir, metadata_file))
854         os.link(os.path.join(private_dir, domainzone_file),
855             os.path.join(dns_dir, domainzone_file))
856         if forestzone_file:
857             os.link(os.path.join(private_dir, forestzone_file),
858                     os.path.join(dns_dir, forestzone_file))
859     except OSError:
860         logger.error(
861             "Failed to setup database for BIND, AD based DNS cannot be used")
862         raise
863     del partfile[domainzonedn]
864     if forestzone_file:
865         del partfile[forestzonedn]
866
867     # Copy root, config, schema partitions (and any other if any)
868     # Since samdb is open in the current process, copy them in a child process
869     try:
870         tdb_copy(os.path.join(private_dir, "sam.ldb"),
871                  os.path.join(dns_dir, "sam.ldb"))
872         for nc in partfile:
873             pfile = partfile[nc]
874             if backend_store == "mdb":
875                 mdb_copy(os.path.join(private_dir, pfile),
876                         os.path.join(dns_dir, pfile))
877             else:
878                 tdb_copy(os.path.join(private_dir, pfile),
879                         os.path.join(dns_dir, pfile))
880     except:
881         logger.error(
882             "Failed to setup database for BIND, AD based DNS cannot be used")
883         raise
884
885     # Give bind read/write permissions dns partitions
886     if paths.bind_gid is not None:
887         try:
888             for dirname, dirs, files in os.walk(dns_dir):
889                 for d in dirs:
890                     dpath = os.path.join(dirname, d)
891                     os.chown(dpath, -1, paths.bind_gid)
892                     os.chmod(dpath, 0o770)
893                 for f in files:
894                     if f.endswith(('.ldb', '.tdb', 'ldb-lock')):
895                         fpath = os.path.join(dirname, f)
896                         os.chown(fpath, -1, paths.bind_gid)
897                         os.chmod(fpath, 0o660)
898         except OSError:
899             if not os.environ.has_key('SAMBA_SELFTEST'):
900                 logger.error(
901                     "Failed to set permissions to sam.ldb* files, fix manually")
902     else:
903         if not os.environ.has_key('SAMBA_SELFTEST'):
904             logger.warning("""Unable to find group id for BIND,
905                 set permissions to sam.ldb* files manually""")
906
907
908 def create_dns_update_list(lp, logger, paths):
909     """Write out a dns_update_list file"""
910     # note that we use no variable substitution on this file
911     # the substitution is done at runtime by samba_dnsupdate, samba_spnupdate
912     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
913     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
914
915
916 def create_named_conf(paths, realm, dnsdomain, dns_backend, logger):
917     """Write out a file containing zone statements suitable for inclusion in a
918     named.conf file (including GSS-TSIG configuration).
919
920     :param paths: all paths
921     :param realm: Realm name
922     :param dnsdomain: DNS Domain name
923     :param dns_backend: DNS backend type
924     :param keytab_name: File name of DNS keytab file
925     :param logger: Logger object
926     """
927
928     # TODO: This really should have been done as a top level import.
929     # It is done here to avoid a depencency loop.  That is, we move
930     # ProvisioningError to another file, and have all the provision
931     # scripts import it from there.
932
933     from samba.provision import ProvisioningError
934
935     if dns_backend == "BIND9_FLATFILE":
936         setup_file(setup_path("named.conf"), paths.namedconf, {
937                     "DNSDOMAIN": dnsdomain,
938                     "REALM": realm,
939                     "ZONE_FILE": paths.dns,
940                     "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
941                     "NAMED_CONF": paths.namedconf,
942                     "NAMED_CONF_UPDATE": paths.namedconf_update
943                     })
944
945         setup_file(setup_path("named.conf.update"), paths.namedconf_update)
946
947     elif dns_backend == "BIND9_DLZ":
948         bind_info = subprocess.Popen(['named -V'], shell=True,
949                                      stdout=subprocess.PIPE,
950                                      stderr=subprocess.STDOUT,
951                                      cwd='.').communicate()[0]
952         bind9_8 = '#'
953         bind9_9 = '#'
954         bind9_10 = '#'
955         bind9_11 = '#'
956         if bind_info.upper().find('BIND 9.8') != -1:
957             bind9_8 = ''
958         elif bind_info.upper().find('BIND 9.9') != -1:
959             bind9_9 = ''
960         elif bind_info.upper().find('BIND 9.10') != -1:
961             bind9_10 = ''
962         elif bind_info.upper().find('BIND 9.11') != -1:
963             bind9_11 = ''
964         elif bind_info.upper().find('BIND 9.7') != -1:
965             raise ProvisioningError("DLZ option incompatible with BIND 9.7.")
966         else:
967             logger.warning("BIND version unknown, please modify %s manually." % paths.namedconf)
968         setup_file(setup_path("named.conf.dlz"), paths.namedconf, {
969                     "NAMED_CONF": paths.namedconf,
970                     "MODULESDIR" : samba.param.modules_dir(),
971                     "BIND9_8" : bind9_8,
972                     "BIND9_9" : bind9_9,
973                     "BIND9_10" : bind9_10,
974                     "BIND9_11" : bind9_11
975                     })
976
977
978 def create_named_txt(path, realm, dnsdomain, dnsname, binddns_dir,
979     keytab_name):
980     """Write out a file containing zone statements suitable for inclusion in a
981     named.conf file (including GSS-TSIG configuration).
982
983     :param path: Path of the new named.conf file.
984     :param realm: Realm name
985     :param dnsdomain: DNS Domain name
986     :param binddns_dir: Path to bind dns directory
987     :param keytab_name: File name of DNS keytab file
988     """
989     setup_file(setup_path("named.txt"), path, {
990             "DNSDOMAIN": dnsdomain,
991             "DNSNAME" : dnsname,
992             "REALM": realm,
993             "DNS_KEYTAB": keytab_name,
994             "DNS_KEYTAB_ABS": os.path.join(binddns_dir, keytab_name),
995             "PRIVATE_DIR": binddns_dir
996         })
997
998
999 def is_valid_dns_backend(dns_backend):
1000     return dns_backend in ("BIND9_FLATFILE", "BIND9_DLZ", "SAMBA_INTERNAL", "NONE")
1001
1002
1003 def is_valid_os_level(os_level):
1004     return DS_DOMAIN_FUNCTION_2000 <= os_level <= DS_DOMAIN_FUNCTION_2016
1005
1006
1007 def create_dns_legacy(samdb, domainsid, forestdn, dnsadmins_sid):
1008     # Set up MicrosoftDNS container
1009     add_dns_container(samdb, forestdn, "CN=System", domainsid, dnsadmins_sid)
1010     # Add root servers
1011     add_rootservers(samdb, forestdn, "CN=System")
1012
1013
1014 def fill_dns_data_legacy(samdb, domainsid, forestdn, dnsdomain, site, hostname,
1015                          hostip, hostip6, dnsadmins_sid):
1016     # Add domain record
1017     add_domain_record(samdb, forestdn, "CN=System", dnsdomain, domainsid,
1018                       dnsadmins_sid)
1019
1020     # Add DNS records for a DC in domain
1021     add_dc_domain_records(samdb, forestdn, "CN=System", site, dnsdomain,
1022                           hostname, hostip, hostip6)
1023
1024
1025 def create_dns_partitions(samdb, domainsid, names, domaindn, forestdn,
1026                           dnsadmins_sid, fill_level):
1027     # Set up additional partitions (DomainDnsZones, ForstDnsZones)
1028     setup_dns_partitions(samdb, domainsid, domaindn, forestdn,
1029                         names.configdn, names.serverdn, fill_level)
1030
1031     # Set up MicrosoftDNS containers
1032     add_dns_container(samdb, domaindn, "DC=DomainDnsZones", domainsid,
1033                       dnsadmins_sid)
1034     if fill_level != FILL_SUBDOMAIN:
1035         add_dns_container(samdb, forestdn, "DC=ForestDnsZones", domainsid,
1036                           dnsadmins_sid, forest=True)
1037
1038
1039 def fill_dns_data_partitions(samdb, domainsid, site, domaindn, forestdn,
1040                              dnsdomain, dnsforest, hostname, hostip, hostip6,
1041                              domainguid, ntdsguid, dnsadmins_sid, autofill=True,
1042                              fill_level=FILL_FULL):
1043     """Fill data in various AD partitions
1044
1045     :param samdb: LDB object connected to sam.ldb file
1046     :param domainsid: Domain SID (as dom_sid object)
1047     :param site: Site name to create hostnames in
1048     :param domaindn: DN of the domain
1049     :param forestdn: DN of the forest
1050     :param dnsdomain: DNS name of the domain
1051     :param dnsforest: DNS name of the forest
1052     :param hostname: Host name of this DC
1053     :param hostip: IPv4 addresses
1054     :param hostip6: IPv6 addresses
1055     :param domainguid: Domain GUID
1056     :param ntdsguid: NTDS GUID
1057     :param dnsadmins_sid: SID for DnsAdmins group
1058     :param autofill: Create DNS records (using fixed template)
1059     """
1060
1061     ##### Set up DC=DomainDnsZones,<DOMAINDN>
1062     # Add rootserver records
1063     add_rootservers(samdb, domaindn, "DC=DomainDnsZones")
1064
1065     # Add domain record
1066     add_domain_record(samdb, domaindn, "DC=DomainDnsZones", dnsdomain,
1067                       domainsid, dnsadmins_sid)
1068
1069     # Add DNS records for a DC in domain
1070     if autofill:
1071         add_dc_domain_records(samdb, domaindn, "DC=DomainDnsZones", site,
1072                               dnsdomain, hostname, hostip, hostip6)
1073
1074     if fill_level != FILL_SUBDOMAIN:
1075         ##### Set up DC=ForestDnsZones,<FORESTDN>
1076         # Add _msdcs record
1077         add_msdcs_record(samdb, forestdn, "DC=ForestDnsZones", dnsforest)
1078
1079         # Add DNS records for a DC in forest
1080         if autofill:
1081             add_dc_msdcs_records(samdb, forestdn, "DC=ForestDnsZones", site,
1082                                  dnsforest, hostname, hostip, hostip6,
1083                                  domainguid, ntdsguid)
1084
1085
1086 def setup_ad_dns(samdb, secretsdb, names, paths, lp, logger,
1087         dns_backend, os_level, dnspass=None, hostip=None, hostip6=None,
1088         targetdir=None, fill_level=FILL_FULL, backend_store=None):
1089     """Provision DNS information (assuming GC role)
1090
1091     :param samdb: LDB object connected to sam.ldb file
1092     :param secretsdb: LDB object connected to secrets.ldb file
1093     :param names: Names shortcut
1094     :param paths: Paths shortcut
1095     :param lp: Loadparm object
1096     :param logger: Logger object
1097     :param dns_backend: Type of DNS backend
1098     :param os_level: Functional level (treated as os level)
1099     :param dnspass: Password for bind's DNS account
1100     :param hostip: IPv4 address
1101     :param hostip6: IPv6 address
1102     :param targetdir: Target directory for creating DNS-related files for BIND9
1103     """
1104
1105     if not is_valid_dns_backend(dns_backend):
1106         raise Exception("Invalid dns backend: %r" % dns_backend)
1107
1108     if not is_valid_os_level(os_level):
1109         raise Exception("Invalid os level: %r" % os_level)
1110
1111     if dns_backend == "NONE":
1112         logger.info("No DNS backend set, not configuring DNS")
1113         return
1114
1115     # Add dns accounts (DnsAdmins, DnsUpdateProxy) in domain
1116     logger.info("Adding DNS accounts")
1117     add_dns_accounts(samdb, names.domaindn)
1118
1119     # If dns_backend is BIND9_FLATFILE
1120     #   Populate only CN=MicrosoftDNS,CN=System,<DOMAINDN>
1121     #
1122     # If dns_backend is SAMBA_INTERNAL or BIND9_DLZ
1123     #   Populate DNS partitions
1124
1125     # If os_level < 2003 (DS_DOMAIN_FUNCTION_2000)
1126     #   All dns records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
1127     #
1128     # If os_level >= 2003 (DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008,
1129     #                        DS_DOMAIN_FUNCTION_2008_R2)
1130     #   Root server records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
1131     #   Domain records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
1132     #   Domain records are in CN=MicrosoftDNS,DC=DomainDnsZones,<DOMAINDN>
1133     #   Forest records are in CN=MicrosoftDNS,DC=ForestDnsZones,<FORESTDN>
1134     domaindn = names.domaindn
1135     forestdn = samdb.get_root_basedn().get_linearized()
1136
1137     dnsdomain = names.dnsdomain.lower()
1138     dnsforest = dnsdomain
1139
1140     site = names.sitename
1141
1142     hostname = names.netbiosname.lower()
1143
1144     dnsadmins_sid = get_dnsadmins_sid(samdb, domaindn)
1145     domainguid = get_domainguid(samdb, domaindn)
1146
1147     samdb.transaction_start()
1148     try:
1149         # Create CN=System
1150         logger.info("Creating CN=MicrosoftDNS,CN=System,%s" % domaindn)
1151         create_dns_legacy(samdb, names.domainsid, domaindn, dnsadmins_sid)
1152
1153         if os_level == DS_DOMAIN_FUNCTION_2000:
1154             # Populating legacy dns
1155             logger.info("Populating CN=MicrosoftDNS,CN=System,%s" % domaindn)
1156             fill_dns_data_legacy(samdb, names.domainsid, domaindn, dnsdomain, site,
1157                                  hostname, hostip, hostip6, dnsadmins_sid)
1158
1159         elif dns_backend in ("SAMBA_INTERNAL", "BIND9_DLZ") and \
1160                 os_level >= DS_DOMAIN_FUNCTION_2003:
1161
1162             # Create DNS partitions
1163             logger.info("Creating DomainDnsZones and ForestDnsZones partitions")
1164             create_dns_partitions(samdb, names.domainsid, names, domaindn, forestdn,
1165                                   dnsadmins_sid, fill_level)
1166
1167             # Populating dns partitions
1168             logger.info("Populating DomainDnsZones and ForestDnsZones partitions")
1169             fill_dns_data_partitions(samdb, names.domainsid, site, domaindn, forestdn,
1170                                      dnsdomain, dnsforest, hostname, hostip, hostip6,
1171                                      domainguid, names.ntdsguid, dnsadmins_sid,
1172                                      fill_level=fill_level)
1173
1174     except:
1175         samdb.transaction_cancel()
1176         raise
1177     else:
1178         samdb.transaction_commit()
1179
1180     if dns_backend.startswith("BIND9_"):
1181         setup_bind9_dns(samdb, secretsdb, names, paths, lp, logger,
1182                         dns_backend, os_level, site=site, dnspass=dnspass, hostip=hostip,
1183                         hostip6=hostip6, targetdir=targetdir,
1184                         backend_store=backend_store)
1185
1186
1187 def setup_bind9_dns(samdb, secretsdb, names, paths, lp, logger,
1188         dns_backend, os_level, site=None, dnspass=None, hostip=None,
1189         hostip6=None, targetdir=None, key_version_number=None,
1190         backend_store=None):
1191     """Provision DNS information (assuming BIND9 backend in DC role)
1192
1193     :param samdb: LDB object connected to sam.ldb file
1194     :param secretsdb: LDB object connected to secrets.ldb file
1195     :param names: Names shortcut
1196     :param paths: Paths shortcut
1197     :param lp: Loadparm object
1198     :param logger: Logger object
1199     :param dns_backend: Type of DNS backend
1200     :param os_level: Functional level (treated as os level)
1201     :param site: Site to create hostnames in
1202     :param dnspass: Password for bind's DNS account
1203     :param hostip: IPv4 address
1204     :param hostip6: IPv6 address
1205     :param targetdir: Target directory for creating DNS-related files for BIND9
1206     """
1207
1208     if (not is_valid_dns_backend(dns_backend) or
1209         not dns_backend.startswith("BIND9_")):
1210         raise Exception("Invalid dns backend: %r" % dns_backend)
1211
1212     if not is_valid_os_level(os_level):
1213         raise Exception("Invalid os level: %r" % os_level)
1214
1215     domaindn = names.domaindn
1216
1217     domainguid = get_domainguid(samdb, domaindn)
1218
1219     secretsdb_setup_dns(secretsdb, names,
1220                         paths.private_dir,
1221                         paths.binddns_dir,
1222                         realm=names.realm,
1223                         dnsdomain=names.dnsdomain,
1224                         dns_keytab_path=paths.dns_keytab, dnspass=dnspass,
1225                         key_version_number=key_version_number)
1226
1227     create_dns_dir(logger, paths)
1228
1229     if dns_backend == "BIND9_FLATFILE":
1230         create_zone_file(lp, logger, paths, targetdir, site=site,
1231                          dnsdomain=names.dnsdomain, hostip=hostip,
1232                          hostip6=hostip6, hostname=names.hostname,
1233                          realm=names.realm, domainguid=domainguid,
1234                          ntdsguid=names.ntdsguid)
1235
1236     if dns_backend == "BIND9_DLZ" and os_level >= DS_DOMAIN_FUNCTION_2003:
1237         create_samdb_copy(samdb, logger, paths,
1238                           names, names.domainsid, domainguid)
1239
1240     create_named_conf(paths, realm=names.realm,
1241                       dnsdomain=names.dnsdomain, dns_backend=dns_backend,
1242                       logger=logger)
1243
1244     create_named_txt(paths.namedtxt,
1245                      realm=names.realm, dnsdomain=names.dnsdomain,
1246                      dnsname = "%s.%s" % (names.hostname, names.dnsdomain),
1247                      binddns_dir=paths.binddns_dir,
1248                      keytab_name=paths.dns_keytab)
1249     logger.info("See %s for an example configuration include file for BIND",
1250                 paths.namedconf)
1251     logger.info("and %s for further documentation required for secure DNS "
1252                 "updates", paths.namedtxt)