4 # update our DNS names using TSIG-GSS
6 # Copyright (C) Andrew Tridgell 2010
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
28 # ensure we get messages out immediately, so they get in the samba logs,
29 # and don't get swallowed by a timeout
30 os.environ['PYTHONUNBUFFERED'] = '1'
32 # forcing GMT avoids a problem in some timezones with kerberos. Both MIT and
33 # heimdal can get mutual authentication errors due to the 24 second difference
34 # between UTC and GMT when using some zone files (eg. the PDT zone from
36 os.environ["TZ"] = "GMT"
38 # Find right directory when running from source tree
39 sys.path.insert(0, "bin/python")
43 from samba import getopt as options
44 from ldb import SCOPE_BASE
45 from samba import dsdb
46 from samba.auth import system_session
47 from samba.samdb import SamDB
48 from samba.dcerpc import netlogon, winbind
49 from samba.netcmd.dns import cmd_dns
50 from samba import gensec
51 from samba.kcc import kcc_utils
52 from samba.common import get_string
55 from samba.dnsresolver import DNSResolver
63 parser = optparse.OptionParser("samba_dnsupdate [options]")
64 sambaopts = options.SambaOptions(parser)
65 parser.add_option_group(sambaopts)
66 parser.add_option_group(options.VersionOptions(parser))
67 parser.add_option("--verbose", action="store_true")
68 parser.add_option("--use-samba-tool", action="store_true", help="Use samba-tool to make updates over RPC, rather than over DNS")
69 parser.add_option("--use-nsupdate", action="store_true", help="Use nsupdate command to make updates over DNS (default, if kinit successful)")
70 parser.add_option("--all-names", action="store_true")
71 parser.add_option("--all-interfaces", action="store_true")
72 parser.add_option("--current-ip", action="append", help="IP address to update DNS to match (helpful if behind NAT, valid multiple times, defaults to values from interfaces=)")
73 parser.add_option("--rpc-server-ip", type="string", help="IP address of server to use with samba-tool (defaults to first --current-ip)")
74 parser.add_option("--use-file", type="string", help="Use a file, rather than real DNS calls")
75 parser.add_option("--update-list", type="string", help="Add DNS names from the given file")
76 parser.add_option("--update-cache", type="string", help="Cache database of already registered records")
77 parser.add_option("--fail-immediately", action='store_true', help="Exit on first failure")
78 parser.add_option("--no-credentials", dest='nocreds', action='store_true', help="don't try and get credentials")
79 parser.add_option("--no-substitutions", dest='nosubs', action='store_true', help="don't try and expands variables in file specified by --update-list")
84 opts, args = parser.parse_args()
90 lp = sambaopts.get_loadparm()
92 domain = lp.get("realm")
93 host = lp.get("netbios name")
94 all_interfaces = opts.all_interfaces
96 IPs = opts.current_ip or samba.interface_ips(lp, bool(all_interfaces)) or []
98 nsupdate_cmd = lp.get('nsupdate command')
99 dns_zone_scavenging = lp.get("dns zone scavenging")
102 print("No IP interfaces - skipping DNS updates\n")
106 rpc_server_ip = opts.rpc_server_ip or IPs[0]
108 IP6s = [ip for ip in IPs if ':' in ip]
109 IP4s = [ip for ip in IPs if ':' not in ip]
111 smb_conf = sambaopts.get_loadparm_path()
114 print("IPs: %s" % IPs)
116 def get_possible_rw_dns_server(creds, domain):
117 """Get a list of possible read-write DNS servers, starting with
118 the SOA. The SOA is the correct answer, but old Samba domains
119 (4.6 and prior) do not maintain this value, so add NS servers
122 ans_soa = check_one_dns_name(domain, 'SOA')
123 # Actually there is only one
124 hosts_soa = [str(a.mname).rstrip('.') for a in ans_soa]
126 # This is not strictly legit, but old Samba domains may have an
127 # unmaintained SOA record, so go for any NS that we can get a
129 ans_ns = check_one_dns_name(domain, 'NS')
130 # Actually there is only one
131 hosts_ns = [str(a.target).rstrip('.') for a in ans_ns]
133 return hosts_soa + hosts_ns
135 def get_krb5_rw_dns_server(creds, domain):
136 """Get a list of read-write DNS servers that we can obtain a ticket
137 to, starting with the SOA. The SOA is the correct answer, but
138 old Samba domains (4.6 and prior) do not maintain this value,
139 so continue with the NS servers as well until we get one that
140 the KDC will issue a ticket to.
143 rw_dns_servers = get_possible_rw_dns_server(creds, domain)
144 # Actually there is only one
145 for i, target_hostname in enumerate(rw_dns_servers):
147 settings["lp_ctx"] = lp
148 settings["target_hostname"] = target_hostname
150 gensec_client = gensec.Security.start_client(settings)
151 gensec_client.set_credentials(creds)
152 gensec_client.set_target_service("DNS")
153 gensec_client.set_target_hostname(target_hostname)
154 gensec_client.want_feature(gensec.FEATURE_SEAL)
155 gensec_client.start_mech_by_sasl_name("GSSAPI")
156 server_to_client = b""
158 (client_finished, client_to_server) = gensec_client.update(server_to_client)
160 print("Successfully obtained Kerberos ticket to DNS/%s as %s" \
161 % (target_hostname, creds.get_username()))
162 return target_hostname
164 # Only raise an exception if they all failed
165 if i == len(rw_dns_servers) - 1:
168 def get_credentials(lp):
169 """# get credentials if we haven't got them already."""
170 from samba import credentials
172 creds = credentials.Credentials()
174 creds.set_machine_account(lp)
175 creds.set_krb_forwardable(credentials.NO_KRB_FORWARDABLE)
176 (tmp_fd, ccachename) = tempfile.mkstemp()
178 if opts.use_file is not None:
181 creds.get_named_ccache(lp, ccachename)
183 # Now confirm we can get a ticket to the DNS server
184 get_krb5_rw_dns_server(creds, sub_vars['DNSDOMAIN'] + '.')
187 except RuntimeError as e:
188 os.unlink(ccachename)
192 class dnsobj(object):
193 """an object to hold a parsed DNS line"""
195 def __init__(self, string_form):
196 list = string_form.split()
198 raise Exception("Invalid DNS entry %r" % string_form)
202 self.existing_port = None
203 self.existing_weight = None
204 self.existing_cname_target = None
213 self.nameservers = []
214 if self.type == 'SRV':
216 raise Exception("Invalid DNS entry %r" % string_form)
219 elif self.type in ['A', 'AAAA']:
220 self.ip = list[2] # usually $IP, which gets replaced
221 elif self.type == 'CNAME':
223 elif self.type == 'NS':
226 raise Exception("Received unexpected DNS reply of type %s: %s" % (self.type, string_form))
230 return "%s %s %s" % (self.type, self.name, self.ip)
231 if self.type == "AAAA":
232 return "%s %s %s" % (self.type, self.name, self.ip)
233 if self.type == "SRV":
234 return "%s %s %s %s" % (self.type, self.name, self.dest, self.port)
235 if self.type == "CNAME":
236 return "%s %s %s" % (self.type, self.name, self.dest)
237 if self.type == "NS":
238 return "%s %s %s" % (self.type, self.name, self.dest)
241 def parse_dns_line(line, sub_vars):
242 """parse a DNS line from."""
243 if line.startswith("SRV _ldap._tcp.pdc._msdcs.") and not samdb.am_pdc():
244 # We keep this as compat to the dns_update_list of 4.0/4.1
246 print("Skipping PDC entry (%s) as we are not a PDC" % line)
248 subline = samba.substitute_var(line, sub_vars)
249 if subline == '' or subline[0] == "#":
251 return dnsobj(subline)
254 def hostname_match(h1, h2):
255 """see if two hostnames match."""
258 return h1.lower().rstrip('.') == h2.lower().rstrip('.')
260 def get_resolver(d=None):
261 resolv_conf = os.getenv('RESOLV_CONF', default='/etc/resolv.conf')
262 resolver = DNSResolver(filename=resolv_conf, configure=True)
264 if d is not None and d.nameservers != []:
265 resolver.nameservers = d.nameservers
269 def check_one_dns_name(name, name_type, d=None):
270 resolver = get_resolver(d)
271 if d and not d.nameservers:
272 d.nameservers = resolver.nameservers
273 # dns.resolver.Answer
274 return resolver.resolve(name, name_type)
276 def check_dns_name(d):
277 """check that a DNS entry exists."""
278 normalised_name = d.name.rstrip('.') + '.'
280 print("Looking for DNS entry %s as %s" % (d, normalised_name))
282 if opts.use_file is not None:
284 dns_file = open(opts.use_file, "r")
289 for line in dns_file:
291 if line == '' or line[0] == "#":
293 if line.lower() == str(d).lower():
298 ans = check_one_dns_name(normalised_name, d.type, d)
299 except dns.exception.Timeout:
300 raise Exception("Timeout while waiting to contact a working DNS server while looking for %s as %s" % (d, normalised_name))
301 except dns.resolver.NoNameservers:
302 raise Exception("Unable to contact a working DNS server while looking for %s as %s" % (d, normalised_name))
303 except dns.resolver.NXDOMAIN:
305 print("The DNS entry %s, queried as %s does not exist" % (d, normalised_name))
307 except dns.resolver.NoAnswer:
309 print("The DNS entry %s, queried as %s does not hold this record type" % (d, normalised_name))
311 except dns.exception.DNSException:
312 raise Exception("Failure while trying to resolve %s as %s" % (d, normalised_name))
313 if d.type in ['A', 'AAAA']:
314 # we need to be sure that our IP is there
316 if str(rdata) == str(d.ip):
318 elif d.type == 'CNAME':
319 for i in range(len(ans)):
320 if hostname_match(ans[i].target, d.dest):
323 d.existing_cname_target = str(ans[i].target)
325 for i in range(len(ans)):
326 if hostname_match(ans[i].target, d.dest):
328 elif d.type == 'SRV':
331 print("Checking %s against %s" % (rdata, d))
332 if hostname_match(rdata.target, d.dest):
333 if str(rdata.port) == str(d.port):
336 d.existing_port = str(rdata.port)
337 d.existing_weight = str(rdata.weight)
340 print("Lookup of %s succeeded, but we failed to find a matching DNS entry for %s" % (normalised_name, d))
345 def get_subst_vars(samdb):
346 """get the list of substitution vars."""
350 vars['DNSDOMAIN'] = samdb.domain_dns_name()
351 vars['DNSFOREST'] = samdb.forest_dns_name()
352 vars['HOSTNAME'] = samdb.host_dns_name()
353 vars['NTDSGUID'] = samdb.get_ntds_GUID()
354 vars['SITE'] = samdb.server_site_name()
355 res = samdb.search(base=samdb.get_default_basedn(), scope=SCOPE_BASE, attrs=["objectGUID"])
356 guid = samdb.schema_format_value("objectGUID", res[0]['objectGUID'][0])
357 vars['DOMAINGUID'] = get_string(guid)
360 vars['IF_RWDC'] = "# "
361 vars['IF_RODC'] = "# "
362 vars['IF_PDC'] = "# "
364 vars['IF_RWGC'] = "# "
365 vars['IF_ROGC'] = "# "
366 vars['IF_DNS_DOMAIN'] = "# "
367 vars['IF_RWDNS_DOMAIN'] = "# "
368 vars['IF_RODNS_DOMAIN'] = "# "
369 vars['IF_DNS_FOREST'] = "# "
370 vars['IF_RWDNS_FOREST'] = "# "
371 vars['IF_R0DNS_FOREST'] = "# "
373 am_rodc = samdb.am_rodc()
382 # check if we "are DNS server"
383 res = samdb.search(base=samdb.get_config_basedn(),
384 expression='(objectguid=%s)' % vars['NTDSGUID'],
385 attrs=["options", "msDS-hasMasterNCs"])
388 if "options" in res[0]:
389 options = int(res[0]["options"][0])
390 if (options & dsdb.DS_NTDSDSA_OPT_IS_GC) != 0:
397 basedn = str(samdb.get_default_basedn())
398 forestdn = str(samdb.get_root_basedn())
400 if "msDS-hasMasterNCs" in res[0]:
401 for e in res[0]["msDS-hasMasterNCs"]:
402 if str(e) == "DC=DomainDnsZones,%s" % basedn:
403 vars['IF_DNS_DOMAIN'] = ""
405 vars['IF_RODNS_DOMAIN'] = ""
407 vars['IF_RWDNS_DOMAIN'] = ""
408 if str(e) == "DC=ForestDnsZones,%s" % forestdn:
409 vars['IF_DNS_FOREST'] = ""
411 vars['IF_RODNS_FOREST'] = ""
413 vars['IF_RWDNS_FOREST'] = ""
418 def call_nsupdate(d, op="add"):
419 """call nsupdate for an entry."""
420 global ccachename, nsupdate_cmd, krb5conf
422 assert(op in ["add", "delete"])
424 if opts.use_file is not None:
426 print("Use File instead of nsupdate for %s (%s)" % (d, op))
429 rfile = open(opts.use_file, 'r+')
432 open(opts.use_file, 'w+').close()
433 # Open it for reading again, in case someone else got to it first
434 rfile = open(opts.use_file, 'r+')
435 fcntl.lockf(rfile, fcntl.LOCK_EX)
436 (file_dir, file_name) = os.path.split(opts.use_file)
437 (tmp_fd, tmpfile) = tempfile.mkstemp(dir=file_dir, prefix=file_name, suffix="XXXXXX")
438 wfile = os.fdopen(tmp_fd, 'a')
441 l = parse_dns_line(line, {})
442 if str(l).lower() == str(d).lower():
446 wfile.write(str(d)+"\n")
449 os.rename(tmpfile, opts.use_file)
453 print("Calling nsupdate for %s (%s)" % (d, op))
455 normalised_name = d.name.rstrip('.') + '.'
457 (tmp_fd, tmpfile) = tempfile.mkstemp()
458 f = os.fdopen(tmp_fd, 'w')
460 resolver = get_resolver(d)
462 # Local the zone for this name
463 zone = dns.resolver.zone_for_name(normalised_name,
466 # Now find the SOA, or if we can't get a ticket to the SOA,
467 # any server with an NS record we can get a ticket for.
469 # Thanks to the Kerberos Credentials cache this is not
470 # expensive inside the loop
471 server = get_krb5_rw_dns_server(creds, zone)
472 f.write('server %s\n' % server)
475 f.write("update %s %s %u A %s\n" % (op, normalised_name, default_ttl, d.ip))
477 f.write("update %s %s %u AAAA %s\n" % (op, normalised_name, default_ttl, d.ip))
479 if op == "add" and d.existing_port is not None:
480 f.write("update delete %s SRV 0 %s %s %s\n" % (normalised_name, d.existing_weight,
481 d.existing_port, d.dest))
482 f.write("update %s %s %u SRV 0 100 %s %s\n" % (op, normalised_name, default_ttl, d.port, d.dest))
483 if d.type == "CNAME":
484 f.write("update %s %s %u CNAME %s\n" % (op, normalised_name, default_ttl, d.dest))
486 f.write("update %s %s %u NS %s\n" % (op, normalised_name, default_ttl, d.dest))
492 # Set a bigger MTU size to work around a bug in nsupdate's doio_send()
493 os.environ["SOCKET_WRAPPER_MTU"] = "2000"
497 os.environ["KRB5CCNAME"] = ccachename
499 cmd = nsupdate_cmd[:]
503 env["KRB5_CONFIG"] = krb5conf
505 env["KRB5CCNAME"] = ccachename
506 ret = subprocess.call(cmd, shell=False, env=env)
508 if opts.fail_immediately:
510 print("Failed update with %s" % tmpfile)
512 error_count = error_count + 1
514 print("Failed nsupdate: %d" % ret)
515 except Exception as estr:
516 if opts.fail_immediately:
518 error_count = error_count + 1
520 print("Failed nsupdate: %s : %s" % (str(d), estr))
523 # Let socket_wrapper set the default MTU size
524 os.environ["SOCKET_WRAPPER_MTU"] = "0"
527 def call_samba_tool(d, op="add", zone=None):
528 """call samba-tool dns to update an entry."""
530 assert(op in ["add", "delete"])
532 if (sub_vars['DNSFOREST'] != sub_vars['DNSDOMAIN']) and \
533 sub_vars['DNSFOREST'].endswith('.' + sub_vars['DNSDOMAIN']):
534 print("Refusing to use samba-tool when forest %s is under domain %s" \
535 % (sub_vars['DNSFOREST'], sub_vars['DNSDOMAIN']))
538 print("Calling samba-tool dns for %s (%s)" % (d, op))
540 normalised_name = d.name.rstrip('.') + '.'
542 if normalised_name == (sub_vars['DNSDOMAIN'] + '.'):
544 zone = sub_vars['DNSDOMAIN']
545 elif normalised_name == (sub_vars['DNSFOREST'] + '.'):
547 zone = sub_vars['DNSFOREST']
548 elif normalised_name == ('_msdcs.' + sub_vars['DNSFOREST'] + '.'):
550 zone = '_msdcs.' + sub_vars['DNSFOREST']
552 if not normalised_name.endswith('.' + sub_vars['DNSDOMAIN'] + '.'):
553 print("Not Calling samba-tool dns for %s (%s), %s not in %s" % (d, op, normalised_name, sub_vars['DNSDOMAIN'] + '.'))
555 elif normalised_name.endswith('._msdcs.' + sub_vars['DNSFOREST'] + '.'):
556 zone = '_msdcs.' + sub_vars['DNSFOREST']
558 zone = sub_vars['DNSDOMAIN']
559 len_zone = len(zone)+2
560 short_name = normalised_name[:-len_zone]
562 len_zone = len(zone)+2
563 short_name = normalised_name[:-len_zone]
566 args = [rpc_server_ip, zone, short_name, "A", d.ip]
568 args = [rpc_server_ip, zone, short_name, "AAAA", d.ip]
570 if op == "add" and d.existing_port is not None:
571 print("Not handling modify of existing SRV %s using samba-tool" % d)
573 args = [rpc_server_ip, zone, short_name, "SRV",
574 "%s %s %s %s" % (d.dest, d.port, "0", "100")]
575 if d.type == "CNAME":
576 if d.existing_cname_target is None:
577 args = [rpc_server_ip, zone, short_name, "CNAME", d.dest]
580 args = [rpc_server_ip, zone, short_name, "CNAME",
581 d.existing_cname_target.rstrip('.'), d.dest]
584 args = [rpc_server_ip, zone, short_name, "NS", d.dest]
586 if smb_conf and args:
587 args += ["--configfile=" + smb_conf]
593 print(f'Calling samba-tool dns {op} --use-kerberos off -P {args}')
594 command, args = cmd._resolve("dns", op, "--use-kerberos", "off", "-P", *args)
595 ret = command._run(*args)
597 if opts.fail_immediately:
599 error_count = error_count + 1
601 print("Failed 'samba-tool dns' based update of %s" % (str(d)))
602 except Exception as estr:
603 if opts.fail_immediately:
605 error_count = error_count + 1
607 print("Failed 'samba-tool dns' based update: %s : %s" % (str(d), estr))
611 def cached_irpc_wb(lp):
613 if irpc_wb is not None:
615 irpc_wb = winbind.winbind("irpc:winbind_server", lp)
618 def rodc_dns_update(d, t, op):
619 '''a single DNS update via the RODC netlogon call'''
622 assert(op in ["add", "delete"])
625 print("Calling netlogon RODC update for %s" % d)
628 netlogon.NlDnsLdapAtSite : netlogon.NlDnsInfoTypeNone,
629 netlogon.NlDnsGcAtSite : netlogon.NlDnsDomainNameAlias,
630 netlogon.NlDnsDsaCname : netlogon.NlDnsDomainNameAlias,
631 netlogon.NlDnsKdcAtSite : netlogon.NlDnsInfoTypeNone,
632 netlogon.NlDnsDcAtSite : netlogon.NlDnsInfoTypeNone,
633 netlogon.NlDnsRfc1510KdcAtSite : netlogon.NlDnsInfoTypeNone,
634 netlogon.NlDnsGenericGcAtSite : netlogon.NlDnsDomainNameAlias
637 w = cached_irpc_wb(lp)
638 dns_names = netlogon.NL_DNS_NAME_INFO_ARRAY()
640 name = netlogon.NL_DNS_NAME_INFO()
642 name.dns_domain_info_type = typemap[t]
645 if d.port is not None:
646 name.port = int(d.port)
648 name.dns_register = True
650 name.dns_register = False
651 dns_names.names = [ name ]
652 site_name = sub_vars['SITE']
657 ret_names = w.DsrUpdateReadOnlyServerDnsRecords(site_name, default_ttl, dns_names)
658 if ret_names.names[0].status != 0:
659 print("Failed to set DNS entry: %s (status %u)" % (d, ret_names.names[0].status))
660 error_count = error_count + 1
661 except RuntimeError as reason:
662 print("Error setting DNS entry of type %u: %s: %s" % (t, d, reason))
663 error_count = error_count + 1
666 print("Called netlogon RODC update for %s" % d)
668 if error_count != 0 and opts.fail_immediately:
672 def call_rodc_update(d, op="add"):
673 '''RODCs need to use the netlogon API for nsupdate'''
676 assert(op in ["add", "delete"])
678 # we expect failure for 3268 if we aren't a GC
679 if d.port is not None and int(d.port) == 3268:
682 # map the DNS request to a netlogon update type
684 netlogon.NlDnsLdapAtSite : '_ldap._tcp.${SITE}._sites.${DNSDOMAIN}',
685 netlogon.NlDnsGcAtSite : '_ldap._tcp.${SITE}._sites.gc._msdcs.${DNSDOMAIN}',
686 netlogon.NlDnsDsaCname : '${NTDSGUID}._msdcs.${DNSFOREST}',
687 netlogon.NlDnsKdcAtSite : '_kerberos._tcp.${SITE}._sites.dc._msdcs.${DNSDOMAIN}',
688 netlogon.NlDnsDcAtSite : '_ldap._tcp.${SITE}._sites.dc._msdcs.${DNSDOMAIN}',
689 netlogon.NlDnsRfc1510KdcAtSite : '_kerberos._tcp.${SITE}._sites.${DNSDOMAIN}',
690 netlogon.NlDnsGenericGcAtSite : '_gc._tcp.${SITE}._sites.${DNSFOREST}'
694 subname = samba.substitute_var(map[t], sub_vars)
695 if subname.lower() == d.name.lower():
696 # found a match - do the update
697 rodc_dns_update(d, t, op)
700 print("Unable to map to netlogon DNS update: %s" % d)
703 # get the list of DNS entries we should have
704 dns_update_list = opts.update_list or lp.private_path('dns_update_list')
706 dns_update_cache = opts.update_cache or lp.private_path('dns_update_cache')
709 # only change the krb5.conf if we are not in selftest
710 if 'SOCKET_WRAPPER_DIR' not in os.environ:
711 # use our private krb5.conf to avoid problems with the wrong domain
712 # bind9 nsupdate wants the default domain set
713 krb5conf = lp.private_path('krb5.conf')
714 os.environ['KRB5_CONFIG'] = krb5conf
717 file = open(dns_update_list, "r")
720 print("The specified update list does not exist")
722 print("The server update list was not found, "
723 "and --update-list was not provided.")
732 samdb = SamDB(url=lp.samdb_url(), session_info=system_session(), lp=lp)
734 # get the substitution dictionary
735 sub_vars = get_subst_vars(samdb)
737 # build up a list of update commands to pass to nsupdate
746 rebuild_cache = False
748 cfile = open(dns_update_cache, 'r+')
751 open(dns_update_cache, 'w+').close()
752 # Open it for reading again, in case someone else got to it first
753 cfile = open(dns_update_cache, 'r+')
754 fcntl.lockf(cfile, fcntl.LOCK_EX)
757 if line == '' or line[0] == "#":
759 c = parse_dns_line(line, {})
762 if str(c) not in cache_set:
764 cache_set.add(str(c))
768 site_specific_rec = []
770 # read each line, and check that the DNS name exists
774 if '${SITE}' in line:
775 site_specific_rec.append(line)
777 if line == '' or line[0] == "#":
779 d = parse_dns_line(line, sub_vars)
782 if d.type == 'A' and len(IP4s) == 0:
784 if d.type == 'AAAA' and len(IP6s) == 0:
786 if str(d) not in dup_set:
792 # Perform automatic site coverage by default
795 if not am_rodc and auto_coverage:
796 site_names = kcc_utils.uncovered_sites_to_cover(samdb,
797 samdb.server_site_name())
799 # Duplicate all site specific records for the uncovered site
800 for site in site_names:
801 to_add = [samba.substitute_var(line, {'SITE': site})
802 for line in site_specific_rec]
804 for site_line in to_add:
805 d = parse_dns_line(site_line,
807 if d is not None and str(d) not in dup_set:
811 # now expand the entries, if any are A record with ip set to $IP
812 # then replace with multiple entries, one for each interface IP
818 for i in range(len(IP4s)-1):
824 for i in range(len(IP6s)-1):
829 # now check if the entries already exist on the DNS server
833 if str(c).lower() == str(d).lower():
839 print("need cache add: %s" % d)
840 if dns_zone_scavenging:
841 update_list.append(d)
843 print("scavenging requires update: %s" % d)
845 update_list.append(d)
847 print("force update: %s" % d)
848 elif not check_dns_name(d):
849 update_list.append(d)
851 print("need update: %s" % d)
856 if str(c).lower() == str(d).lower():
863 print("need cache remove: %s" % c)
864 if not opts.all_names and not check_dns_name(c):
866 delete_list.append(c)
868 print("need delete: %s" % c)
870 if len(delete_list) == 0 and len(update_list) == 0 and not rebuild_cache:
872 print("No DNS updates needed")
876 print("%d DNS updates and %d DNS deletes needed" % (len(update_list), len(delete_list)))
878 use_samba_tool = opts.use_samba_tool
879 use_nsupdate = opts.use_nsupdate
881 if (delete_list or update_list) and not opts.nocreds:
883 creds = get_credentials(lp)
884 except RuntimeError as e:
887 if sub_vars['IF_RWDNS_DOMAIN'] == "# ":
893 print("Failed to get Kerberos credentials, falling back to samba-tool: %s" % e)
894 use_samba_tool = True
897 # ask nsupdate to delete entries as needed
898 for d in delete_list:
899 if d.rpc or (not use_nsupdate and use_samba_tool):
901 print("delete (samba-tool): %s" % d)
902 call_samba_tool(d, op="delete", zone=d.zone)
905 if d.name.lower() == domain.lower():
907 print("skip delete (rodc): %s" % d)
909 if d.type not in [ 'A', 'AAAA' ]:
911 print("delete (rodc): %s" % d)
912 call_rodc_update(d, op="delete")
915 print("delete (nsupdate): %s" % d)
916 call_nsupdate(d, op="delete")
919 print("delete (nsupdate): %s" % d)
920 call_nsupdate(d, op="delete")
922 # ask nsupdate to add entries as needed
923 for d in update_list:
924 if d.rpc or (not use_nsupdate and use_samba_tool):
926 print("update (samba-tool): %s" % d)
927 call_samba_tool(d, zone=d.zone)
930 if d.name.lower() == domain.lower():
932 print("skip (rodc): %s" % d)
934 if d.type not in [ 'A', 'AAAA' ]:
936 print("update (rodc): %s" % d)
940 print("update (nsupdate): %s" % d)
944 print("update(nsupdate): %s" % d)
948 print("Rebuilding cache at %s" % dns_update_cache)
949 (file_dir, file_name) = os.path.split(dns_update_cache)
950 (tmp_fd, tmpfile) = tempfile.mkstemp(dir=file_dir, prefix=file_name, suffix="XXXXXX")
951 wfile = os.fdopen(tmp_fd, 'a')
954 print("Adding %s to %s" % (str(d), file_name))
955 wfile.write(str(d)+"\n")
957 os.rename(tmpfile, dns_update_cache)
959 # delete the ccache if we created it
960 if ccachename is not None:
961 os.unlink(ccachename)
964 print("Failed update of %u entries" % error_count)
965 sys.exit(error_count)