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