335ea8d965a64917b7a570f3202ddf45af80e00e
[ddiss/samba.git] / source4 / scripting / 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 samba
30 from samba.ndr import ndr_pack, ndr_unpack
31 from samba import read_and_sub_file, setup_file
32 from samba.dcerpc import dnsp, misc, security
33 from samba.dsdb import (
34     DS_DOMAIN_FUNCTION_2000,
35     DS_DOMAIN_FUNCTION_2003,
36     DS_DOMAIN_FUNCTION_2008,
37     DS_DOMAIN_FUNCTION_2008_R2
38     )
39 from base64 import b64encode
40 from samba.provision.descriptor import (
41     get_domain_descriptor,
42     get_dns_partition_descriptor
43     )
44 from samba.provision.common import (
45     setup_path,
46     setup_add_ldif,
47     setup_modify_ldif,
48     setup_ldb
49     )
50
51
52 def get_domainguid(samdb, domaindn):
53     res = samdb.search(base=domaindn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
54     domainguid =  str(ndr_unpack(misc.GUID, res[0]["objectGUID"][0]))
55     return domainguid
56
57 def get_ntdsguid(samdb, domaindn):
58     configdn = samdb.get_config_basedn()
59
60     res1 = samdb.search(base="OU=Domain Controllers,%s" % domaindn, scope=ldb.SCOPE_ONELEVEL,
61                         attrs=["dNSHostName"])
62
63     res2 = samdb.search(expression="serverReference=%s" % res1[0].dn, base=configdn)
64
65     res3 = samdb.search(base="CN=NTDS Settings,%s" % res2[0].dn, scope=ldb.SCOPE_BASE,
66                         attrs=["objectGUID"])
67     ntdsguid = str(ndr_unpack(misc.GUID, res3[0]["objectGUID"][0]))
68     return ntdsguid
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 class ARecord(dnsp.DnssrvRpcRecord):
77     def __init__(self, ip_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
78         super(ARecord, self).__init__()
79         self.wType = dnsp.DNS_TYPE_A
80         self.rank = rank
81         self.dwSerial = serial
82         self.dwTtlSeconds = ttl
83         self.data = ip_addr
84
85 class AAAARecord(dnsp.DnssrvRpcRecord):
86     def __init__(self, ip6_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
87         super(AAAARecord, self).__init__()
88         self.wType = dnsp.DNS_TYPE_AAAA
89         self.rank = rank
90         self.dwSerial = serial
91         self.dwTtlSeconds = ttl
92         self.data = ip6_addr
93
94 class CNameRecord(dnsp.DnssrvRpcRecord):
95     def __init__(self, cname, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
96         super(CNameRecord, self).__init__()
97         self.wType = dnsp.DNS_TYPE_CNAME
98         self.rank = rank
99         self.dwSerial = serial
100         self.dwTtlSeconds = ttl
101         self.data = cname
102
103 class NSRecord(dnsp.DnssrvRpcRecord):
104     def __init__(self, dns_server, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
105         super(NSRecord, self).__init__()
106         self.wType = dnsp.DNS_TYPE_NS
107         self.rank = rank
108         self.dwSerial = serial
109         self.dwTtlSeconds = ttl
110         self.data = dns_server
111
112 class SOARecord(dnsp.DnssrvRpcRecord):
113     def __init__(self, mname, rname, serial=1, refresh=900, retry=600,
114                  expire=86400, minimum=3600, ttl=3600, rank=dnsp.DNS_RANK_ZONE):
115         super(SOARecord, self).__init__()
116         self.wType = dnsp.DNS_TYPE_SOA
117         self.rank = rank
118         self.dwSerial = serial
119         self.dwTtlSeconds = ttl
120         soa = dnsp.soa()
121         soa.serial = serial
122         soa.refresh = refresh
123         soa.retry = retry
124         soa.expire = expire
125         soa.mname = mname
126         soa.rname = rname
127         self.data = soa
128
129 class SRVRecord(dnsp.DnssrvRpcRecord):
130     def __init__(self, target, port, priority=0, weight=100, serial=1, ttl=900,
131                 rank=dnsp.DNS_RANK_ZONE):
132         super(SRVRecord, self).__init__()
133         self.wType = dnsp.DNS_TYPE_SRV
134         self.rank = rank
135         self.dwSerial = serial
136         self.dwTtlSeconds = ttl
137         srv = dnsp.srv()
138         srv.nameTarget = target
139         srv.wPort = port
140         srv.wPriority = priority
141         srv.wWeight = weight
142         self.data = srv
143
144 def setup_dns_partitions(samdb, domainsid, domaindn, forestdn, configdn, serverdn):
145     domainzone_dn = "DC=DomainDnsZones,%s" % domaindn
146     forestzone_dn = "DC=ForestDnsZones,%s" % forestdn
147     descriptor = get_dns_partition_descriptor(domainsid)
148     setup_add_ldif(samdb, setup_path("provision_dnszones_partitions.ldif"), {
149         "DOMAINZONE_DN": domainzone_dn,
150         "FORESTZONE_DN": forestzone_dn,
151         "SECDESC"      : b64encode(descriptor)
152         })
153
154     domainzone_guid = get_domainguid(samdb, domainzone_dn)
155     forestzone_guid = get_domainguid(samdb, forestzone_dn)
156
157     domainzone_guid = str(uuid.uuid4())
158     forestzone_guid = str(uuid.uuid4())
159
160     domainzone_dns = ldb.Dn(samdb, domainzone_dn).canonical_ex_str().strip()
161     forestzone_dns = ldb.Dn(samdb, forestzone_dn).canonical_ex_str().strip()
162
163     setup_add_ldif(samdb, setup_path("provision_dnszones_add.ldif"), {
164         "DOMAINZONE_DN": domainzone_dn,
165         "FORESTZONE_DN": forestzone_dn,
166         "DOMAINZONE_GUID": domainzone_guid,
167         "FORESTZONE_GUID": forestzone_guid,
168         "DOMAINZONE_DNS": domainzone_dns,
169         "FORESTZONE_DNS": forestzone_dns,
170         "CONFIGDN": configdn,
171         "SERVERDN": serverdn,
172         })
173
174     setup_modify_ldif(samdb, setup_path("provision_dnszones_modify.ldif"), {
175         "CONFIGDN": configdn,
176         "SERVERDN": serverdn,
177         "DOMAINZONE_DN": domainzone_dn,
178         "FORESTZONE_DN": forestzone_dn,
179     })
180
181
182 def add_dns_accounts(samdb, domaindn):
183     setup_add_ldif(samdb, setup_path("provision_dns_accounts_add.ldif"), {
184         "DOMAINDN": domaindn,
185         })
186
187 def add_dns_container(samdb, domaindn, prefix, domainsid, dnsadmins_sid):
188     # CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
189     sddl = "O:SYG:SYD:AI" \
190     "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)" \
191     "(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;%s)" \
192     "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
193     "(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
194     "S:AI" % dnsadmins_sid
195     sec = security.descriptor.from_sddl(sddl, domainsid)
196     msg = ldb.Message(ldb.Dn(samdb, "CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)))
197     msg["objectClass"] = ["top", "container"]
198     msg["nTSecurityDescriptor"] = ndr_pack(sec)
199     samdb.add(msg)
200
201 def add_rootservers(samdb, domaindn, prefix):
202     rootservers = {}
203     rootservers["a.root-servers.net"] = "198.41.0.4"
204     rootservers["b.root-servers.net"] = "192.228.79.201"
205     rootservers["c.root-servers.net"] = "192.33.4.12"
206     rootservers["d.root-servers.net"] = "128.8.10.90"
207     rootservers["e.root-servers.net"] = "192.203.230.10"
208     rootservers["f.root-servers.net"] = "192.5.5.241"
209     rootservers["g.root-servers.net"] = "192.112.36.4"
210     rootservers["h.root-servers.net"] = "128.63.2.53"
211     rootservers["i.root-servers.net"] = "192.36.148.17"
212     rootservers["j.root-servers.net"] = "192.58.128.30"
213     rootservers["k.root-servers.net"] = "193.0.14.129"
214     rootservers["l.root-servers.net"] = "199.7.83.42"
215     rootservers["m.root-servers.net"] = "202.12.27.33"
216
217     rootservers_v6 = {}
218     rootservers_v6["a.root-servers.net"] = "2001:503:ba3e::2:30"
219     rootservers_v6["f.root-servers.net"] = "2001:500:2f::f"
220     rootservers_v6["h.root-servers.net"] = "2001:500:1::803f:235"
221     rootservers_v6["j.root-servers.net"] = "2001:503:c27::2:30"
222     rootservers_v6["k.root-servers.net"] = "2001:7fd::1"
223     rootservers_v6["m.root-servers.net"] = "2001:dc3::35"
224
225     container_dn = "DC=RootDNSServers,CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)
226
227     # Add DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
228     msg = ldb.Message(ldb.Dn(samdb, container_dn))
229     msg["objectClass"] = ["top", "dnsZone"]
230     msg["cn"] = ldb.MessageElement("Zone", ldb.FLAG_MOD_ADD, "cn")
231     samdb.add(msg)
232
233     # Add DC=@,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
234     record = []
235     for rserver in rootservers:
236         record.append(ndr_pack(NSRecord(rserver, serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT)))
237
238     msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
239     msg["objectClass"] = ["top", "dnsNode"]
240     msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
241     samdb.add(msg)
242
243     # Add DC=<rootserver>,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
244     for rserver in rootservers:
245         record = [ndr_pack(ARecord(rootservers[rserver], serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT))]
246         # Add AAAA record as well (How does W2K* add IPv6 records?)
247         #if rserver in rootservers_v6:
248         #    record.append(ndr_pack(AAAARecord(rootservers_v6[rserver], serial=0, ttl=0)))
249         msg = ldb.Message(ldb.Dn(samdb, "DC=%s,%s" % (rserver, container_dn)))
250         msg["objectClass"] = ["top", "dnsNode"]
251         msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
252         samdb.add(msg)
253
254 def add_at_record(samdb, container_dn, prefix, hostname, dnsdomain, hostip, hostip6):
255
256     fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
257
258     at_records = []
259
260     # SOA record
261     at_soa_record = SOARecord(fqdn_hostname, "hostmaster.%s" % dnsdomain)
262     at_records.append(ndr_pack(at_soa_record))
263
264     # NS record
265     at_ns_record = NSRecord(fqdn_hostname)
266     at_records.append(ndr_pack(at_ns_record))
267
268     if hostip is not None:
269         # A record
270         at_a_record = ARecord(hostip)
271         at_records.append(ndr_pack(at_a_record))
272
273     if hostip6 is not None:
274         # AAAA record
275         at_aaaa_record = AAAARecord(hostip6)
276         at_records.append(ndr_pack(at_aaaa_record))
277
278     msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
279     msg["objectClass"] = ["top", "dnsNode"]
280     msg["dnsRecord"] = ldb.MessageElement(at_records, ldb.FLAG_MOD_ADD, "dnsRecord")
281     samdb.add(msg)
282
283 def add_srv_record(samdb, container_dn, prefix, host, port):
284     srv_record = SRVRecord(host, port)
285     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
286     msg["objectClass"] = ["top", "dnsNode"]
287     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(srv_record), ldb.FLAG_MOD_ADD, "dnsRecord")
288     samdb.add(msg)
289
290 def add_ns_record(samdb, container_dn, prefix, host):
291     ns_record = NSRecord(host)
292     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
293     msg["objectClass"] = ["top", "dnsNode"]
294     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
295     samdb.add(msg)
296
297 def add_ns_glue_record(samdb, container_dn, prefix, host):
298     ns_record = NSRecord(host, rank=dnsp.DNS_RANK_NS_GLUE)
299     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
300     msg["objectClass"] = ["top", "dnsNode"]
301     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
302     samdb.add(msg)
303
304 def add_cname_record(samdb, container_dn, prefix, host):
305     cname_record = CNameRecord(host)
306     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
307     msg["objectClass"] = ["top", "dnsNode"]
308     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(cname_record), ldb.FLAG_MOD_ADD, "dnsRecord")
309     samdb.add(msg)
310
311 def add_host_record(samdb, container_dn, prefix, hostip, hostip6):
312     host_records = []
313     if hostip:
314         a_record = ARecord(hostip)
315         host_records.append(ndr_pack(a_record))
316     if hostip6:
317         aaaa_record = AAAARecord(hostip6)
318         host_records.append(ndr_pack(aaaa_record))
319     if host_records:
320         msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
321         msg["objectClass"] = ["top", "dnsNode"]
322         msg["dnsRecord"] = ldb.MessageElement(host_records, ldb.FLAG_MOD_ADD, "dnsRecord")
323         samdb.add(msg)
324
325 def add_domain_record(samdb, domaindn, prefix, dnsdomain, domainsid, dnsadmins_sid):
326     # DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
327     sddl = "O:SYG:BAD:AI" \
328     "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;DA)" \
329     "(A;;CC;;;AU)" \
330     "(A;;RPLCLORC;;;WD)" \
331     "(A;;RPWPCRCCDCLCLORCWOWDSDDTSW;;;SY)" \
332     "(A;CI;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
333     "(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;%s)" \
334     "(A;CIID;RPWPCRCCDCLCRCWOWDSDDTSW;;;ED)" \
335     "(OA;CIID;RPWPCR;91e647de-d96f-4b70-9557-d63ff4f3ccd8;;PS)" \
336     "(A;CIID;RPWPCRCCDCLCLORCWOWDSDDTSW;;;EA)" \
337     "(A;CIID;LC;;;RU)" \
338     "(A;CIID;RPWPCRCCLCLORCWOWDSDSW;;;BA)" \
339     "S:AI" % dnsadmins_sid
340     sec = security.descriptor.from_sddl(sddl, domainsid)
341     msg = ldb.Message(ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" % (dnsdomain, prefix, domaindn)))
342     msg["objectClass"] = ["top", "dnsZone"]
343     msg["ntSecurityDescriptor"] = ndr_pack(sec)
344     samdb.add(msg)
345
346 def add_msdcs_record(samdb, forestdn, prefix, dnsforest):
347     # DC=_msdcs.<DNSFOREST>,CN=MicrosoftDNS,<PREFIX>,<FORESTDN>
348     msg = ldb.Message(ldb.Dn(samdb, "DC=_msdcs.%s,CN=MicrosoftDNS,%s,%s" %
349                                     (dnsforest, prefix, forestdn)))
350     msg["objectClass"] = ["top", "dnsZone"]
351     samdb.add(msg)
352
353
354 def add_dc_domain_records(samdb, domaindn, prefix, site, dnsdomain, hostname, hostip, hostip6):
355
356     fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
357
358     # Set up domain container - DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
359     domain_container_dn = ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" %
360                                     (dnsdomain, prefix, domaindn))
361
362     # DC=@ record
363     add_at_record(samdb, domain_container_dn, "DC=@", hostname, dnsdomain, hostip, hostip6)
364
365     # DC=<HOSTNAME> record
366     add_host_record(samdb, domain_container_dn, "DC=%s" % hostname, hostip, hostip6)
367
368     # DC=_kerberos._tcp record
369     add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp", fqdn_hostname, 88)
370
371     # DC=_kerberos._tcp.<SITENAME>._sites record
372     add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp.%s._sites" % site,
373                     fqdn_hostname, 88)
374
375     # DC=_kerberos._udp record
376     add_srv_record(samdb, domain_container_dn, "DC=_kerberos._udp", fqdn_hostname, 88)
377
378     # DC=_kpasswd._tcp record
379     add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._tcp", fqdn_hostname, 464)
380
381     # DC=_kpasswd._udp record
382     add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._udp", fqdn_hostname, 464)
383
384     # DC=_ldap._tcp record
385     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp", fqdn_hostname, 389)
386
387     # DC=_ldap._tcp.<SITENAME>._sites record
388     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites" % site,
389                     fqdn_hostname, 389)
390
391     # FIXME: The number of SRV records depend on the various roles this DC has.
392     #        _gc and _msdcs records are added if the we are the forest dc and not subdomain dc
393     #
394     # Assumption: current DC is GC and add all the entries
395
396     # DC=_gc._tcp record
397     add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp", fqdn_hostname, 3268)
398
399     # DC=_gc._tcp.<SITENAME>,_sites record
400     add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp.%s._sites" % site, fqdn_hostname, 3268)
401
402     # DC=_msdcs record
403     add_ns_glue_record(samdb, domain_container_dn, "DC=_msdcs", fqdn_hostname)
404
405     # FIXME: Following entries are added only if DomainDnsZones and ForestDnsZones partitions
406     #        are created
407     #
408     # Assumption: Additional entries won't hurt on os_level = 2000
409
410     # DC=_ldap._tcp.<SITENAME>._sites.DomainDnsZones
411     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites.DomainDnsZones" % site,
412                     fqdn_hostname, 389)
413
414     # DC=_ldap._tcp.<SITENAME>._sites.ForestDnsZones
415     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites.ForestDnsZones" % site,
416                     fqdn_hostname, 389)
417
418     # DC=_ldap._tcp.DomainDnsZones
419     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.DomainDnsZones",
420                     fqdn_hostname, 389)
421
422     # DC=_ldap._tcp.ForestDnsZones
423     add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.ForestDnsZones",
424                     fqdn_hostname, 389)
425
426     # DC=DomainDnsZones
427     add_host_record(samdb, domain_container_dn, "DC=DomainDnsZones", hostip, hostip6)
428
429     # DC=ForestDnsZones
430     add_host_record(samdb, domain_container_dn, "DC=ForestDnsZones", hostip, hostip6)
431
432
433 def add_dc_msdcs_records(samdb, forestdn, prefix, site, dnsforest, hostname,
434                             hostip, hostip6, domainguid, ntdsguid):
435
436     fqdn_hostname = "%s.%s" % (hostname, dnsforest)
437
438     # Set up forest container - DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
439     forest_container_dn = ldb.Dn(samdb, "DC=_msdcs.%s,CN=MicrosoftDNS,%s,%s" %
440                                     (dnsforest, prefix, forestdn))
441
442     # DC=@ record
443     add_at_record(samdb, forest_container_dn, "DC=@", hostname, dnsforest, None, None)
444
445     # DC=_kerberos._tcp.dc record
446     add_srv_record(samdb, forest_container_dn, "DC=_kerberos._tcp.dc", fqdn_hostname, 88)
447
448     # DC=_kerberos._tcp.<SITENAME>._sites.dc record
449     add_srv_record(samdb, forest_container_dn, "DC=_kerberos._tcp.%s._sites.dc" % site,
450                     fqdn_hostname, 88)
451
452     # DC=_ldap._tcp.dc record
453     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.dc", fqdn_hostname, 389)
454
455     # DC=_ldap._tcp.<SITENAME>._sites.dc record
456     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.dc" % site,
457                     fqdn_hostname, 389)
458
459     # DC=_ldap._tcp.<SITENAME>._sites.gc record
460     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.gc" % site,
461                     fqdn_hostname, 3268)
462
463     # DC=_ldap._tcp.gc record
464     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.gc", fqdn_hostname, 3268)
465
466     # DC=_ldap._tcp.pdc record
467     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.pdc", fqdn_hostname, 389)
468
469     # DC=gc record
470     add_host_record(samdb, forest_container_dn, "DC=gc", hostip, hostip6)
471
472     # DC=_ldap._tcp.<DOMAINGUID>.domains record
473     add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s.domains" % domainguid,
474                     fqdn_hostname, 389)
475
476     # DC=<NTDSGUID>
477     add_cname_record(samdb, forest_container_dn, "DC=%s" % ntdsguid, fqdn_hostname)
478
479
480 def secretsdb_setup_dns(secretsdb, names, private_dir, realm,
481                         dnsdomain, dns_keytab_path, dnspass):
482     """Add DNS specific bits to a secrets database.
483
484     :param secretsdb: Ldb Handle to the secrets database
485     :param names: Names shortcut
486     :param machinepass: Machine password
487     """
488     try:
489         os.unlink(os.path.join(private_dir, dns_keytab_path))
490     except OSError:
491         pass
492
493     setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
494             "REALM": realm,
495             "DNSDOMAIN": dnsdomain,
496             "DNS_KEYTAB": dns_keytab_path,
497             "DNSPASS_B64": b64encode(dnspass),
498             "HOSTNAME": names.hostname,
499             "DNSNAME" : '%s.%s' % (
500                 names.netbiosname.lower(), names.dnsdomain.lower())
501             })
502
503
504 def create_dns_dir(logger, paths):
505     """Write out a DNS zone file, from the info in the current database.
506
507     :param logger: Logger object
508     :param paths: paths object
509     """
510     dns_dir = os.path.dirname(paths.dns)
511
512     try:
513         shutil.rmtree(dns_dir, True)
514     except OSError:
515         pass
516
517     os.mkdir(dns_dir, 0770)
518
519     if paths.bind_gid is not None:
520         try:
521             os.chown(dns_dir, -1, paths.bind_gid)
522             # chmod needed to cope with umask
523             os.chmod(dns_dir, 0770)
524         except OSError:
525             if not os.environ.has_key('SAMBA_SELFTEST'):
526                 logger.error("Failed to chown %s to bind gid %u" % (
527                     dns_dir, paths.bind_gid))
528
529
530 def create_zone_file(lp, logger, paths, targetdir, dnsdomain,
531                      hostip, hostip6, hostname, realm, domainguid,
532                      ntdsguid, site):
533     """Write out a DNS zone file, from the info in the current database.
534
535     :param paths: paths object
536     :param dnsdomain: DNS Domain name
537     :param domaindn: DN of the Domain
538     :param hostip: Local IPv4 IP
539     :param hostip6: Local IPv6 IP
540     :param hostname: Local hostname
541     :param realm: Realm name
542     :param domainguid: GUID of the domain.
543     :param ntdsguid: GUID of the hosts nTDSDSA record.
544     """
545     assert isinstance(domainguid, str)
546
547     if hostip6 is not None:
548         hostip6_base_line = "            IN AAAA    " + hostip6
549         hostip6_host_line = hostname + "        IN AAAA    " + hostip6
550         gc_msdcs_ip6_line = "gc._msdcs               IN AAAA    " + hostip6
551     else:
552         hostip6_base_line = ""
553         hostip6_host_line = ""
554         gc_msdcs_ip6_line = ""
555
556     if hostip is not None:
557         hostip_base_line = "            IN A    " + hostip
558         hostip_host_line = hostname + "        IN A    " + hostip
559         gc_msdcs_ip_line = "gc._msdcs               IN A    " + hostip
560     else:
561         hostip_base_line = ""
562         hostip_host_line = ""
563         gc_msdcs_ip_line = ""
564
565     # we need to freeze the zone while we update the contents
566     if targetdir is None:
567         rndc = ' '.join(lp.get("rndc command"))
568         os.system(rndc + " freeze " + lp.get("realm"))
569
570     setup_file(setup_path("provision.zone"), paths.dns, {
571             "HOSTNAME": hostname,
572             "DNSDOMAIN": dnsdomain,
573             "REALM": realm,
574             "HOSTIP_BASE_LINE": hostip_base_line,
575             "HOSTIP_HOST_LINE": hostip_host_line,
576             "DOMAINGUID": domainguid,
577             "DATESTRING": time.strftime("%Y%m%d%H"),
578             "DEFAULTSITE": site,
579             "NTDSGUID": ntdsguid,
580             "HOSTIP6_BASE_LINE": hostip6_base_line,
581             "HOSTIP6_HOST_LINE": hostip6_host_line,
582             "GC_MSDCS_IP_LINE": gc_msdcs_ip_line,
583             "GC_MSDCS_IP6_LINE": gc_msdcs_ip6_line,
584         })
585
586     if paths.bind_gid is not None:
587         try:
588             os.chown(paths.dns, -1, paths.bind_gid)
589             # chmod needed to cope with umask
590             os.chmod(paths.dns, 0664)
591         except OSError:
592             if not os.environ.has_key('SAMBA_SELFTEST'):
593                 logger.error("Failed to chown %s to bind gid %u" % (
594                     paths.dns, paths.bind_gid))
595
596     if targetdir is None:
597         os.system(rndc + " unfreeze " + lp.get("realm"))
598
599 def tdb_copy(logger, file1, file2):
600     """Copy tdb file using tdbbackup utility and rename it
601     """
602     # Find the location of tdbbackup tool
603     dirs = ["bin", samba.param.bin_dir()]
604     for d in dirs:
605         toolpath = os.path.join(d, "tdbbackup")
606         if os.path.exists(toolpath):
607             break
608     status = os.system("%s -s '.dns' %s" % (toolpath, file1))
609     if status == 0:
610         os.rename("%s.dns" % file1, file2)
611     else:
612         raise Exception("Error copying %s" % file1)
613
614 def create_samdb_copy(samdb, logger, paths, names, domainsid, domainguid):
615     """Create a copy of samdb and give write permissions to named for dns partitions
616     """
617     private_dir = paths.private_dir
618     samldb_dir = os.path.join(private_dir, "sam.ldb.d")
619     dns_dir = os.path.dirname(paths.dns)
620     dns_samldb_dir = os.path.join(dns_dir, "sam.ldb.d")
621
622     # Find the partitions and corresponding filenames
623     partfile = {}
624     res = samdb.search(base="@PARTITION", scope=ldb.SCOPE_BASE, attrs=["partition"])
625     for tmp in res[0]["partition"]:
626         (nc, fname) = tmp.split(':')
627         partfile[nc.upper()] = fname
628
629     # Create empty domain partition
630     domaindn = names.domaindn.upper()
631     domainpart_file = os.path.join(dns_dir, partfile[domaindn])
632     try:
633         os.mkdir(dns_samldb_dir)
634         file(domainpart_file, 'w').close()
635
636         # Fill the basedn and @OPTION records in domain partition
637         dom_ldb = samba.Ldb(domainpart_file)
638         domainguid_line = "objectGUID: %s\n-" % domainguid
639         descr = b64encode(get_domain_descriptor(domainsid))
640         setup_add_ldif(dom_ldb, setup_path("provision_basedn.ldif"), {
641             "DOMAINDN" : names.domaindn,
642             "DOMAINGUID" : domainguid_line,
643             "DOMAINSID" : str(domainsid),
644             "DESCRIPTOR" : descr})
645         setup_add_ldif(dom_ldb, setup_path("provision_basedn_options.ldif"), None)
646     except:
647         logger.error("Failed to setup database for BIND, AD based DNS cannot be used")
648         raise
649     del partfile[domaindn]
650
651     # Link dns partitions and metadata
652     domainzonedn = "DC=DOMAINDNSZONES,%s" % names.domaindn.upper()
653     forestzonedn = "DC=FORESTDNSZONES,%s" % names.rootdn.upper()
654     domainzone_file = partfile[domainzonedn]
655     forestzone_file = partfile[forestzonedn]
656     metadata_file = "metadata.tdb"
657     try:
658         os.link(os.path.join(samldb_dir, metadata_file),
659             os.path.join(dns_samldb_dir, metadata_file))
660         os.link(os.path.join(private_dir, domainzone_file),
661             os.path.join(dns_dir, domainzone_file))
662         os.link(os.path.join(private_dir, forestzone_file),
663             os.path.join(dns_dir, forestzone_file))
664     except OSError:
665         logger.error("Failed to setup database for BIND, AD based DNS cannot be used")
666         raise
667     del partfile[domainzonedn]
668     del partfile[forestzonedn]
669
670     # Copy root, config, schema partitions (and any other if any)
671     # Since samdb is open in the current process, copy them in a child process
672     try:
673         tdb_copy(logger,
674                  os.path.join(private_dir, "sam.ldb"),
675                  os.path.join(dns_dir, "sam.ldb"))
676         for nc in partfile:
677             pfile = partfile[nc]
678             tdb_copy(logger,
679                      os.path.join(private_dir, pfile),
680                      os.path.join(dns_dir, pfile))
681     except:
682         logger.error("Failed to setup database for BIND, AD based DNS cannot be used")
683         raise
684
685     # Give bind read/write permissions dns partitions
686     if paths.bind_gid is not None:
687         try:
688             os.chown(samldb_dir, -1, paths.bind_gid)
689             os.chmod(samldb_dir, 0750)
690
691             for dirname, dirs, files in os.walk(dns_dir):
692                 for d in dirs:
693                     dpath = os.path.join(dirname, d)
694                     os.chown(dpath, -1, paths.bind_gid)
695                     os.chmod(dpath, 0770)
696                 for f in files:
697                     if f.endswith('.ldb') or f.endswith('.tdb'):
698                         fpath = os.path.join(dirname, f)
699                         os.chown(fpath, -1, paths.bind_gid)
700                         os.chmod(fpath, 0660)
701         except OSError:
702             if not os.environ.has_key('SAMBA_SELFTEST'):
703                 logger.error("Failed to set permissions to sam.ldb* files, fix manually")
704     else:
705         if not os.environ.has_key('SAMBA_SELFTEST'):
706             logger.warning("""Unable to find group id for BIND,
707                 set permissions to sam.ldb* files manually""")
708
709
710 def create_dns_update_list(lp, logger, paths):
711     """Write out a dns_update_list file"""
712     # note that we use no variable substitution on this file
713     # the substitution is done at runtime by samba_dnsupdate, samba_spnupdate
714     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
715     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
716
717
718 def create_named_conf(paths, realm, dnsdomain, dns_backend):
719     """Write out a file containing zone statements suitable for inclusion in a
720     named.conf file (including GSS-TSIG configuration).
721
722     :param paths: all paths
723     :param realm: Realm name
724     :param dnsdomain: DNS Domain name
725     :param dns_backend: DNS backend type
726     :param keytab_name: File name of DNS keytab file
727     """
728
729     if dns_backend == "BIND9_FLATFILE":
730         setup_file(setup_path("named.conf"), paths.namedconf, {
731                     "DNSDOMAIN": dnsdomain,
732                     "REALM": realm,
733                     "ZONE_FILE": paths.dns,
734                     "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
735                     "NAMED_CONF": paths.namedconf,
736                     "NAMED_CONF_UPDATE": paths.namedconf_update
737                     })
738
739         setup_file(setup_path("named.conf.update"), paths.namedconf_update)
740
741     elif dns_backend == "BIND9_DLZ":
742         dlz_module_path = os.path.join(samba.param.modules_dir(),
743                                         "bind9/dlz_bind9.so")
744         setup_file(setup_path("named.conf.dlz"), paths.namedconf, {
745                     "NAMED_CONF": paths.namedconf,
746                     "BIND9_DLZ_MODULE": dlz_module_path,
747                     })
748
749
750
751 def create_named_txt(path, realm, dnsdomain, dnsname, private_dir,
752     keytab_name):
753     """Write out a file containing zone statements suitable for inclusion in a
754     named.conf file (including GSS-TSIG configuration).
755
756     :param path: Path of the new named.conf file.
757     :param realm: Realm name
758     :param dnsdomain: DNS Domain name
759     :param private_dir: Path to private directory
760     :param keytab_name: File name of DNS keytab file
761     """
762     setup_file(setup_path("named.txt"), path, {
763             "DNSDOMAIN": dnsdomain,
764             "DNSNAME" : dnsname,
765             "REALM": realm,
766             "DNS_KEYTAB": keytab_name,
767             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
768             "PRIVATE_DIR": private_dir
769         })
770
771
772 def is_valid_dns_backend(dns_backend):
773         return dns_backend in ("BIND9_FLATFILE", "BIND9_DLZ", "SAMBA_INTERNAL", "NONE")
774
775
776 def is_valid_os_level(os_level):
777     return DS_DOMAIN_FUNCTION_2000 <= os_level <= DS_DOMAIN_FUNCTION_2008_R2
778
779
780 def setup_ad_dns(samdb, secretsdb, domainsid, names, paths, lp, logger, dns_backend,
781                  os_level, site, dnspass=None, hostip=None, hostip6=None,
782                  targetdir=None):
783     """Provision DNS information (assuming GC role)
784
785     :param samdb: LDB object connected to sam.ldb file
786     :param secretsdb: LDB object connected to secrets.ldb file
787     :param names: Names shortcut
788     :param paths: Paths shortcut
789     :param lp: Loadparm object
790     :param logger: Logger object
791     :param dns_backend: Type of DNS backend
792     :param os_level: Functional level (treated as os level)
793     :param site: Site to create hostnames in
794     :param dnspass: Password for bind's DNS account
795     :param hostip: IPv4 address
796     :param hostip6: IPv6 address
797     :param targetdir: Target directory for creating DNS-related files for BIND9
798     """
799
800     if not is_valid_dns_backend(dns_backend):
801         raise Exception("Invalid dns backend: %r" % dns_backend)
802
803     if not is_valid_os_level(os_level):
804         raise Exception("Invalid os level: %r" % os_level)
805
806     if dns_backend is "NONE":
807         logger.info("No DNS backend set, not configuring DNS")
808         return
809
810     # If dns_backend is BIND9_FLATFILE
811     #   Populate only CN=MicrosoftDNS,CN=System,<FORESTDN>
812     #
813     # If dns_backend is SAMBA_INTERNAL or BIND9_DLZ
814     #   Populate DNS partitions
815
816     # If os_level < 2003 (DS_DOMAIN_FUNCTION_2000)
817     #   All dns records are in CN=MicrosoftDNS,CN=System,<FORESTDN>
818     #
819     # If os_level >= 2003 (DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008,
820     #                        DS_DOMAIN_FUNCTION_2008_R2)
821     #   Root server records are in CN=MicrosoftDNS,CN=System,<FORESTDN>
822     #   Domain records are in CN=MicrosoftDNS,CN=System,<FORESTDN>
823     #   Domain records are in CN=MicrosoftDNS,DC=DomainDnsZones,<DOMAINDN>
824     #   Forest records are in CN=MicrosoftDNS,DC=ForestDnsZones,<FORESTDN>
825
826     domaindn = names.domaindn
827     forestdn = samdb.get_root_basedn().get_linearized()
828
829     dnsdomain = names.dnsdomain.lower()
830     dnsforest = dnsdomain
831
832     hostname = names.netbiosname.lower()
833
834     domainguid = get_domainguid(samdb, domaindn)
835     ntdsguid = get_ntdsguid(samdb, domaindn)
836
837     # Add dns accounts (DnsAdmins, DnsUpdateProxy) in domain
838     logger.info("Adding DNS accounts")
839     add_dns_accounts(samdb, domaindn)
840     dnsadmins_sid = get_dnsadmins_sid(samdb, domaindn)
841
842     logger.info("Populating CN=MicrosoftDNS,CN=System,%s" % forestdn)
843
844     # Set up MicrosoftDNS container
845     add_dns_container(samdb, forestdn, "CN=System", domainsid, dnsadmins_sid)
846
847     # Add root servers
848     add_rootservers(samdb, forestdn, "CN=System")
849
850     if os_level == DS_DOMAIN_FUNCTION_2000:
851
852         # Add domain record
853         add_domain_record(samdb, forestdn, "CN=System", dnsdomain, domainsid, dnsadmins_sid)
854
855         # Add DNS records for a DC in domain
856         add_dc_domain_records(samdb, forestdn, "CN=System", site, dnsdomain,
857                                 hostname, hostip, hostip6)
858
859     elif dns_backend in ("SAMBA_INTERNAL", "BIND9_DLZ") and \
860             os_level >= DS_DOMAIN_FUNCTION_2003:
861
862         # Set up additional partitions (DomainDnsZones, ForstDnsZones)
863         logger.info("Creating DomainDnsZones and ForestDnsZones partitions")
864         setup_dns_partitions(samdb, domainsid, domaindn, forestdn,
865                             names.configdn, names.serverdn)
866
867         ##### Set up DC=DomainDnsZones,<DOMAINDN>
868         logger.info("Populating DomainDnsZones partition")
869
870         # Set up MicrosoftDNS container
871         add_dns_container(samdb, domaindn, "DC=DomainDnsZones", domainsid, dnsadmins_sid)
872
873         # Add rootserver records
874         add_rootservers(samdb, domaindn, "DC=DomainDnsZones")
875
876         # Add domain record
877         add_domain_record(samdb, domaindn, "DC=DomainDnsZones", dnsdomain, domainsid,
878                           dnsadmins_sid)
879
880         # Add DNS records for a DC in domain
881         add_dc_domain_records(samdb, domaindn, "DC=DomainDnsZones", site, dnsdomain,
882                                 hostname, hostip, hostip6)
883
884         ##### Set up DC=ForestDnsZones,<DOMAINDN>
885         logger.info("Populating ForestDnsZones partition")
886
887         # Set up MicrosoftDNS container
888         add_dns_container(samdb, forestdn, "DC=ForestDnsZones", domainsid, dnsadmins_sid)
889
890         # Add _msdcs record
891         add_msdcs_record(samdb, forestdn, "DC=ForestDnsZones", dnsforest)
892
893         # Add DNS records for a DC in forest
894         add_dc_msdcs_records(samdb, forestdn, "DC=ForestDnsZones", site, dnsforest,
895                                 hostname, hostip, hostip6, domainguid, ntdsguid)
896
897     if dns_backend.startswith("BIND9_"):
898         secretsdb_setup_dns(secretsdb, names,
899                             paths.private_dir, realm=names.realm,
900                             dnsdomain=names.dnsdomain,
901                             dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
902
903         create_dns_dir(logger, paths)
904
905         # Only make a zone file on the first DC, it should be
906         # replicated with DNS replication
907         if dns_backend == "BIND9_FLATFILE":
908             create_zone_file(lp, logger, paths, targetdir, site=site,
909                              dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
910                              hostname=names.hostname, realm=names.realm,
911                              domainguid=domainguid, ntdsguid=names.ntdsguid)
912
913         if dns_backend == "BIND9_DLZ" and os_level >= DS_DOMAIN_FUNCTION_2003:
914             create_samdb_copy(samdb, logger, paths, names, domainsid, domainguid)
915
916         create_named_conf(paths, realm=names.realm,
917                           dnsdomain=names.dnsdomain, dns_backend=dns_backend)
918
919         create_named_txt(paths.namedtxt,
920                          realm=names.realm, dnsdomain=names.dnsdomain,
921                          dnsname = "%s.%s" % (names.hostname, names.dnsdomain),
922                          private_dir=paths.private_dir,
923                          keytab_name=paths.dns_keytab)
924         logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
925         logger.info("and %s for further documentation required for secure DNS "
926                     "updates", paths.namedtxt)