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