1 # Unix SMB/CIFS implementation.
2 # backend code for provisioning DNS for a Samba4 server
4 # Copyright (C) Kai Blin <kai@samba.org> 2011
5 # Copyright (C) Amitay Isaacs <amitay@gmail.com> 2011
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.
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.
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/>.
21 """DNS-related provisioning"""
28 from base64 import b64encode
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
39 from base64 import b64encode
40 from samba.provision.descriptor import (
41 get_domain_descriptor,
42 get_dns_partition_descriptor
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)
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)
56 def setup_ldb(ldb, ldif_path, subst_vars):
57 """Import a LDIF a file into a LDB handle, optionally substituting
60 :note: Either all LDIF data will be added or none (using transactions).
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.
66 assert ldb is not None
67 ldb.transaction_start()
69 add_ldif(ldb, ldif_path, subst_vars)
71 ldb.transaction_cancel()
74 ldb.transaction_commit()
77 """Return an absolute path to the provision tempate file specified by file"""
78 return os.path.join(samba.param.setup_dir(), file)
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]))
85 def get_ntdsguid(samdb, domaindn):
86 configdn = samdb.get_config_basedn()
88 res1 = samdb.search(base="OU=Domain Controllers,%s" % domaindn, scope=ldb.SCOPE_ONELEVEL,
89 attrs=["dNSHostName"])
91 res2 = samdb.search(expression="serverReference=%s" % res1[0].dn, base=configdn)
93 res3 = samdb.search(base="CN=NTDS Settings,%s" % res2[0].dn, scope=ldb.SCOPE_BASE,
95 ntdsguid = str(ndr_unpack(misc.GUID, res3[0]["objectGUID"][0]))
98 def get_dnsadmins_sid(samdb, domaindn):
99 res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % domaindn, scope=ldb.SCOPE_BASE,
101 dnsadmins_sid = ndr_unpack(security.dom_sid, res[0]["objectSid"][0])
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
109 self.dwSerial = serial
110 self.dwTtlSeconds = ttl
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
118 self.dwSerial = serial
119 self.dwTtlSeconds = ttl
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
127 self.dwSerial = serial
128 self.dwTtlSeconds = ttl
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
136 self.dwSerial = serial
137 self.dwTtlSeconds = ttl
138 self.data = dns_server
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
146 self.dwSerial = serial
147 self.dwTtlSeconds = ttl
150 soa.refresh = refresh
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
163 self.dwSerial = serial
164 self.dwTtlSeconds = ttl
166 srv.nameTarget = target
168 srv.wPriority = priority
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)
182 domainzone_guid = get_domainguid(samdb, domainzone_dn)
183 forestzone_guid = get_domainguid(samdb, forestzone_dn)
185 domainzone_guid = str(uuid.uuid4())
186 forestzone_guid = str(uuid.uuid4())
188 domainzone_dns = ldb.Dn(samdb, domainzone_dn).canonical_ex_str().strip()
189 forestzone_dns = ldb.Dn(samdb, forestzone_dn).canonical_ex_str().strip()
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,
202 modify_ldif(samdb, "provision_dnszones_modify.ldif", {
203 "CONFIGDN": configdn,
204 "SERVERDN": serverdn,
205 "DOMAINZONE_DN": domainzone_dn,
206 "FORESTZONE_DN": forestzone_dn,
210 def add_dns_accounts(samdb, domaindn):
211 add_ldif(samdb, "provision_dns_accounts_add.ldif", {
212 "DOMAINDN": domaindn,
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)
229 def add_rootservers(samdb, domaindn, prefix):
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"
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"
253 container_dn = "DC=RootDNSServers,CN=MicrosoftDNS,%s,%s" % (prefix, domaindn)
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")
261 # Add DC=@,DC=RootDNSServers,CN=MicrosoftDNS,<PREFIX>,<DOMAINDN>
263 for rserver in rootservers:
264 record.append(ndr_pack(NSRecord(rserver, serial=0, ttl=0, rank=dnsp.DNS_RANK_ROOT_HINT)))
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")
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")
282 def add_at_record(samdb, container_dn, prefix, hostname, dnsdomain, hostip, hostip6):
284 fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
289 at_soa_record = SOARecord(fqdn_hostname, "hostmaster.%s" % dnsdomain)
290 at_records.append(ndr_pack(at_soa_record))
293 at_ns_record = NSRecord(fqdn_hostname)
294 at_records.append(ndr_pack(at_ns_record))
296 if hostip is not None:
298 at_a_record = ARecord(hostip)
299 at_records.append(ndr_pack(at_a_record))
301 if hostip6 is not None:
303 at_aaaa_record = AAAARecord(hostip6)
304 at_records.append(ndr_pack(at_aaaa_record))
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")
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")
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")
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")
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")
339 def add_host_record(samdb, container_dn, prefix, hostip, hostip6):
342 a_record = ARecord(hostip)
343 host_records.append(ndr_pack(a_record))
345 aaaa_record = AAAARecord(hostip6)
346 host_records.append(ndr_pack(aaaa_record))
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")
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)" \
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)" \
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)
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"]
382 def add_dc_domain_records(samdb, domaindn, prefix, site, dnsdomain, hostname, hostip, hostip6):
384 fqdn_hostname = "%s.%s" % (hostname, dnsdomain)
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))
391 add_at_record(samdb, domain_container_dn, "DC=@", hostname, dnsdomain, hostip, hostip6)
393 # DC=<HOSTNAME> record
394 add_host_record(samdb, domain_container_dn, "DC=%s" % hostname, hostip, hostip6)
396 # DC=_kerberos._tcp record
397 add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp", fqdn_hostname, 88)
399 # DC=_kerberos._tcp.<SITENAME>._sites record
400 add_srv_record(samdb, domain_container_dn, "DC=_kerberos._tcp.%s._sites" % site,
403 # DC=_kerberos._udp record
404 add_srv_record(samdb, domain_container_dn, "DC=_kerberos._udp", fqdn_hostname, 88)
406 # DC=_kpasswd._tcp record
407 add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._tcp", fqdn_hostname, 464)
409 # DC=_kpasswd._udp record
410 add_srv_record(samdb, domain_container_dn, "DC=_kpasswd._udp", fqdn_hostname, 464)
412 # DC=_ldap._tcp record
413 add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp", fqdn_hostname, 389)
415 # DC=_ldap._tcp.<SITENAME>._sites record
416 add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites" % site,
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
422 # Assumption: current DC is GC and add all the entries
425 add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp", fqdn_hostname, 3268)
427 # DC=_gc._tcp.<SITENAME>,_sites record
428 add_srv_record(samdb, domain_container_dn, "DC=_gc._tcp.%s._sites" % site, fqdn_hostname, 3268)
431 add_ns_glue_record(samdb, domain_container_dn, "DC=_msdcs", fqdn_hostname)
433 # FIXME: Following entries are added only if DomainDnsZones and ForestDnsZones partitions
436 # Assumption: Additional entries won't hurt on os_level = 2000
438 # DC=_ldap._tcp.<SITENAME>._sites.DomainDnsZones
439 add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites.DomainDnsZones" % site,
442 # DC=_ldap._tcp.<SITENAME>._sites.ForestDnsZones
443 add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.%s._sites.ForestDnsZones" % site,
446 # DC=_ldap._tcp.DomainDnsZones
447 add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.DomainDnsZones",
450 # DC=_ldap._tcp.ForestDnsZones
451 add_srv_record(samdb, domain_container_dn, "DC=_ldap._tcp.ForestDnsZones",
455 add_host_record(samdb, domain_container_dn, "DC=DomainDnsZones", hostip, hostip6)
458 add_host_record(samdb, domain_container_dn, "DC=ForestDnsZones", hostip, hostip6)
461 def add_dc_msdcs_records(samdb, forestdn, prefix, site, dnsforest, hostname,
462 hostip, hostip6, domainguid, ntdsguid):
464 fqdn_hostname = "%s.%s" % (hostname, dnsforest)
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))
471 add_at_record(samdb, forest_container_dn, "DC=@", hostname, dnsforest, None, None)
473 # DC=_kerberos._tcp.dc record
474 add_srv_record(samdb, forest_container_dn, "DC=_kerberos._tcp.dc", fqdn_hostname, 88)
476 # DC=_kerberos._tcp.<SITENAME>._sites.dc record
477 add_srv_record(samdb, forest_container_dn, "DC=_kerberos._tcp.%s._sites.dc" % site,
480 # DC=_ldap._tcp.dc record
481 add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.dc", fqdn_hostname, 389)
483 # DC=_ldap._tcp.<SITENAME>._sites.dc record
484 add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.dc" % site,
487 # DC=_ldap._tcp.<SITENAME>._sites.gc record
488 add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s._sites.gc" % site,
491 # DC=_ldap._tcp.gc record
492 add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.gc", fqdn_hostname, 3268)
494 # DC=_ldap._tcp.pdc record
495 add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.pdc", fqdn_hostname, 389)
498 add_host_record(samdb, forest_container_dn, "DC=gc", hostip, hostip6)
500 # DC=_ldap._tcp.<DOMAINGUID>.domains record
501 add_srv_record(samdb, forest_container_dn, "DC=_ldap._tcp.%s.domains" % domainguid,
505 add_cname_record(samdb, forest_container_dn, "DC=%s" % ntdsguid, fqdn_hostname)
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.
512 :param secretsdb: Ldb Handle to the secrets database
513 :param names: Names shortcut
514 :param machinepass: Machine password
517 os.unlink(os.path.join(private_dir, dns_keytab_path))
521 setup_ldb(secretsdb, setup_path("secrets_dns.ldif"), {
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())
532 def create_dns_dir(logger, paths):
533 """Write out a DNS zone file, from the info in the current database.
535 :param logger: Logger object
536 :param paths: paths object
538 dns_dir = os.path.dirname(paths.dns)
541 shutil.rmtree(dns_dir, True)
545 os.mkdir(dns_dir, 0770)
547 if paths.bind_gid is not None:
549 os.chown(dns_dir, -1, paths.bind_gid)
550 # chmod needed to cope with umask
551 os.chmod(dns_dir, 0770)
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))
558 def create_zone_file(lp, logger, paths, targetdir, dnsdomain,
559 hostip, hostip6, hostname, realm, domainguid,
561 """Write out a DNS zone file, from the info in the current database.
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.
573 assert isinstance(domainguid, str)
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
580 hostip6_base_line = ""
581 hostip6_host_line = ""
582 gc_msdcs_ip6_line = ""
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
589 hostip_base_line = ""
590 hostip_host_line = ""
591 gc_msdcs_ip_line = ""
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"))
598 setup_file(setup_path("provision.zone"), paths.dns, {
599 "HOSTNAME": hostname,
600 "DNSDOMAIN": dnsdomain,
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"),
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,
614 if paths.bind_gid is not None:
616 os.chown(paths.dns, -1, paths.bind_gid)
617 # chmod needed to cope with umask
618 os.chmod(paths.dns, 0664)
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))
624 if targetdir is None:
625 os.system(rndc + " unfreeze " + lp.get("realm"))
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
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"
642 # Copy config, schema partitions, create empty domain partition
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))
653 logger.error("Failed to setup database for BIND, AD based DNS cannot be used")
656 # Link metadata and dns partitions
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))
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))
673 logger.error("Failed to setup database for BIND, AD based DNS cannot be used")
676 # Fill the basedn and @OPTION records in domain partition
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)
688 logger.error("Failed to setup database for BIND, AD based DNS cannot be used")
691 # Give bind read/write permissions dns partitions
692 if paths.bind_gid is not None:
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)
713 if not os.environ.has_key('SAMBA_SELFTEST'):
714 logger.error("Failed to set permissions to sam.ldb* files, fix manually")
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""")
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)
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).
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
740 if dns_backend == "BIND9_FLATFILE":
741 setup_file(setup_path("named.conf"), paths.namedconf, {
742 "DNSDOMAIN": dnsdomain,
744 "ZONE_FILE": paths.dns,
745 "REALM_WC": "*." + ".".join(realm.split(".")[1:]),
746 "NAMED_CONF": paths.namedconf,
747 "NAMED_CONF_UPDATE": paths.namedconf_update
750 setup_file(setup_path("named.conf.update"), paths.namedconf_update)
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,
762 def create_named_txt(path, realm, dnsdomain, dnsname, private_dir,
764 """Write out a file containing zone statements suitable for inclusion in a
765 named.conf file (including GSS-TSIG configuration).
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
773 setup_file(setup_path("named.txt"), path, {
774 "DNSDOMAIN": dnsdomain,
777 "DNS_KEYTAB": keytab_name,
778 "DNS_KEYTAB_ABS": os.path.join(private_dir, keytab_name),
779 "PRIVATE_DIR": private_dir
783 def is_valid_dns_backend(dns_backend):
784 return dns_backend in ("BIND9_FLATFILE", "BIND9_DLZ", "SAMBA_INTERNAL", "NONE")
787 def is_valid_os_level(os_level):
788 return DS_DOMAIN_FUNCTION_2000 <= os_level <= DS_DOMAIN_FUNCTION_2008_R2
791 def setup_ad_dns(samdb, secretsdb, domainsid, names, paths, lp, logger, dns_backend,
792 os_level, site, dnspass=None, hostip=None, hostip6=None,
794 """Provision DNS information (assuming GC role)
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
811 if not is_valid_dns_backend(dns_backend):
812 raise Exception("Invalid dns backend: %r" % dns_backend)
814 if not is_valid_os_level(os_level):
815 raise Exception("Invalid os level: %r" % os_level)
817 if dns_backend is "NONE":
818 logger.info("No DNS backend set, not configuring DNS")
821 # If dns_backend is BIND9_FLATFILE
822 # Populate only CN=MicrosoftDNS,CN=System,<DOMAINDN>
824 # If dns_backend is SAMBA_INTERNAL or BIND9_DLZ
825 # Populate DNS partitions
827 # If os_level < 2003 (DS_DOMAIN_FUNCTION_2000)
828 # All dns records are in CN=MicrosoftDNS,CN=System,<DOMAINDN>
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>
837 domaindn = names.domaindn
838 forestdn = samdb.get_root_basedn().get_linearized()
840 dnsdomain = names.dnsdomain.lower()
841 dnsforest = dnsdomain
843 hostname = names.netbiosname.lower()
845 domainguid = get_domainguid(samdb, domaindn)
846 ntdsguid = get_ntdsguid(samdb, domaindn)
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)
853 logger.info("Populating CN=MicrosoftDNS,CN=System,%s" % domaindn)
855 # Set up MicrosoftDNS container
856 add_dns_container(samdb, domaindn, "CN=System", domainsid, dnsadmins_sid)
859 add_rootservers(samdb, domaindn, "CN=System")
861 if os_level == DS_DOMAIN_FUNCTION_2000:
864 add_domain_record(samdb, domaindn, "CN=System", dnsdomain, domainsid, dnsadmins_sid)
866 # Add DNS records for a DC in domain
867 add_dc_domain_records(samdb, domaindn, "CN=System", site, dnsdomain,
868 hostname, hostip, hostip6)
870 elif dns_backend in ("SAMBA_INTERNAL", "BIND9_DLZ") and \
871 os_level >= DS_DOMAIN_FUNCTION_2003:
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)
878 ##### Set up DC=DomainDnsZones,<DOMAINDN>
879 logger.info("Populating DomainDnsZones partition")
881 # Set up MicrosoftDNS container
882 add_dns_container(samdb, domaindn, "DC=DomainDnsZones", domainsid, dnsadmins_sid)
884 # Add rootserver records
885 add_rootservers(samdb, domaindn, "DC=DomainDnsZones")
888 add_domain_record(samdb, domaindn, "DC=DomainDnsZones", dnsdomain, domainsid,
891 # Add DNS records for a DC in domain
892 add_dc_domain_records(samdb, domaindn, "DC=DomainDnsZones", site, dnsdomain,
893 hostname, hostip, hostip6)
895 ##### Set up DC=ForestDnsZones,<DOMAINDN>
896 logger.info("Populating ForestDnsZones partition")
898 # Set up MicrosoftDNS container
899 add_dns_container(samdb, forestdn, "DC=ForestDnsZones", domainsid, dnsadmins_sid)
902 add_msdcs_record(samdb, forestdn, "DC=ForestDnsZones", dnsforest)
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)
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)
914 create_dns_dir(logger, paths)
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)
924 if dns_backend == "BIND9_DLZ" and os_level >= DS_DOMAIN_FUNCTION_2003:
925 create_samdb_copy(logger, paths, names, domainsid, domainguid)
927 create_named_conf(paths, realm=names.realm,
928 dnsdomain=names.dnsdomain, dns_backend=dns_backend)
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)