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/>.
27 from samba.kcc import kcc_utils
29 # ensure we get messages out immediately, so they get in the samba logs,
30 # and don't get swallowed by a timeout
31 os.environ['PYTHONUNBUFFERED'] = '1'
33 # forcing GMT avoids a problem in some timezones with kerberos. Both MIT
34 # heimdal can get mutual authentication errors due to the 24 second difference
35 # between UTC and GMT when using some zone files (eg. the PDT zone from
37 os.environ["TZ"] = "GMT"
39 # Find right directory when running from source tree
40 sys.path.insert(0, "bin/python")
44 from samba import getopt as options
45 from ldb import SCOPE_BASE
46 from samba import dsdb
47 from samba.auth import system_session
48 from samba.samdb import SamDB
49 from samba.dcerpc import netlogon, winbind
50 from samba.netcmd.dns import cmd_dns
51 from samba import gensec
54 samba.ensure_third_party_module("dns", "dnspython")
62 parser = optparse.OptionParser("samba_dnsupdate")
63 sambaopts = options.SambaOptions(parser)
64 parser.add_option_group(sambaopts)
65 parser.add_option_group(options.VersionOptions(parser))
66 parser.add_option("--verbose", action="store_true")
67 parser.add_option("--use-samba-tool", action="store_true", help="Use samba-tool to make updates over RPC, rather than over DNS")
68 parser.add_option("--use-nsupdate", action="store_true", help="Use nsupdate command to make updates over DNS (default, if kinit successful)")
69 parser.add_option("--all-names", action="store_true")
70 parser.add_option("--all-interfaces", action="store_true")
71 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=)")
72 parser.add_option("--rpc-server-ip", type="string", help="IP address of server to use with samba-tool (defaults to first --current-ip)")
73 parser.add_option("--use-file", type="string", help="Use a file, rather than real DNS calls")
74 parser.add_option("--update-list", type="string", help="Add DNS names from the given file")
75 parser.add_option("--update-cache", type="string", help="Cache database of already registered records")
76 parser.add_option("--fail-immediately", action='store_true', help="Exit on first failure")
77 parser.add_option("--no-credentials", dest='nocreds', action='store_true', help="don't try and get credentials")
78 parser.add_option("--no-substitutions", dest='nosubs', action='store_true', help="don't try and expands variables in file specified by --update-list")
83 opts, args = parser.parse_args()
89 lp = sambaopts.get_loadparm()
91 domain = lp.get("realm")
92 host = lp.get("netbios name")
93 if opts.all_interfaces:
96 all_interfaces = False
101 IPs = samba.interface_ips(lp, all_interfaces)
103 nsupdate_cmd = lp.get('nsupdate command')
106 print "No IP interfaces - skipping DNS updates"
109 if opts.rpc_server_ip:
110 rpc_server_ip = opts.rpc_server_ip
112 rpc_server_ip = IPs[0]
117 if i.find(':') != -1:
124 print "IPs: %s" % IPs
126 def get_possible_rw_dns_server(creds, domain):
127 """Get a list of possible read-write DNS servers, starting with
128 the SOA. The SOA is the correct answer, but old Samba domains
129 (4.6 and prior) do not maintain this value, so add NS servers
133 ans_soa = check_one_dns_name(domain, 'SOA')
135 # Actually there is only one
136 for i in range(len(ans_soa)):
137 hostnames.append(str(ans_soa[i].mname).rstrip('.'))
139 # This is not strictly legit, but old Samba domains may have an
140 # unmaintained SOA record, so go for any NS that we can get a
142 ans_ns = check_one_dns_name(domain, 'NS')
144 # Actually there is only one
145 for i in range(len(ans_ns)):
146 hostnames.append(str(ans_ns[i].target).rstrip('.'))
150 def get_krb5_rw_dns_server(creds, domain):
151 """Get a list of read-write DNS servers that we can obtain a ticket
152 for, starting with the SOA. The SOA is the correct answer, but
153 old Samba domains (4.6 and prior) do not maintain this value,
154 so continue with the NS servers as well until we get one that
155 the KDC will issue a ticket to.
158 rw_dns_servers = get_possible_rw_dns_server(creds, domain)
159 # Actually there is only one
160 for i in range(len(rw_dns_servers)):
161 target_hostname = str(rw_dns_servers[i])
163 settings["lp_ctx"] = lp
164 settings["target_hostname"] = target_hostname
166 gensec_client = gensec.Security.start_client(settings)
167 gensec_client.set_credentials(creds)
168 gensec_client.set_target_service("DNS")
169 gensec_client.set_target_hostname(target_hostname)
170 gensec_client.want_feature(gensec.FEATURE_SEAL)
171 gensec_client.start_mech_by_sasl_name("GSSAPI")
172 server_to_client = ""
174 (client_finished, client_to_server) = gensec_client.update(server_to_client)
176 print "Successfully obtained Kerberos ticket to DNS/%s as %s" \
177 % (target_hostname, creds.get_username())
178 return target_hostname
180 # Only raise an exception if they all failed
181 if i != len(rw_dns_servers) - 1:
185 def get_credentials(lp):
186 """# get credentials if we haven't got them already."""
187 from samba import credentials
189 creds = credentials.Credentials()
191 creds.set_machine_account(lp)
192 creds.set_krb_forwardable(credentials.NO_KRB_FORWARDABLE)
193 (tmp_fd, ccachename) = tempfile.mkstemp()
195 creds.get_named_ccache(lp, ccachename)
197 if opts.use_file is not None:
200 # Now confirm we can get a ticket to the DNS server
201 get_krb5_rw_dns_server(creds, sub_vars['DNSDOMAIN'] + '.')
204 except RuntimeError as e:
205 os.unlink(ccachename)
209 class dnsobj(object):
210 """an object to hold a parsed DNS line"""
212 def __init__(self, string_form):
213 list = string_form.split()
215 raise Exception("Invalid DNS entry %r" % string_form)
219 self.existing_port = None
220 self.existing_weight = None
221 self.existing_cname_target = None
230 self.nameservers = []
231 if self.type == 'SRV':
233 raise Exception("Invalid DNS entry %r" % string_form)
236 elif self.type in ['A', 'AAAA']:
237 self.ip = list[2] # usually $IP, which gets replaced
238 elif self.type == 'CNAME':
240 elif self.type == 'NS':
243 raise Exception("Received unexpected DNS reply of type %s: %s" % (self.type, string_form))
247 return "%s %s %s" % (self.type, self.name, self.ip)
248 if self.type == "AAAA":
249 return "%s %s %s" % (self.type, self.name, self.ip)
250 if self.type == "SRV":
251 return "%s %s %s %s" % (self.type, self.name, self.dest, self.port)
252 if self.type == "CNAME":
253 return "%s %s %s" % (self.type, self.name, self.dest)
254 if self.type == "NS":
255 return "%s %s %s" % (self.type, self.name, self.dest)
258 def parse_dns_line(line, sub_vars):
259 """parse a DNS line from."""
260 if line.startswith("SRV _ldap._tcp.pdc._msdcs.") and not samdb.am_pdc():
261 # We keep this as compat to the dns_update_list of 4.0/4.1
263 print "Skipping PDC entry (%s) as we are not a PDC" % line
265 subline = samba.substitute_var(line, sub_vars)
266 if subline == '' or subline[0] == "#":
268 return dnsobj(subline)
271 def hostname_match(h1, h2):
272 """see if two hostnames match."""
275 return h1.lower().rstrip('.') == h2.lower().rstrip('.')
277 def get_resolver(d=None):
278 resolv_conf = os.getenv('RESOLV_CONF')
280 resolv_conf = '/etc/resolv.conf'
281 resolver = dns.resolver.Resolver(filename=resolv_conf, configure=True)
283 if d is not None and d.nameservers != []:
284 resolver.nameservers = d.nameservers
288 def check_one_dns_name(name, name_type, d=None):
289 resolver = get_resolver(d)
290 if d is not None and len(d.nameservers) == 0:
291 d.nameservers = resolver.nameservers
293 ans = resolver.query(name, name_type)
296 def check_dns_name(d):
297 """check that a DNS entry exists."""
298 normalised_name = d.name.rstrip('.') + '.'
300 print "Looking for DNS entry %s as %s" % (d, normalised_name)
302 if opts.use_file is not None:
304 dns_file = open(opts.use_file, "r")
308 for line in dns_file:
310 if line == '' or line[0] == "#":
312 if line.lower() == str(d).lower():
317 ans = check_one_dns_name(normalised_name, d.type, d)
318 except dns.exception.Timeout:
319 raise Exception("Timeout while waiting to contact a working DNS server while looking for %s as %s" % (d, normalised_name))
320 except dns.resolver.NoNameservers:
321 raise Exception("Unable to contact a working DNS server while looking for %s as %s" % (d, normalised_name))
322 except dns.resolver.NXDOMAIN:
324 print "The DNS entry %s, queried as %s does not exist" % (d, normalised_name)
326 except dns.resolver.NoAnswer:
328 print "The DNS entry %s, queried as %s does not hold this record type" % (d, normalised_name)
330 except dns.exception.DNSException:
331 raise Exception("Failure while trying to resolve %s as %s" % (d, normalised_name))
332 if d.type in ['A', 'AAAA']:
333 # we need to be sure that our IP is there
335 if str(rdata) == str(d.ip):
337 elif d.type == 'CNAME':
338 for i in range(len(ans)):
339 if hostname_match(ans[i].target, d.dest):
342 d.existing_cname_target = str(ans[i].target)
344 for i in range(len(ans)):
345 if hostname_match(ans[i].target, d.dest):
347 elif d.type == 'SRV':
350 print "Checking %s against %s" % (rdata, d)
351 if hostname_match(rdata.target, d.dest):
352 if str(rdata.port) == str(d.port):
355 d.existing_port = str(rdata.port)
356 d.existing_weight = str(rdata.weight)
359 print "Lookup of %s succeeded, but we failed to find a matching DNS entry for %s" % (normalised_name, d)
364 def get_subst_vars(samdb):
365 """get the list of substitution vars."""
369 vars['DNSDOMAIN'] = samdb.domain_dns_name()
370 vars['DNSFOREST'] = samdb.forest_dns_name()
371 vars['HOSTNAME'] = samdb.host_dns_name()
372 vars['NTDSGUID'] = samdb.get_ntds_GUID()
373 vars['SITE'] = samdb.server_site_name()
374 res = samdb.search(base=samdb.get_default_basedn(), scope=SCOPE_BASE, attrs=["objectGUID"])
375 guid = samdb.schema_format_value("objectGUID", res[0]['objectGUID'][0])
376 vars['DOMAINGUID'] = guid
379 vars['IF_RWDC'] = "# "
380 vars['IF_RODC'] = "# "
381 vars['IF_PDC'] = "# "
383 vars['IF_RWGC'] = "# "
384 vars['IF_ROGC'] = "# "
385 vars['IF_DNS_DOMAIN'] = "# "
386 vars['IF_RWDNS_DOMAIN'] = "# "
387 vars['IF_RODNS_DOMAIN'] = "# "
388 vars['IF_DNS_FOREST'] = "# "
389 vars['IF_RWDNS_FOREST'] = "# "
390 vars['IF_R0DNS_FOREST'] = "# "
392 am_rodc = samdb.am_rodc()
401 # check if we "are DNS server"
402 res = samdb.search(base=samdb.get_config_basedn(),
403 expression='(objectguid=%s)' % vars['NTDSGUID'],
404 attrs=["options", "msDS-hasMasterNCs"])
407 if "options" in res[0]:
408 options = int(res[0]["options"][0])
409 if (options & dsdb.DS_NTDSDSA_OPT_IS_GC) != 0:
416 basedn = str(samdb.get_default_basedn())
417 forestdn = str(samdb.get_root_basedn())
419 if "msDS-hasMasterNCs" in res[0]:
420 for e in res[0]["msDS-hasMasterNCs"]:
421 if str(e) == "DC=DomainDnsZones,%s" % basedn:
422 vars['IF_DNS_DOMAIN'] = ""
424 vars['IF_RODNS_DOMAIN'] = ""
426 vars['IF_RWDNS_DOMAIN'] = ""
427 if str(e) == "DC=ForestDnsZones,%s" % forestdn:
428 vars['IF_DNS_FOREST'] = ""
430 vars['IF_RODNS_FOREST'] = ""
432 vars['IF_RWDNS_FOREST'] = ""
437 def call_nsupdate(d, op="add"):
438 """call nsupdate for an entry."""
439 global ccachename, nsupdate_cmd, krb5conf
441 assert(op in ["add", "delete"])
444 print "Calling nsupdate for %s (%s)" % (d, op)
446 if opts.use_file is not None:
448 rfile = open(opts.use_file, 'r+')
451 rfile = open(opts.use_file, 'w+')
452 # Open it for reading again, in case someone else got to it first
453 rfile = open(opts.use_file, 'r+')
454 fcntl.lockf(rfile, fcntl.LOCK_EX)
455 (file_dir, file_name) = os.path.split(opts.use_file)
456 (tmp_fd, tmpfile) = tempfile.mkstemp(dir=file_dir, prefix=file_name, suffix="XXXXXX")
457 wfile = os.fdopen(tmp_fd, 'a')
461 l = parse_dns_line(line, {})
462 if str(l).lower() == str(d).lower():
466 wfile.write(str(d)+"\n")
467 os.rename(tmpfile, opts.use_file)
468 fcntl.lockf(rfile, fcntl.LOCK_UN)
471 normalised_name = d.name.rstrip('.') + '.'
473 (tmp_fd, tmpfile) = tempfile.mkstemp()
474 f = os.fdopen(tmp_fd, 'w')
476 # Getting this line right is really important. When we are under
477 # resolv_wrapper, then we want to use RESOLV_CONF and the
478 # nameserver therein. The issue is that this parameter forces us
479 # to only ever use that server, and not some other server that the
480 # NS record may point to, even as we get a ticket to that other
483 # Therefore we must not set this in production, instead we want
484 # to find the name of a SOA for the zone and use that server.
486 if os.getenv('RESOLV_CONF') and d.nameservers != []:
487 f.write('server %s\n' % d.nameservers[0])
489 resolver = get_resolver(d)
491 # Local the zone for this name
492 zone = dns.resolver.zone_for_name(normalised_name,
495 # Now find the SOA, or if we can't get a ticket to the SOA,
496 # any server with an NS record we can get a ticket for.
498 # Thanks to the Kerberos Crednetials cache this is not
499 # expensive inside the loop
500 server = get_krb5_rw_dns_server(creds, zone)
501 f.write('server %s\n' % server)
504 f.write("update %s %s %u A %s\n" % (op, normalised_name, default_ttl, d.ip))
506 f.write("update %s %s %u AAAA %s\n" % (op, normalised_name, default_ttl, d.ip))
508 if op == "add" and d.existing_port is not None:
509 f.write("update delete %s SRV 0 %s %s %s\n" % (normalised_name, d.existing_weight,
510 d.existing_port, d.dest))
511 f.write("update %s %s %u SRV 0 100 %s %s\n" % (op, normalised_name, default_ttl, d.port, d.dest))
512 if d.type == "CNAME":
513 f.write("update %s %s %u CNAME %s\n" % (op, normalised_name, default_ttl, d.dest))
515 f.write("update %s %s %u NS %s\n" % (op, normalised_name, default_ttl, d.dest))
521 # Set a bigger MTU size to work around a bug in nsupdate's doio_send()
522 os.environ["SOCKET_WRAPPER_MTU"] = "2000"
526 os.environ["KRB5CCNAME"] = ccachename
528 cmd = nsupdate_cmd[:]
532 env["KRB5_CONFIG"] = krb5conf
534 env["KRB5CCNAME"] = ccachename
535 ret = subprocess.call(cmd, shell=False, env=env)
537 if opts.fail_immediately:
539 print("Failed update with %s" % tmpfile)
541 error_count = error_count + 1
543 print("Failed nsupdate: %d" % ret)
544 except Exception as estr:
545 if opts.fail_immediately:
547 error_count = error_count + 1
549 print("Failed nsupdate: %s : %s" % (str(d), estr))
552 # Let socket_wrapper set the default MTU size
553 os.environ["SOCKET_WRAPPER_MTU"] = "0"
556 def call_samba_tool(d, op="add", zone=None):
557 """call samba-tool dns to update an entry."""
559 assert(op in ["add", "delete"])
561 if (sub_vars['DNSFOREST'] != sub_vars['DNSDOMAIN']) and \
562 sub_vars['DNSFOREST'].endswith('.' + sub_vars['DNSDOMAIN']):
563 print "Refusing to use samba-tool when forest %s is under domain %s" \
564 % (sub_vars['DNSFOREST'], sub_vars['DNSDOMAIN'])
567 print "Calling samba-tool dns for %s (%s)" % (d, op)
569 normalised_name = d.name.rstrip('.') + '.'
571 if normalised_name == (sub_vars['DNSDOMAIN'] + '.'):
573 zone = sub_vars['DNSDOMAIN']
574 elif normalised_name == (sub_vars['DNSFOREST'] + '.'):
576 zone = sub_vars['DNSFOREST']
577 elif normalised_name == ('_msdcs.' + sub_vars['DNSFOREST'] + '.'):
579 zone = '_msdcs.' + sub_vars['DNSFOREST']
581 if not normalised_name.endswith('.' + sub_vars['DNSDOMAIN'] + '.'):
582 print "Not Calling samba-tool dns for %s (%s), %s not in %s" % (d, op, normalised_name, sub_vars['DNSDOMAIN'] + '.')
584 elif normalised_name.endswith('._msdcs.' + sub_vars['DNSFOREST'] + '.'):
585 zone = '_msdcs.' + sub_vars['DNSFOREST']
587 zone = sub_vars['DNSDOMAIN']
588 len_zone = len(zone)+2
589 short_name = normalised_name[:-len_zone]
591 len_zone = len(zone)+2
592 short_name = normalised_name[:-len_zone]
595 args = [rpc_server_ip, zone, short_name, "A", d.ip]
597 args = [rpc_server_ip, zone, short_name, "AAAA", d.ip]
599 if op == "add" and d.existing_port is not None:
600 print "Not handling modify of exising SRV %s using samba-tool" % d
603 args = [rpc_server_ip, zone, short_name, "SRV",
604 "%s %s %s %s" % (d.existing_weight,
605 d.existing_port, "0", "100"),
606 "%s %s %s %s" % (d.dest, d.port, "0", "100")]
608 args = [rpc_server_ip, zone, short_name, "SRV", "%s %s %s %s" % (d.dest, d.port, "0", "100")]
609 if d.type == "CNAME":
610 if d.existing_cname_target is None:
611 args = [rpc_server_ip, zone, short_name, "CNAME", d.dest]
614 args = [rpc_server_ip, zone, short_name, "CNAME",
615 d.existing_cname_target.rstrip('.'), d.dest]
618 args = [rpc_server_ip, zone, short_name, "NS", d.dest]
624 print "Calling samba-tool dns %s -k no -P %s" % (op, args)
625 ret = cmd._run("dns", op, "-k", "no", "-P", *args)
627 if opts.fail_immediately:
629 error_count = error_count + 1
631 print("Failed 'samba-tool dns' based update of %s" % (str(d)))
632 except Exception as estr:
633 if opts.fail_immediately:
635 error_count = error_count + 1
637 print("Failed 'samba-tool dns' based update: %s : %s" % (str(d), estr))
640 def rodc_dns_update(d, t, op):
641 '''a single DNS update via the RODC netlogon call'''
644 assert(op in ["add", "delete"])
647 print "Calling netlogon RODC update for %s" % d
650 netlogon.NlDnsLdapAtSite : netlogon.NlDnsInfoTypeNone,
651 netlogon.NlDnsGcAtSite : netlogon.NlDnsDomainNameAlias,
652 netlogon.NlDnsDsaCname : netlogon.NlDnsDomainNameAlias,
653 netlogon.NlDnsKdcAtSite : netlogon.NlDnsInfoTypeNone,
654 netlogon.NlDnsDcAtSite : netlogon.NlDnsInfoTypeNone,
655 netlogon.NlDnsRfc1510KdcAtSite : netlogon.NlDnsInfoTypeNone,
656 netlogon.NlDnsGenericGcAtSite : netlogon.NlDnsDomainNameAlias
659 w = winbind.winbind("irpc:winbind_server", lp)
660 dns_names = netlogon.NL_DNS_NAME_INFO_ARRAY()
662 name = netlogon.NL_DNS_NAME_INFO()
664 name.dns_domain_info_type = typemap[t]
667 if d.port is not None:
668 name.port = int(d.port)
670 name.dns_register = True
672 name.dns_register = False
673 dns_names.names = [ name ]
674 site_name = sub_vars['SITE'].decode('utf-8')
679 ret_names = w.DsrUpdateReadOnlyServerDnsRecords(site_name, default_ttl, dns_names)
680 if ret_names.names[0].status != 0:
681 print("Failed to set DNS entry: %s (status %u)" % (d, ret_names.names[0].status))
682 error_count = error_count + 1
683 except RuntimeError as reason:
684 print("Error setting DNS entry of type %u: %s: %s" % (t, d, reason))
685 error_count = error_count + 1
687 if error_count != 0 and opts.fail_immediately:
691 def call_rodc_update(d, op="add"):
692 '''RODCs need to use the netlogon API for nsupdate'''
695 assert(op in ["add", "delete"])
697 # we expect failure for 3268 if we aren't a GC
698 if d.port is not None and int(d.port) == 3268:
701 # map the DNS request to a netlogon update type
703 netlogon.NlDnsLdapAtSite : '_ldap._tcp.${SITE}._sites.${DNSDOMAIN}',
704 netlogon.NlDnsGcAtSite : '_ldap._tcp.${SITE}._sites.gc._msdcs.${DNSDOMAIN}',
705 netlogon.NlDnsDsaCname : '${NTDSGUID}._msdcs.${DNSFOREST}',
706 netlogon.NlDnsKdcAtSite : '_kerberos._tcp.${SITE}._sites.dc._msdcs.${DNSDOMAIN}',
707 netlogon.NlDnsDcAtSite : '_ldap._tcp.${SITE}._sites.dc._msdcs.${DNSDOMAIN}',
708 netlogon.NlDnsRfc1510KdcAtSite : '_kerberos._tcp.${SITE}._sites.${DNSDOMAIN}',
709 netlogon.NlDnsGenericGcAtSite : '_gc._tcp.${SITE}._sites.${DNSFOREST}'
713 subname = samba.substitute_var(map[t], sub_vars)
714 if subname.lower() == d.name.lower():
715 # found a match - do the update
716 rodc_dns_update(d, t, op)
719 print("Unable to map to netlogon DNS update: %s" % d)
722 # get the list of DNS entries we should have
724 dns_update_list = opts.update_list
726 dns_update_list = lp.private_path('dns_update_list')
728 if opts.update_cache:
729 dns_update_cache = opts.update_cache
731 dns_update_cache = lp.private_path('dns_update_cache')
734 # only change the krb5.conf if we are not in selftest
735 if 'SOCKET_WRAPPER_DIR' not in os.environ:
736 # use our private krb5.conf to avoid problems with the wrong domain
737 # bind9 nsupdate wants the default domain set
738 krb5conf = lp.private_path('krb5.conf')
739 os.environ['KRB5_CONFIG'] = krb5conf
741 file = open(dns_update_list, "r")
746 samdb = SamDB(url=lp.samdb_url(), session_info=system_session(), lp=lp)
748 # get the substitution dictionary
749 sub_vars = get_subst_vars(samdb)
751 # build up a list of update commands to pass to nsupdate
760 rebuild_cache = False
762 cfile = open(dns_update_cache, 'r+')
765 cfile = open(dns_update_cache, 'w+')
766 # Open it for reading again, in case someone else got to it first
767 cfile = open(dns_update_cache, 'r+')
768 fcntl.lockf(cfile, fcntl.LOCK_EX)
771 if line == '' or line[0] == "#":
773 c = parse_dns_line(line, {})
776 if str(c) not in cache_set:
778 cache_set.add(str(c))
780 site_specific_rec = []
782 # read each line, and check that the DNS name exists
786 if '${SITE}' in line:
787 site_specific_rec.append(line)
789 if line == '' or line[0] == "#":
791 d = parse_dns_line(line, sub_vars)
794 if d.type == 'A' and len(IP4s) == 0:
796 if d.type == 'AAAA' and len(IP6s) == 0:
798 if str(d) not in dup_set:
802 # Perform automatic site coverage by default
805 if not am_rodc and auto_coverage:
806 site_names = kcc_utils.uncovered_sites_to_cover(samdb,
807 samdb.server_site_name())
809 # Duplicate all site specific records for the uncovered site
810 for site in site_names:
811 to_add = [samba.substitute_var(line, {'SITE': site})
812 for line in site_specific_rec]
814 for site_line in to_add:
815 d = parse_dns_line(site_line,
817 if d is not None and str(d) not in dup_set:
821 # now expand the entries, if any are A record with ip set to $IP
822 # then replace with multiple entries, one for each interface IP
828 for i in range(len(IP4s)-1):
834 for i in range(len(IP6s)-1):
839 # now check if the entries already exist on the DNS server
843 if str(c).lower() == str(d).lower():
849 print "need cache add: %s" % d
851 update_list.append(d)
853 print "force update: %s" % d
854 elif not check_dns_name(d):
855 update_list.append(d)
857 print "need update: %s" % d
863 if str(c).lower() == str(d).lower():
870 print "need cache remove: %s" % c
871 if not opts.all_names and not check_dns_name(c):
873 delete_list.append(c)
875 print "need delete: %s" % c
877 if len(delete_list) == 0 and len(update_list) == 0 and not rebuild_cache:
879 print "No DNS updates needed"
883 print "%d DNS updates and %d DNS deletes needed" % (len(update_list), len(delete_list))
885 use_samba_tool = opts.use_samba_tool
886 use_nsupdate = opts.use_nsupdate
888 if len(delete_list) != 0 or len(update_list) != 0 and not opts.nocreds:
890 creds = get_credentials(lp)
891 except RuntimeError as e:
894 if sub_vars['IF_RWDNS_DOMAIN'] == "# ":
900 print "Failed to get Kerberos credentials, falling back to samba-tool: %s" % e
901 use_samba_tool = True
904 # ask nsupdate to delete entries as needed
905 for d in delete_list:
906 if d.rpc or (not use_nsupdate and use_samba_tool):
908 print "update (samba-tool): %s" % d
909 call_samba_tool(d, op="delete", zone=d.zone)
912 if d.name.lower() == domain.lower():
914 print "skip delete (rodc): %s" % d
916 if not d.type in [ 'A', 'AAAA' ]:
918 print "delete (rodc): %s" % d
919 call_rodc_update(d, op="delete")
922 print "delete (nsupdate): %s" % d
923 call_nsupdate(d, op="delete")
926 print "delete (nsupdate): %s" % d
927 call_nsupdate(d, op="delete")
929 # ask nsupdate to add entries as needed
930 for d in update_list:
931 if d.rpc or (not use_nsupdate and use_samba_tool):
933 print "update (samba-tool): %s" % d
934 call_samba_tool(d, zone=d.zone)
937 if d.name.lower() == domain.lower():
939 print "skip (rodc): %s" % d
941 if not d.type in [ 'A', 'AAAA' ]:
943 print "update (rodc): %s" % d
947 print "update (nsupdate): %s" % d
951 print "update(nsupdate): %s" % d
955 print "Rebuilding cache at %s" % dns_update_cache
956 (file_dir, file_name) = os.path.split(dns_update_cache)
957 (tmp_fd, tmpfile) = tempfile.mkstemp(dir=file_dir, prefix=file_name, suffix="XXXXXX")
958 wfile = os.fdopen(tmp_fd, 'a')
961 print "Adding %s to %s" % (str(d), file_name)
962 wfile.write(str(d)+"\n")
963 os.rename(tmpfile, dns_update_cache)
964 fcntl.lockf(cfile, fcntl.LOCK_UN)
966 # delete the ccache if we created it
967 if ccachename is not None:
968 os.unlink(ccachename)
971 print("Failed update of %u entries" % error_count)
972 sys.exit(error_count)