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
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
52 samba.ensure_third_party_module("dns", "dnspython")
60 parser = optparse.OptionParser("samba_dnsupdate")
61 sambaopts = options.SambaOptions(parser)
62 parser.add_option_group(sambaopts)
63 parser.add_option_group(options.VersionOptions(parser))
64 parser.add_option("--verbose", action="store_true")
65 parser.add_option("--use-samba-tool", action="store_true", help="Use samba-tool to make updates over RPC, rather than over DNS")
66 parser.add_option("--use-nsupdate", action="store_true", help="Use nsupdate command to make updates over DNS (default, if kinit successful)")
67 parser.add_option("--all-names", action="store_true")
68 parser.add_option("--all-interfaces", action="store_true")
69 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=)")
70 parser.add_option("--rpc-server-ip", type="string", help="IP address of server to use with samba-tool (defaults to first --current-ip)")
71 parser.add_option("--use-file", type="string", help="Use a file, rather than real DNS calls")
72 parser.add_option("--update-list", type="string", help="Add DNS names from the given file")
73 parser.add_option("--update-cache", type="string", help="Cache database of already registered records")
74 parser.add_option("--fail-immediately", action='store_true', help="Exit on first failure")
75 parser.add_option("--no-credentials", dest='nocreds', action='store_true', help="don't try and get credentials")
76 parser.add_option("--no-substitutions", dest='nosubs', action='store_true', help="don't try and expands variables in file specified by --update-list")
81 opts, args = parser.parse_args()
87 lp = sambaopts.get_loadparm()
89 domain = lp.get("realm")
90 host = lp.get("netbios name")
91 if opts.all_interfaces:
94 all_interfaces = False
99 IPs = samba.interface_ips(lp, all_interfaces)
101 nsupdate_cmd = lp.get('nsupdate command')
104 print "No IP interfaces - skipping DNS updates"
107 if opts.rpc_server_ip:
108 rpc_server_ip = opts.rpc_server_ip
110 rpc_server_ip = IPs[0]
115 if i.find(':') != -1:
122 print "IPs: %s" % IPs
124 def get_possible_rw_dns_server(creds, domain):
125 """Get a list of possible read-write DNS servers, starting with
126 the SOA. The SOA is the correct answer, but old Samba domains
127 (4.6 and prior) do not maintain this value, so add NS servers
131 ans_soa = check_one_dns_name(domain, 'SOA')
133 # Actually there is only one
134 for i in range(len(ans_soa)):
135 hostnames.append(str(ans_soa[i].mname).rstrip('.'))
137 # This is not strictly legit, but old Samba domains may have an
138 # unmaintained SOA record, so go for any NS that we can get a
140 ans_ns = check_one_dns_name(domain, 'NS')
142 # Actually there is only one
143 for i in range(len(ans_ns)):
144 hostnames.append(str(ans_ns[i].target).rstrip('.'))
148 def get_krb5_rw_dns_server(creds, domain):
149 """Get a list of read-write DNS servers that we can obtain a ticket
150 for, starting with the SOA. The SOA is the correct answer, but
151 old Samba domains (4.6 and prior) do not maintain this value,
152 so continue with the NS servers as well until we get one that
153 the KDC will issue a ticket to.
156 rw_dns_servers = get_possible_rw_dns_server(creds, domain)
157 # Actually there is only one
158 for i in range(len(rw_dns_servers)):
159 target_hostname = str(rw_dns_servers[i])
161 settings["lp_ctx"] = lp
162 settings["target_hostname"] = target_hostname
164 gensec_client = gensec.Security.start_client(settings)
165 gensec_client.set_credentials(creds)
166 gensec_client.set_target_service("DNS")
167 gensec_client.set_target_hostname(target_hostname)
168 gensec_client.want_feature(gensec.FEATURE_SEAL)
169 gensec_client.start_mech_by_sasl_name("GSSAPI")
170 server_to_client = ""
172 (client_finished, client_to_server) = gensec_client.update(server_to_client)
174 print "Successfully obtained Kerberos ticket to DNS/%s as %s" \
175 % (target_hostname, creds.get_username())
176 return target_hostname
178 # Only raise an exception if they all failed
179 if i != len(rw_dns_servers) - 1:
183 def get_credentials(lp):
184 """# get credentials if we haven't got them already."""
185 from samba import credentials
187 creds = credentials.Credentials()
189 creds.set_machine_account(lp)
190 creds.set_krb_forwardable(credentials.NO_KRB_FORWARDABLE)
191 (tmp_fd, ccachename) = tempfile.mkstemp()
193 creds.get_named_ccache(lp, ccachename)
195 if opts.use_file is not None:
198 # Now confirm we can get a ticket to the DNS server
199 get_krb5_rw_dns_server(creds, sub_vars['DNSDOMAIN'] + '.')
202 except RuntimeError as e:
203 os.unlink(ccachename)
207 class dnsobj(object):
208 """an object to hold a parsed DNS line"""
210 def __init__(self, string_form):
211 list = string_form.split()
213 raise Exception("Invalid DNS entry %r" % string_form)
217 self.existing_port = None
218 self.existing_weight = None
219 self.existing_cname_target = None
228 self.nameservers = []
229 if self.type == 'SRV':
231 raise Exception("Invalid DNS entry %r" % string_form)
234 elif self.type in ['A', 'AAAA']:
235 self.ip = list[2] # usually $IP, which gets replaced
236 elif self.type == 'CNAME':
238 elif self.type == 'NS':
241 raise Exception("Received unexpected DNS reply of type %s: %s" % (self.type, string_form))
245 return "%s %s %s" % (self.type, self.name, self.ip)
246 if self.type == "AAAA":
247 return "%s %s %s" % (self.type, self.name, self.ip)
248 if self.type == "SRV":
249 return "%s %s %s %s" % (self.type, self.name, self.dest, self.port)
250 if self.type == "CNAME":
251 return "%s %s %s" % (self.type, self.name, self.dest)
252 if self.type == "NS":
253 return "%s %s %s" % (self.type, self.name, self.dest)
256 def parse_dns_line(line, sub_vars):
257 """parse a DNS line from."""
258 if line.startswith("SRV _ldap._tcp.pdc._msdcs.") and not samdb.am_pdc():
259 # We keep this as compat to the dns_update_list of 4.0/4.1
261 print "Skipping PDC entry (%s) as we are not a PDC" % line
263 subline = samba.substitute_var(line, sub_vars)
264 if subline == '' or subline[0] == "#":
266 return dnsobj(subline)
269 def hostname_match(h1, h2):
270 """see if two hostnames match."""
273 return h1.lower().rstrip('.') == h2.lower().rstrip('.')
275 def get_resolver(d=None):
276 resolv_conf = os.getenv('RESOLV_CONF')
278 resolv_conf = '/etc/resolv.conf'
279 resolver = dns.resolver.Resolver(filename=resolv_conf, configure=True)
281 if d is not None and d.nameservers != []:
282 resolver.nameservers = d.nameservers
286 def check_one_dns_name(name, name_type, d=None):
287 resolver = get_resolver(d)
288 if d is not None and len(d.nameservers) == 0:
289 d.nameservers = resolver.nameservers
291 ans = resolver.query(name, name_type)
294 def check_dns_name(d):
295 """check that a DNS entry exists."""
296 normalised_name = d.name.rstrip('.') + '.'
298 print "Looking for DNS entry %s as %s" % (d, normalised_name)
300 if opts.use_file is not None:
302 dns_file = open(opts.use_file, "r")
306 for line in dns_file:
308 if line == '' or line[0] == "#":
310 if line.lower() == str(d).lower():
315 ans = check_one_dns_name(normalised_name, d.type, d)
316 except dns.exception.Timeout:
317 raise Exception("Timeout while waiting to contact a working DNS server while looking for %s as %s" % (d, normalised_name))
318 except dns.resolver.NoNameservers:
319 raise Exception("Unable to contact a working DNS server while looking for %s as %s" % (d, normalised_name))
320 except dns.resolver.NXDOMAIN:
322 print "The DNS entry %s, queried as %s does not exist" % (d, normalised_name)
324 except dns.resolver.NoAnswer:
326 print "The DNS entry %s, queried as %s does not hold this record type" % (d, normalised_name)
328 except dns.exception.DNSException:
329 raise Exception("Failure while trying to resolve %s as %s" % (d, normalised_name))
330 if d.type in ['A', 'AAAA']:
331 # we need to be sure that our IP is there
333 if str(rdata) == str(d.ip):
335 elif d.type == 'CNAME':
336 for i in range(len(ans)):
337 if hostname_match(ans[i].target, d.dest):
340 d.existing_cname_target = str(ans[i].target)
342 for i in range(len(ans)):
343 if hostname_match(ans[i].target, d.dest):
345 elif d.type == 'SRV':
348 print "Checking %s against %s" % (rdata, d)
349 if hostname_match(rdata.target, d.dest):
350 if str(rdata.port) == str(d.port):
353 d.existing_port = str(rdata.port)
354 d.existing_weight = str(rdata.weight)
357 print "Lookup of %s succeeded, but we failed to find a matching DNS entry for %s" % (normalised_name, d)
362 def get_subst_vars(samdb):
363 """get the list of substitution vars."""
367 vars['DNSDOMAIN'] = samdb.domain_dns_name()
368 vars['DNSFOREST'] = samdb.forest_dns_name()
369 vars['HOSTNAME'] = samdb.host_dns_name()
370 vars['NTDSGUID'] = samdb.get_ntds_GUID()
371 vars['SITE'] = samdb.server_site_name()
372 res = samdb.search(base=samdb.get_default_basedn(), scope=SCOPE_BASE, attrs=["objectGUID"])
373 guid = samdb.schema_format_value("objectGUID", res[0]['objectGUID'][0])
374 vars['DOMAINGUID'] = guid
377 vars['IF_RWDC'] = "# "
378 vars['IF_RODC'] = "# "
379 vars['IF_PDC'] = "# "
381 vars['IF_RWGC'] = "# "
382 vars['IF_ROGC'] = "# "
383 vars['IF_DNS_DOMAIN'] = "# "
384 vars['IF_RWDNS_DOMAIN'] = "# "
385 vars['IF_RODNS_DOMAIN'] = "# "
386 vars['IF_DNS_FOREST'] = "# "
387 vars['IF_RWDNS_FOREST'] = "# "
388 vars['IF_R0DNS_FOREST'] = "# "
390 am_rodc = samdb.am_rodc()
399 # check if we "are DNS server"
400 res = samdb.search(base=samdb.get_config_basedn(),
401 expression='(objectguid=%s)' % vars['NTDSGUID'],
402 attrs=["options", "msDS-hasMasterNCs"])
405 if "options" in res[0]:
406 options = int(res[0]["options"][0])
407 if (options & dsdb.DS_NTDSDSA_OPT_IS_GC) != 0:
414 basedn = str(samdb.get_default_basedn())
415 forestdn = str(samdb.get_root_basedn())
417 if "msDS-hasMasterNCs" in res[0]:
418 for e in res[0]["msDS-hasMasterNCs"]:
419 if str(e) == "DC=DomainDnsZones,%s" % basedn:
420 vars['IF_DNS_DOMAIN'] = ""
422 vars['IF_RODNS_DOMAIN'] = ""
424 vars['IF_RWDNS_DOMAIN'] = ""
425 if str(e) == "DC=ForestDnsZones,%s" % forestdn:
426 vars['IF_DNS_FOREST'] = ""
428 vars['IF_RODNS_FOREST'] = ""
430 vars['IF_RWDNS_FOREST'] = ""
435 def call_nsupdate(d, op="add"):
436 """call nsupdate for an entry."""
437 global ccachename, nsupdate_cmd, krb5conf
439 assert(op in ["add", "delete"])
442 print "Calling nsupdate for %s (%s)" % (d, op)
444 if opts.use_file is not None:
446 rfile = open(opts.use_file, 'r+')
449 rfile = open(opts.use_file, 'w+')
450 # Open it for reading again, in case someone else got to it first
451 rfile = open(opts.use_file, 'r+')
452 fcntl.lockf(rfile, fcntl.LOCK_EX)
453 (file_dir, file_name) = os.path.split(opts.use_file)
454 (tmp_fd, tmpfile) = tempfile.mkstemp(dir=file_dir, prefix=file_name, suffix="XXXXXX")
455 wfile = os.fdopen(tmp_fd, 'a')
459 l = parse_dns_line(line, {})
460 if str(l).lower() == str(d).lower():
464 wfile.write(str(d)+"\n")
465 os.rename(tmpfile, opts.use_file)
466 fcntl.lockf(rfile, fcntl.LOCK_UN)
469 normalised_name = d.name.rstrip('.') + '.'
471 (tmp_fd, tmpfile) = tempfile.mkstemp()
472 f = os.fdopen(tmp_fd, 'w')
474 # Getting this line right is really important. When we are under
475 # resolv_wrapper, then we want to use RESOLV_CONF and the
476 # nameserver therein. The issue is that this parameter forces us
477 # to only ever use that server, and not some other server that the
478 # NS record may point to, even as we get a ticket to that other
481 # Therefore we must not set this in production, instead we want
482 # to find the name of a SOA for the zone and use that server.
484 if os.getenv('RESOLV_CONF') and d.nameservers != []:
485 f.write('server %s\n' % d.nameservers[0])
487 resolver = get_resolver(d)
489 # Local the zone for this name
490 zone = dns.resolver.zone_for_name(normalised_name,
493 # Now find the SOA, or if we can't get a ticket to the SOA,
494 # any server with an NS record we can get a ticket for.
496 # Thanks to the Kerberos Crednetials cache this is not
497 # expensive inside the loop
498 server = get_krb5_rw_dns_server(creds, zone)
499 f.write('server %s\n' % server)
502 f.write("update %s %s %u A %s\n" % (op, normalised_name, default_ttl, d.ip))
504 f.write("update %s %s %u AAAA %s\n" % (op, normalised_name, default_ttl, d.ip))
506 if op == "add" and d.existing_port is not None:
507 f.write("update delete %s SRV 0 %s %s %s\n" % (normalised_name, d.existing_weight,
508 d.existing_port, d.dest))
509 f.write("update %s %s %u SRV 0 100 %s %s\n" % (op, normalised_name, default_ttl, d.port, d.dest))
510 if d.type == "CNAME":
511 f.write("update %s %s %u CNAME %s\n" % (op, normalised_name, default_ttl, d.dest))
513 f.write("update %s %s %u NS %s\n" % (op, normalised_name, default_ttl, d.dest))
519 # Set a bigger MTU size to work around a bug in nsupdate's doio_send()
520 os.environ["SOCKET_WRAPPER_MTU"] = "2000"
524 os.environ["KRB5CCNAME"] = ccachename
526 cmd = nsupdate_cmd[:]
530 env["KRB5_CONFIG"] = krb5conf
532 env["KRB5CCNAME"] = ccachename
533 ret = subprocess.call(cmd, shell=False, env=env)
535 if opts.fail_immediately:
537 print("Failed update with %s" % tmpfile)
539 error_count = error_count + 1
541 print("Failed nsupdate: %d" % ret)
542 except Exception as estr:
543 if opts.fail_immediately:
545 error_count = error_count + 1
547 print("Failed nsupdate: %s : %s" % (str(d), estr))
550 # Let socket_wrapper set the default MTU size
551 os.environ["SOCKET_WRAPPER_MTU"] = "0"
554 def call_samba_tool(d, op="add", zone=None):
555 """call samba-tool dns to update an entry."""
557 assert(op in ["add", "delete"])
559 if (sub_vars['DNSFOREST'] != sub_vars['DNSDOMAIN']) and \
560 sub_vars['DNSFOREST'].endswith('.' + sub_vars['DNSDOMAIN']):
561 print "Refusing to use samba-tool when forest %s is under domain %s" \
562 % (sub_vars['DNSFOREST'], sub_vars['DNSDOMAIN'])
565 print "Calling samba-tool dns for %s (%s)" % (d, op)
567 normalised_name = d.name.rstrip('.') + '.'
569 if normalised_name == (sub_vars['DNSDOMAIN'] + '.'):
571 zone = sub_vars['DNSDOMAIN']
572 elif normalised_name == (sub_vars['DNSFOREST'] + '.'):
574 zone = sub_vars['DNSFOREST']
575 elif normalised_name == ('_msdcs.' + sub_vars['DNSFOREST'] + '.'):
577 zone = '_msdcs.' + sub_vars['DNSFOREST']
579 if not normalised_name.endswith('.' + sub_vars['DNSDOMAIN'] + '.'):
580 print "Not Calling samba-tool dns for %s (%s), %s not in %s" % (d, op, normalised_name, sub_vars['DNSDOMAIN'] + '.')
582 elif normalised_name.endswith('._msdcs.' + sub_vars['DNSFOREST'] + '.'):
583 zone = '_msdcs.' + sub_vars['DNSFOREST']
585 zone = sub_vars['DNSDOMAIN']
586 len_zone = len(zone)+2
587 short_name = normalised_name[:-len_zone]
589 len_zone = len(zone)+2
590 short_name = normalised_name[:-len_zone]
593 args = [rpc_server_ip, zone, short_name, "A", d.ip]
595 args = [rpc_server_ip, zone, short_name, "AAAA", d.ip]
597 if op == "add" and d.existing_port is not None:
598 print "Not handling modify of exising SRV %s using samba-tool" % d
601 args = [rpc_server_ip, zone, short_name, "SRV",
602 "%s %s %s %s" % (d.existing_weight,
603 d.existing_port, "0", "100"),
604 "%s %s %s %s" % (d.dest, d.port, "0", "100")]
606 args = [rpc_server_ip, zone, short_name, "SRV", "%s %s %s %s" % (d.dest, d.port, "0", "100")]
607 if d.type == "CNAME":
608 if d.existing_cname_target is None:
609 args = [rpc_server_ip, zone, short_name, "CNAME", d.dest]
612 args = [rpc_server_ip, zone, short_name, "CNAME",
613 d.existing_cname_target.rstrip('.'), d.dest]
616 args = [rpc_server_ip, zone, short_name, "NS", d.dest]
622 print "Calling samba-tool dns %s -k no -P %s" % (op, args)
623 ret = cmd._run("dns", op, "-k", "no", "-P", *args)
625 if opts.fail_immediately:
627 error_count = error_count + 1
629 print("Failed 'samba-tool dns' based update of %s" % (str(d)))
630 except Exception as estr:
631 if opts.fail_immediately:
633 error_count = error_count + 1
635 print("Failed 'samba-tool dns' based update: %s : %s" % (str(d), estr))
638 def rodc_dns_update(d, t, op):
639 '''a single DNS update via the RODC netlogon call'''
642 assert(op in ["add", "delete"])
645 print "Calling netlogon RODC update for %s" % d
648 netlogon.NlDnsLdapAtSite : netlogon.NlDnsInfoTypeNone,
649 netlogon.NlDnsGcAtSite : netlogon.NlDnsDomainNameAlias,
650 netlogon.NlDnsDsaCname : netlogon.NlDnsDomainNameAlias,
651 netlogon.NlDnsKdcAtSite : netlogon.NlDnsInfoTypeNone,
652 netlogon.NlDnsDcAtSite : netlogon.NlDnsInfoTypeNone,
653 netlogon.NlDnsRfc1510KdcAtSite : netlogon.NlDnsInfoTypeNone,
654 netlogon.NlDnsGenericGcAtSite : netlogon.NlDnsDomainNameAlias
657 w = winbind.winbind("irpc:winbind_server", lp)
658 dns_names = netlogon.NL_DNS_NAME_INFO_ARRAY()
660 name = netlogon.NL_DNS_NAME_INFO()
662 name.dns_domain_info_type = typemap[t]
665 if d.port is not None:
666 name.port = int(d.port)
668 name.dns_register = True
670 name.dns_register = False
671 dns_names.names = [ name ]
672 site_name = sub_vars['SITE'].decode('utf-8')
677 ret_names = w.DsrUpdateReadOnlyServerDnsRecords(site_name, default_ttl, dns_names)
678 if ret_names.names[0].status != 0:
679 print("Failed to set DNS entry: %s (status %u)" % (d, ret_names.names[0].status))
680 error_count = error_count + 1
681 except RuntimeError as reason:
682 print("Error setting DNS entry of type %u: %s: %s" % (t, d, reason))
683 error_count = error_count + 1
685 if error_count != 0 and opts.fail_immediately:
689 def call_rodc_update(d, op="add"):
690 '''RODCs need to use the netlogon API for nsupdate'''
693 assert(op in ["add", "delete"])
695 # we expect failure for 3268 if we aren't a GC
696 if d.port is not None and int(d.port) == 3268:
699 # map the DNS request to a netlogon update type
701 netlogon.NlDnsLdapAtSite : '_ldap._tcp.${SITE}._sites.${DNSDOMAIN}',
702 netlogon.NlDnsGcAtSite : '_ldap._tcp.${SITE}._sites.gc._msdcs.${DNSDOMAIN}',
703 netlogon.NlDnsDsaCname : '${NTDSGUID}._msdcs.${DNSFOREST}',
704 netlogon.NlDnsKdcAtSite : '_kerberos._tcp.${SITE}._sites.dc._msdcs.${DNSDOMAIN}',
705 netlogon.NlDnsDcAtSite : '_ldap._tcp.${SITE}._sites.dc._msdcs.${DNSDOMAIN}',
706 netlogon.NlDnsRfc1510KdcAtSite : '_kerberos._tcp.${SITE}._sites.${DNSDOMAIN}',
707 netlogon.NlDnsGenericGcAtSite : '_gc._tcp.${SITE}._sites.${DNSFOREST}'
711 subname = samba.substitute_var(map[t], sub_vars)
712 if subname.lower() == d.name.lower():
713 # found a match - do the update
714 rodc_dns_update(d, t, op)
717 print("Unable to map to netlogon DNS update: %s" % d)
720 # get the list of DNS entries we should have
722 dns_update_list = opts.update_list
724 dns_update_list = lp.private_path('dns_update_list')
726 if opts.update_cache:
727 dns_update_cache = opts.update_cache
729 dns_update_cache = lp.private_path('dns_update_cache')
732 # only change the krb5.conf if we are not in selftest
733 if 'SOCKET_WRAPPER_DIR' not in os.environ:
734 # use our private krb5.conf to avoid problems with the wrong domain
735 # bind9 nsupdate wants the default domain set
736 krb5conf = lp.private_path('krb5.conf')
737 os.environ['KRB5_CONFIG'] = krb5conf
739 file = open(dns_update_list, "r")
744 samdb = SamDB(url=lp.samdb_url(), session_info=system_session(), lp=lp)
746 # get the substitution dictionary
747 sub_vars = get_subst_vars(samdb)
749 # build up a list of update commands to pass to nsupdate
758 rebuild_cache = False
760 cfile = open(dns_update_cache, 'r+')
763 cfile = open(dns_update_cache, 'w+')
764 # Open it for reading again, in case someone else got to it first
765 cfile = open(dns_update_cache, 'r+')
766 fcntl.lockf(cfile, fcntl.LOCK_EX)
769 if line == '' or line[0] == "#":
771 c = parse_dns_line(line, {})
774 if str(c) not in cache_set:
776 cache_set.add(str(c))
778 # read each line, and check that the DNS name exists
781 if line == '' or line[0] == "#":
783 d = parse_dns_line(line, sub_vars)
786 if d.type == 'A' and len(IP4s) == 0:
788 if d.type == 'AAAA' and len(IP6s) == 0:
790 if str(d) not in dup_set:
794 # now expand the entries, if any are A record with ip set to $IP
795 # then replace with multiple entries, one for each interface IP
801 for i in range(len(IP4s)-1):
807 for i in range(len(IP6s)-1):
812 # now check if the entries already exist on the DNS server
816 if str(c).lower() == str(d).lower():
822 print "need cache add: %s" % d
824 update_list.append(d)
826 print "force update: %s" % d
827 elif not check_dns_name(d):
828 update_list.append(d)
830 print "need update: %s" % d
836 if str(c).lower() == str(d).lower():
843 print "need cache remove: %s" % c
844 if not opts.all_names and not check_dns_name(c):
846 delete_list.append(c)
848 print "need delete: %s" % c
850 if len(delete_list) == 0 and len(update_list) == 0 and not rebuild_cache:
852 print "No DNS updates needed"
856 print "%d DNS updates and %d DNS deletes needed" % (len(update_list), len(delete_list))
858 use_samba_tool = opts.use_samba_tool
859 use_nsupdate = opts.use_nsupdate
861 if len(delete_list) != 0 or len(update_list) != 0 and not opts.nocreds:
863 creds = get_credentials(lp)
864 except RuntimeError as e:
867 if sub_vars['IF_RWDNS_DOMAIN'] == "# ":
873 print "Failed to get Kerberos credentials, falling back to samba-tool: %s" % e
874 use_samba_tool = True
877 # ask nsupdate to delete entries as needed
878 for d in delete_list:
879 if d.rpc or (not use_nsupdate and use_samba_tool):
881 print "update (samba-tool): %s" % d
882 call_samba_tool(d, op="delete", zone=d.zone)
885 if d.name.lower() == domain.lower():
887 print "skip delete (rodc): %s" % d
889 if not d.type in [ 'A', 'AAAA' ]:
891 print "delete (rodc): %s" % d
892 call_rodc_update(d, op="delete")
895 print "delete (nsupdate): %s" % d
896 call_nsupdate(d, op="delete")
899 print "delete (nsupdate): %s" % d
900 call_nsupdate(d, op="delete")
902 # ask nsupdate to add entries as needed
903 for d in update_list:
904 if d.rpc or (not use_nsupdate and use_samba_tool):
906 print "update (samba-tool): %s" % d
907 call_samba_tool(d, zone=d.zone)
910 if d.name.lower() == domain.lower():
912 print "skip (rodc): %s" % d
914 if not d.type in [ 'A', 'AAAA' ]:
916 print "update (rodc): %s" % d
920 print "update (nsupdate): %s" % d
924 print "update(nsupdate): %s" % d
928 print "Rebuilding cache at %s" % dns_update_cache
929 (file_dir, file_name) = os.path.split(dns_update_cache)
930 (tmp_fd, tmpfile) = tempfile.mkstemp(dir=file_dir, prefix=file_name, suffix="XXXXXX")
931 wfile = os.fdopen(tmp_fd, 'a')
934 print "Adding %s to %s" % (str(d), file_name)
935 wfile.write(str(d)+"\n")
936 os.rename(tmpfile, dns_update_cache)
937 fcntl.lockf(cfile, fcntl.LOCK_UN)
939 # delete the ccache if we created it
940 if ccachename is not None:
941 os.unlink(ccachename)
944 print("Failed update of %u entries" % error_count)
945 sys.exit(error_count)