d7d75cdca54a4da76527ba7fc6dcb2608880cb39
[kai/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 import samba
29 from samba.ndr import ndr_pack, ndr_unpack
30 from samba import read_and_sub_file, setup_file
31 from samba.dcerpc import dnsp, misc
32 from samba.dsdb import (
33     DS_DOMAIN_FUNCTION_2000,
34     DS_DOMAIN_FUNCTION_2003,
35     DS_DOMAIN_FUNCTION_2008,
36     DS_DOMAIN_FUNCTION_2008_R2
37     )
38 from base64 import b64encode
39
40
41 def add_ldif(ldb, ldif_file, subst_vars, controls=["relax:0"]):
42     ldif_file_path = os.path.join(samba.param.setup_dir(), ldif_file)
43     data = read_and_sub_file(ldif_file_path, subst_vars)
44     ldb.add_ldif(data, controls)
45
46 def modify_ldif(ldb, ldif_file, subst_vars, controls=["relax:0"]):
47     ldif_file_path = os.path.join(samba.param.setup_dir(), ldif_file)
48     data = read_and_sub_file(ldif_file_path, subst_vars)
49     ldb.modify_ldif(data, controls)
50
51 def setup_ldb(ldb, ldif_path, subst_vars):
52     """Import a LDIF a file into a LDB handle, optionally substituting
53     variables.
54
55     :note: Either all LDIF data will be added or none (using transactions).
56
57     :param ldb: LDB file to import into.
58     :param ldif_path: Path to the LDIF file.
59     :param subst_vars: Dictionary with substitution variables.
60     """
61     assert ldb is not None
62     ldb.transaction_start()
63     try:
64         add_ldif(ldb, ldif_path, subst_vars)
65     except Exception:
66         ldb.transaction_cancel()
67         raise
68     else:
69         ldb.transaction_commit()
70
71 def setup_path(file):
72     """Return an absolute path to the provision tempate file specified by file"""
73     return os.path.join(samba.param.setup_dir(), file)
74
75 def get_domainguid(samdb, domaindn):
76     res = samdb.search(base=domaindn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
77     domainguid =  str(ndr_unpack(misc.GUID, res[0]["objectGUID"][0]))
78     return domainguid
79
80 def get_ntdsguid(samdb, domaindn):
81     configdn = samdb.get_config_basedn()
82
83     res1 = samdb.search(base="OU=Domain Controllers,%s" % domaindn, scope=ldb.SCOPE_ONELEVEL,
84                         attrs=["dNSHostName"])
85
86     res2 = samdb.search(expression="serverReference=%s" % res1[0].dn, base=configdn)
87
88     res3 = samdb.search(base="CN=NTDS Settings,%s" % res2[0].dn, scope=ldb.SCOPE_BASE,
89                         attrs=["objectGUID"])
90     ntdsguid = str(ndr_unpack(misc.GUID, res3[0]["objectGUID"][0]))
91     return ntdsguid
92
93
94 class ARecord(dnsp.DnssrvRpcRecord):
95     def __init__(self, ip_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
96         super(ARecord, self).__init__()
97         self.wType = dnsp.DNS_TYPE_A
98         self.rank = rank
99         self.dwSerial = serial
100         self.dwTtlSeconds = ttl
101         self.data = ip_addr
102
103 class AAAARecord(dnsp.DnssrvRpcRecord):
104     def __init__(self, ip6_addr, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
105         super(AAAARecord, self).__init__()
106         self.wType = dnsp.DNS_TYPE_AAAA
107         self.rank = rank
108         self.dwSerial = serial
109         self.dwTtlSeconds = ttl
110         self.data = ip6_addr
111
112 class CNameRecord(dnsp.DnssrvRpcRecord):
113     def __init__(self, cname, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
114         super(CNameRecord, self).__init__()
115         self.wType = dnsp.DNS_TYPE_CNAME
116         self.rank = rank
117         self.dwSerial = serial
118         self.dwTtlSeconds = ttl
119         self.data = cname
120
121 class NSRecord(dnsp.DnssrvRpcRecord):
122     def __init__(self, dns_server, serial=1, ttl=900, rank=dnsp.DNS_RANK_ZONE):
123         super(NSRecord, self).__init__()
124         self.wType = dnsp.DNS_TYPE_NS
125         self.rank = rank
126         self.dwSerial = serial
127         self.dwTtlSeconds = ttl
128         self.data = dns_server
129
130 class SOARecord(dnsp.DnssrvRpcRecord):
131     def __init__(self, mname, rname, serial=1, refresh=900, retry=600,
132                  expire=86400, minimum=3600, ttl=3600, rank=dnsp.DNS_RANK_ZONE):
133         super(SOARecord, self).__init__()
134         self.wType = dnsp.DNS_TYPE_SOA
135         self.rank = rank
136         self.dwSerial = serial
137         self.dwTtlSeconds = ttl
138         soa = dnsp.soa()
139         soa.serial = serial
140         soa.refresh = refresh
141         soa.retry = retry
142         soa.expire = expire
143         soa.mname = mname
144         soa.rname = rname
145         self.data = soa
146
147 class SRVRecord(dnsp.DnssrvRpcRecord):
148     def __init__(self, target, port, priority=0, weight=100, serial=1, ttl=900,
149                 rank=dnsp.DNS_RANK_ZONE):
150         super(SRVRecord, self).__init__()
151         self.wType = dnsp.DNS_TYPE_SRV
152         self.rank = rank
153         self.dwSerial = serial
154         self.dwTtlSeconds = ttl
155         srv = dnsp.srv()
156         srv.nameTarget = target
157         srv.wPort = port
158         srv.wPriority = priority
159         srv.wWeight = weight
160         self.data = srv
161
162
163 def setup_dns_partitions(samdb, domaindn, forestdn, configdn, serverdn):
164
165     # FIXME: Default security descriptor for Domain-DNS objectCategory is different in
166     #        our documentation from windows
167
168     domainzone_dn = "DC=DomainDnsZones,%s" % domaindn
169     forestzone_dn = "DC=ForestDnsZones,%s" % forestdn
170
171     add_ldif(samdb, "provision_dnszones_partitions.ldif", {
172         "DOMAINZONE_DN": domainzone_dn,
173         "FORESTZONE_DN": forestzone_dn,
174         })
175
176     domainzone_guid = get_domainguid(samdb, domainzone_dn)
177     forestzone_guid = get_domainguid(samdb, forestzone_dn)
178
179     domainzone_guid = str(uuid.uuid4())
180     forestzone_guid = str(uuid.uuid4())
181
182     domainzone_dns = ldb.Dn(samdb, domainzone_dn).canonical_ex_str().strip()
183     forestzone_dns = ldb.Dn(samdb, forestzone_dn).canonical_ex_str().strip()
184
185     add_ldif(samdb, "provision_dnszones_add.ldif", {
186         "DOMAINZONE_DN": domainzone_dn,
187         "FORESTZONE_DN": forestzone_dn,
188         "DOMAINZONE_GUID": domainzone_guid,
189         "FORESTZONE_GUID": forestzone_guid,
190         "DOMAINZONE_DNS": domainzone_dns,
191         "FORESTZONE_DNS": forestzone_dns,
192         "CONFIGDN": configdn,
193         "SERVERDN": serverdn,
194         })
195
196     modify_ldif(samdb, "provision_dnszones_modify.ldif", {
197         "CONFIGDN": configdn,
198         "SERVERDN": serverdn,
199         "DOMAINZONE_DN": domainzone_dn,
200         "FORESTZONE_DN": forestzone_dn,
201     })
202
203
204 def add_dns_accounts(samdb, domaindn):
205     add_ldif(samdb, "provision_dns_accounts_add.ldif", {
206         "DOMAINDN": domaindn,
207         })
208
209 def add_dns_container(samdb, domaindn, prefix):
210     # CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
211     msg = ldb.Message(ldb.Dn(samdb, "CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)))
212     msg["objectClass"] = ["top", "container"]
213     msg["displayName"] = ldb.MessageElement("DNS Servers", ldb.FLAG_MOD_ADD, "displayName")
214     samdb.add(msg)
215
216
217 def add_rootservers(samdb, domaindn, prefix):
218     rootservers = {}
219     rootservers["a.root-servers.net"] = "198.41.0.4"
220     rootservers["b.root-servers.net"] = "192.228.79.201"
221     rootservers["c.root-servers.net"] = "192.33.4.12"
222     rootservers["d.root-servers.net"] = "128.8.10.90"
223     rootservers["e.root-servers.net"] = "192.203.230.10"
224     rootservers["f.root-servers.net"] = "192.5.5.241"
225     rootservers["g.root-servers.net"] = "192.112.36.4"
226     rootservers["h.root-servers.net"] = "128.63.2.53"
227     rootservers["i.root-servers.net"] = "192.36.148.17"
228     rootservers["j.root-servers.net"] = "192.58.128.30"
229     rootservers["k.root-servers.net"] = "193.0.14.129"
230     rootservers["l.root-servers.net"] = "199.7.83.42"
231     rootservers["m.root-servers.net"] = "202.12.27.33"
232
233     rootservers_v6 = {}
234     rootservers_v6["a.root-servers.net"] = "2001:503:ba3e::2:30"
235     rootservers_v6["f.root-servers.net"] = "2001:500:2f::f"
236     rootservers_v6["h.root-servers.net"] = "2001:500:1::803f:235"
237     rootservers_v6["j.root-servers.net"] = "2001:503:c27::2:30"
238     rootservers_v6["k.root-servers.net"] = "2001:7fd::1"
239     rootservers_v6["m.root-servers.net"] = "2001:dc3::35"
240
241     container_dn = "DC=RootDNSServers,CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)
242
243     # Add DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
244     msg = ldb.Message(ldb.Dn(samdb, container_dn))
245     msg["objectClass"] = ["top", "dnsZone"]
246     samdb.add(msg)
247
248     # Add DC=@,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
249     record = []
250     for rserver in rootservers:
251         record.append(ndr_pack(NSRecord(rserver, serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT)))
252
253     msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
254     msg["objectClass"] = ["top", "dnsNode"]
255     msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
256     samdb.add(msg)
257
258     # Add DC=<rootserver>,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
259     for rserver in rootservers:
260         record = [ndr_pack(ARecord(rootservers[rserver], serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT))]
261         # Add AAAA record as well (How does W2K* add IPv6 records?)
262         #if rserver in rootservers_v6:
263         #    record.append(ndr_pack(AAAARecord(rootservers_v6[rserver], serial=0, ttl=0)))
264         msg = ldb.Message(ldb.Dn(samdb, "DC=%s,%s" % (rserver, container_dn)))
265         msg["objectClass"] = ["top", "dnsNode"]
266         msg["dnsRecord"] = ldb.MessageElement(record, ldb.FLAG_MOD_ADD, "dnsRecord")
267         samdb.add(msg)
268
269 def add_at_record(samdb, container_dn, prefix, hostname, dnsdomain, hostip, hostip6):
270
271     fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
272
273     at_records = []
274
275     # SOA record
276     at_soa_record = SOARecord(fqdn_hostname, "hostmaster.%s" % dnsdomain)
277     at_records.append(ndr_pack(at_soa_record))
278
279     # NS record
280     at_ns_record = NSRecord(fqdn_hostname)
281     at_records.append(ndr_pack(at_ns_record))
282
283     if hostip is not None:
284         # A record
285         at_a_record = ARecord(hostip)
286         at_records.append(ndr_pack(at_a_record))
287
288     if hostip6 is not None:
289         # AAAA record
290         at_aaaa_record = AAAARecord(hostip6)
291         at_records.append(ndr_pack(at_aaaa_record))
292
293     msg = ldb.Message(ldb.Dn(samdb, "DC=@,%s" % container_dn))
294     msg["objectClass"] = ["top", "dnsNode"]
295     msg["dnsRecord"] = ldb.MessageElement(at_records, ldb.FLAG_MOD_ADD, "dnsRecord")
296     samdb.add(msg)
297
298 def add_srv_record(samdb, container_dn, prefix, host, port):
299     srv_record = SRVRecord(host, port)
300     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
301     msg["objectClass"] = ["top", "dnsNode"]
302     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(srv_record), ldb.FLAG_MOD_ADD, "dnsRecord")
303     samdb.add(msg)
304
305 def add_ns_record(samdb, container_dn, prefix, host):
306     ns_record = NSRecord(host)
307     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
308     msg["objectClass"] = ["top", "dnsNode"]
309     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
310     samdb.add(msg)
311
312 def add_ns_glue_record(samdb, container_dn, prefix, host):
313     ns_record = NSRecord(host, rank=dnsp.DNS_RANK_NS_GLUE)
314     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
315     msg["objectClass"] = ["top", "dnsNode"]
316     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(ns_record), ldb.FLAG_MOD_ADD, "dnsRecord")
317     samdb.add(msg)
318
319 def add_cname_record(samdb, container_dn, prefix, host):
320     cname_record = CNameRecord(host)
321     msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
322     msg["objectClass"] = ["top", "dnsNode"]
323     msg["dnsRecord"] = ldb.MessageElement(ndr_pack(cname_record), ldb.FLAG_MOD_ADD, "dnsRecord")
324     samdb.add(msg)
325
326 def add_host_record(samdb, container_dn, prefix, hostip, hostip6):
327     host_records = []
328     if hostip:
329         a_record = ARecord(hostip)
330         host_records.append(ndr_pack(a_record))
331     if hostip6:
332         aaaa_record = AAAARecord(hostip6)
333         host_records.append(ndr_pack(aaaa_record))
334     if host_records:
335         msg = ldb.Message(ldb.Dn(samdb, "%s,%s" % (prefix, container_dn)))
336         msg["objectClass"] = ["top", "dnsNode"]
337         msg["dnsRecord"] = ldb.MessageElement(host_records, ldb.FLAG_MOD_ADD, "dnsRecord")
338         samdb.add(msg)
339
340 def add_domain_record(samdb, domaindn, prefix, dnsdomain):
341     # DC=<DNSDOMAIN>,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
342     msg = ldb.Message(ldb.Dn(samdb, "DC=%s,CN=MicrosoftDNS,%s,%s" % (dnsdomain, prefix, domaindn)))
343     msg["objectClass"] = ["top", "dnsZone"]
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
600 def create_dns_update_list(lp, logger, paths):
601     """Write out a dns_update_list file"""
602     # note that we use no variable substitution on this file
603     # the substitution is done at runtime by samba_dnsupdate, samba_spnupdate
604     setup_file(setup_path("dns_update_list"), paths.dns_update_list, None)
605     setup_file(setup_path("spn_update_list"), paths.spn_update_list, None)
606
607
608 def create_named_conf(paths, realm, dnsdomain, dns_backend):
609     """Write out a file containing zone statements suitable for inclusion in a
610     named.conf file (including GSS-TSIG configuration).
611
612     :param paths: all paths
613     :param realm: Realm name
614     :param dnsdomain: DNS Domain name
615     :param dns_backend: DNS backend type
616     :param keytab_name: File name of DNS keytab file
617     """
618
619     if dns_backend == "BIND9_FLATFILE":
620         setup_file(setup_path("named.conf"), paths.namedconf, {
621                     "DNSDOMAIN": dnsdomain,
622                     "REALM": realm,
623                     "ZONE_FILE": paths.dns,
624                     "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
625                     "NAMED_CONF": paths.namedconf,
626                     "NAMED_CONF_UPDATE": paths.namedconf_update
627                     })
628
629         setup_file(setup_path("named.conf.update"), paths.namedconf_update)
630
631     elif dns_backend == "BIND9_DLZ":
632         dlz_module_path = os.path.join(samba.param.modules_dir(),
633                                         "bind9/dlz_bind9.so")
634         setup_file(setup_path("named.conf.dlz"), paths.namedconf, {
635                     "NAMED_CONF": paths.namedconf,
636                     "BIND9_DLZ_MODULE": dlz_module_path,
637                     })
638
639
640
641 def create_named_txt(path, realm, dnsdomain, dnsname, private_dir,
642     keytab_name):
643     """Write out a file containing zone statements suitable for inclusion in a
644     named.conf file (including GSS-TSIG configuration).
645
646     :param path: Path of the new named.conf file.
647     :param realm: Realm name
648     :param dnsdomain: DNS Domain name
649     :param private_dir: Path to private directory
650     :param keytab_name: File name of DNS keytab file
651     """
652     setup_file(setup_path("named.txt"), path, {
653             "DNSDOMAIN": dnsdomain,
654             "DNSNAME" : dnsname,
655             "REALM": realm,
656             "DNS_KEYTAB": keytab_name,
657             "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
658             "PRIVATE_DIR": private_dir
659         })
660
661
662 def is_valid_dns_backend(dns_backend):
663         return dns_backend in ("BIND9_FLATFILE", "BIND9_DLZ", "SAMBA_INTERNAL", "NONE")
664
665
666 def is_valid_os_level(os_level):
667     return DS_DOMAIN_FUNCTION_2000 <= os_level <= DS_DOMAIN_FUNCTION_2008_R2
668
669
670 def setup_ad_dns(samdb, secretsdb, names, paths, lp, logger, dns_backend,
671                  os_level, site, dnspass=None, hostip=None, hostip6=None,
672                  targetdir=None):
673     """Provision DNS information (assuming GC role)
674
675     :param samdb: LDB object connected to sam.ldb file
676     :param secretsdb: LDB object connected to secrets.ldb file
677     :param names: Names shortcut
678     :param paths: Paths shortcut
679     :param lp: Loadparm object
680     :param logger: Logger object
681     :param dns_backend: Type of DNS backend
682     :param os_level: Functional level (treated as os level)
683     :param site: Site to create hostnames in
684     :param dnspass: Password for bind's DNS account
685     :param hostip: IPv4 address
686     :param hostip6: IPv6 address
687     :param targetdir: Target directory for creating DNS-related files for BIND9
688     """
689
690     if not is_valid_dns_backend(dns_backend):
691         raise Exception("Invalid dns backend: %r" % dns_backend)
692
693     if not is_valid_os_level(os_level):
694         raise Exception("Invalid os level: %r" % os_level)
695
696     if dns_backend is "NONE":
697         logger.info("No DNS backend set, not configuring DNS")
698         return
699
700     # If dns_backend is BIND9_FLATFILE
701     #   Populate only CN=MicrosoftDNS,CN=System,<DOMAINDN>
702     #
703     # If dns_backend is SAMBA_INTERNAL or BIND9_DLZ
704     #   Populate DNS partitions
705
706     # If os_level < 2003 (DS_DOMAIN_FUNCTION_2000)
707     #   All dns records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
708     #
709     # If os_level >= 2003 (DS_DOMAIN_FUNCTION_2003, DS_DOMAIN_FUNCTION_2008,
710     #                        DS_DOMAIN_FUNCTION_2008_R2)
711     #   Root server records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
712     #   Domain records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
713     #   Domain records are in CN=MicrosoftDNS,DC=DomainDnsZones,<DOMAINDN>
714     #   Forest records are in CN=MicrosoftDNS,DC=ForestDnsZones,<DOMAINDN>
715
716     domaindn = names.domaindn
717     forestdn = samdb.get_root_basedn().get_linearized()
718
719     dnsdomain = names.dnsdomain.lower()
720     dnsforest = dnsdomain
721
722     hostname = names.netbiosname.lower()
723
724     domainguid = get_domainguid(samdb, domaindn)
725     ntdsguid = get_ntdsguid(samdb, domaindn)
726
727     # Add dns accounts (DnsAdmins, DnsUpdateProxy) in domain
728     logger.info("Adding DNS accounts")
729     add_dns_accounts(samdb, domaindn)
730
731     logger.info("Populating CN=MicrosoftDNS,CN=System,%s" % domaindn)
732
733     # Set up MicrosoftDNS container
734     add_dns_container(samdb, domaindn, "CN=System")
735
736     # Add root servers
737     add_rootservers(samdb, domaindn, "CN=System")
738
739     if os_level == DS_DOMAIN_FUNCTION_2000:
740
741         # Add domain record
742         add_domain_record(samdb, domaindn, "CN=System", dnsdomain)
743
744         # Add DNS records for a DC in domain
745         add_dc_domain_records(samdb, domaindn, "CN=System", site, dnsdomain,
746                                 hostname, hostip, hostip6)
747
748     elif dns_backend in ("SAMBA_INTERNAL", "BIND9_DLZ") and \
749             os_level >= DS_DOMAIN_FUNCTION_2003:
750
751         # Set up additional partitions (DomainDnsZones, ForstDnsZones)
752         logger.info("Creating DomainDnsZones and ForestDnsZones partitions")
753         setup_dns_partitions(samdb, domaindn, forestdn, names.configdn, names.serverdn)
754
755         ##### Set up DC=DomainDnsZones,<DOMAINDN>
756         logger.info("Populating DomainDnsZones partition")
757
758         # Set up MicrosoftDNS container
759         add_dns_container(samdb, domaindn, "DC=DomainDnsZones")
760
761         # Add rootserver records
762         add_rootservers(samdb, domaindn, "DC=DomainDnsZones")
763
764         # Add domain record
765         add_domain_record(samdb, domaindn, "DC=DomainDnsZones", dnsdomain)
766
767         # Add DNS records for a DC in domain
768         add_dc_domain_records(samdb, domaindn, "DC=DomainDnsZones", site, dnsdomain,
769                                 hostname, hostip, hostip6)
770
771         ##### Set up DC=ForestDnsZones,<DOMAINDN>
772         logger.info("Populating ForestDnsZones partition")
773
774         # Set up MicrosoftDNS container
775         add_dns_container(samdb, forestdn, "DC=ForestDnsZones")
776
777         # Add _msdcs record
778         add_msdcs_record(samdb, forestdn, "DC=ForestDnsZones", dnsforest)
779
780         # Add DNS records for a DC in forest
781         add_dc_msdcs_records(samdb, forestdn, "DC=ForestDnsZones", site, dnsforest,
782                                 hostname, hostip, hostip6, domainguid, ntdsguid)
783
784     if dns_backend.startswith("BIND9_"):
785         secretsdb_setup_dns(secretsdb, names,
786                             paths.private_dir, realm=names.realm,
787                             dnsdomain=names.dnsdomain,
788                             dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
789
790         create_dns_dir(logger, paths)
791
792         # Only make a zone file on the first DC, it should be
793         # replicated with DNS replication
794         if dns_backend == "BIND9_FLATFILE":
795             create_zone_file(lp, logger, paths, targetdir, site=site,
796                              dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
797                              hostname=names.hostname, realm=names.realm,
798                              domainguid=domainguid, ntdsguid=names.ntdsguid)
799
800         create_named_conf(paths, realm=names.realm,
801                           dnsdomain=names.dnsdomain, dns_backend=dns_backend)
802
803         create_named_txt(paths.namedtxt,
804                          realm=names.realm, dnsdomain=names.dnsdomain,
805                          dnsname = "%s.%s" % (names.hostname, names.dnsdomain),
806                          private_dir=paths.private_dir,
807                          keytab_name=paths.dns_keytab)
808         logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
809         logger.info("and %s for further documentation required for secure DNS "
810                     "updates", paths.namedtxt)