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
125 def get_credentials(lp
):
126 """# get credentials if we haven't got them already."""
127 from samba
import credentials
129 creds
= credentials
.Credentials()
131 creds
.set_machine_account(lp
)
132 creds
.set_krb_forwardable(credentials
.NO_KRB_FORWARDABLE
)
133 (tmp_fd
, ccachename
) = tempfile
.mkstemp()
135 creds
.get_named_ccache(lp
, ccachename
)
137 if opts
.use_file
is not None:
140 # Now confirm we can get a ticket to a DNS server
141 ans
= check_one_dns_name(sub_vars
['DNSDOMAIN'] + '.', 'NS')
142 for i
in range(len(ans
)):
143 target_hostname
= str(ans
[i
].target
).rstrip('.')
145 settings
["lp_ctx"] = lp
146 settings
["target_hostname"] = target_hostname
148 gensec_client
= gensec
.Security
.start_client(settings
)
149 gensec_client
.set_credentials(creds
)
150 gensec_client
.set_target_service("DNS")
151 gensec_client
.set_target_hostname(target_hostname
)
152 gensec_client
.want_feature(gensec
.FEATURE_SEAL
)
153 gensec_client
.start_mech_by_sasl_name("GSSAPI")
154 server_to_client
= ""
156 (client_finished
, client_to_server
) = gensec_client
.update(server_to_client
)
158 print "Successfully obtained Kerberos ticket to DNS/%s as %s" \
159 % (target_hostname
, creds
.get_username())
162 # Only raise an exception if they all failed
163 if i
!= len(ans
) - 1:
167 except RuntimeError as e
:
168 os
.unlink(ccachename
)
172 class dnsobj(object):
173 """an object to hold a parsed DNS line"""
175 def __init__(self
, string_form
):
176 list = string_form
.split()
178 raise Exception("Invalid DNS entry %r" % string_form
)
182 self
.existing_port
= None
183 self
.existing_weight
= None
184 self
.existing_cname_target
= None
193 self
.nameservers
= []
194 if self
.type == 'SRV':
196 raise Exception("Invalid DNS entry %r" % string_form
)
199 elif self
.type in ['A', 'AAAA']:
200 self
.ip
= list[2] # usually $IP, which gets replaced
201 elif self
.type == 'CNAME':
203 elif self
.type == 'NS':
206 raise Exception("Received unexpected DNS reply of type %s: %s" % (self
.type, string_form
))
210 return "%s %s %s" % (self
.type, self
.name
, self
.ip
)
211 if self
.type == "AAAA":
212 return "%s %s %s" % (self
.type, self
.name
, self
.ip
)
213 if self
.type == "SRV":
214 return "%s %s %s %s" % (self
.type, self
.name
, self
.dest
, self
.port
)
215 if self
.type == "CNAME":
216 return "%s %s %s" % (self
.type, self
.name
, self
.dest
)
217 if self
.type == "NS":
218 return "%s %s %s" % (self
.type, self
.name
, self
.dest
)
221 def parse_dns_line(line
, sub_vars
):
222 """parse a DNS line from."""
223 if line
.startswith("SRV _ldap._tcp.pdc._msdcs.") and not samdb
.am_pdc():
224 # We keep this as compat to the dns_update_list of 4.0/4.1
226 print "Skipping PDC entry (%s) as we are not a PDC" % line
228 subline
= samba
.substitute_var(line
, sub_vars
)
229 if subline
== '' or subline
[0] == "#":
231 return dnsobj(subline
)
234 def hostname_match(h1
, h2
):
235 """see if two hostnames match."""
238 return h1
.lower().rstrip('.') == h2
.lower().rstrip('.')
240 def check_one_dns_name(name
, name_type
, d
=None):
241 resolv_conf
= os
.getenv('RESOLV_CONF')
243 resolv_conf
= '/etc/resolv.conf'
244 resolver
= dns
.resolver
.Resolver(filename
=resolv_conf
, configure
=True)
246 if d
is not None and d
.nameservers
!= []:
247 resolver
.nameservers
= d
.nameservers
249 d
.nameservers
= resolver
.nameservers
251 ans
= resolver
.query(name
, name_type
)
254 def check_dns_name(d
):
255 """check that a DNS entry exists."""
256 normalised_name
= d
.name
.rstrip('.') + '.'
258 print "Looking for DNS entry %s as %s" % (d
, normalised_name
)
260 if opts
.use_file
is not None:
262 dns_file
= open(opts
.use_file
, "r")
266 for line
in dns_file
:
268 if line
== '' or line
[0] == "#":
270 if line
.lower() == str(d
).lower():
275 ans
= check_one_dns_name(normalised_name
, d
.type, d
)
276 except dns
.exception
.Timeout
:
277 raise Exception("Timeout while waiting to contact a working DNS server while looking for %s as %s" % (d
, normalised_name
))
278 except dns
.resolver
.NoNameservers
:
279 raise Exception("Unable to contact a working DNS server while looking for %s as %s" % (d
, normalised_name
))
280 except dns
.resolver
.NXDOMAIN
:
282 print "The DNS entry %s, queried as %s does not exist" % (d
, normalised_name
)
284 except dns
.resolver
.NoAnswer
:
286 print "The DNS entry %s, queried as %s does not hold this record type" % (d
, normalised_name
)
288 except dns
.exception
.DNSException
:
289 raise Exception("Failure while trying to resolve %s as %s" % (d
, normalised_name
))
290 if d
.type in ['A', 'AAAA']:
291 # we need to be sure that our IP is there
293 if str(rdata
) == str(d
.ip
):
295 elif d
.type == 'CNAME':
296 for i
in range(len(ans
)):
297 if hostname_match(ans
[i
].target
, d
.dest
):
300 d
.existing_cname_target
= str(ans
[i
].target
)
302 for i
in range(len(ans
)):
303 if hostname_match(ans
[i
].target
, d
.dest
):
305 elif d
.type == 'SRV':
308 print "Checking %s against %s" % (rdata
, d
)
309 if hostname_match(rdata
.target
, d
.dest
):
310 if str(rdata
.port
) == str(d
.port
):
313 d
.existing_port
= str(rdata
.port
)
314 d
.existing_weight
= str(rdata
.weight
)
317 print "Lookup of %s succeeded, but we failed to find a matching DNS entry for %s" % (normalised_name
, d
)
322 def get_subst_vars(samdb
):
323 """get the list of substitution vars."""
327 vars['DNSDOMAIN'] = samdb
.domain_dns_name()
328 vars['DNSFOREST'] = samdb
.forest_dns_name()
329 vars['HOSTNAME'] = samdb
.host_dns_name()
330 vars['NTDSGUID'] = samdb
.get_ntds_GUID()
331 vars['SITE'] = samdb
.server_site_name()
332 res
= samdb
.search(base
=samdb
.get_default_basedn(), scope
=SCOPE_BASE
, attrs
=["objectGUID"])
333 guid
= samdb
.schema_format_value("objectGUID", res
[0]['objectGUID'][0])
334 vars['DOMAINGUID'] = guid
337 vars['IF_RWDC'] = "# "
338 vars['IF_RODC'] = "# "
339 vars['IF_PDC'] = "# "
341 vars['IF_RWGC'] = "# "
342 vars['IF_ROGC'] = "# "
343 vars['IF_DNS_DOMAIN'] = "# "
344 vars['IF_RWDNS_DOMAIN'] = "# "
345 vars['IF_RODNS_DOMAIN'] = "# "
346 vars['IF_DNS_FOREST'] = "# "
347 vars['IF_RWDNS_FOREST'] = "# "
348 vars['IF_R0DNS_FOREST'] = "# "
350 am_rodc
= samdb
.am_rodc()
359 # check if we "are DNS server"
360 res
= samdb
.search(base
=samdb
.get_config_basedn(),
361 expression
='(objectguid=%s)' % vars['NTDSGUID'],
362 attrs
=["options", "msDS-hasMasterNCs"])
365 if "options" in res
[0]:
366 options
= int(res
[0]["options"][0])
367 if (options
& dsdb
.DS_NTDSDSA_OPT_IS_GC
) != 0:
374 basedn
= str(samdb
.get_default_basedn())
375 forestdn
= str(samdb
.get_root_basedn())
377 if "msDS-hasMasterNCs" in res
[0]:
378 for e
in res
[0]["msDS-hasMasterNCs"]:
379 if str(e
) == "DC=DomainDnsZones,%s" % basedn
:
380 vars['IF_DNS_DOMAIN'] = ""
382 vars['IF_RODNS_DOMAIN'] = ""
384 vars['IF_RWDNS_DOMAIN'] = ""
385 if str(e
) == "DC=ForestDnsZones,%s" % forestdn
:
386 vars['IF_DNS_FOREST'] = ""
388 vars['IF_RODNS_FOREST'] = ""
390 vars['IF_RWDNS_FOREST'] = ""
395 def call_nsupdate(d
, op
="add"):
396 """call nsupdate for an entry."""
397 global ccachename
, nsupdate_cmd
, krb5conf
399 assert(op
in ["add", "delete"])
402 print "Calling nsupdate for %s (%s)" % (d
, op
)
404 if opts
.use_file
is not None:
406 rfile
= open(opts
.use_file
, 'r+')
409 rfile
= open(opts
.use_file
, 'w+')
410 # Open it for reading again, in case someone else got to it first
411 rfile
= open(opts
.use_file
, 'r+')
412 fcntl
.lockf(rfile
, fcntl
.LOCK_EX
)
413 (file_dir
, file_name
) = os
.path
.split(opts
.use_file
)
414 (tmp_fd
, tmpfile
) = tempfile
.mkstemp(dir=file_dir
, prefix
=file_name
, suffix
="XXXXXX")
415 wfile
= os
.fdopen(tmp_fd
, 'a')
419 l
= parse_dns_line(line
, {})
420 if str(l
).lower() == str(d
).lower():
424 wfile
.write(str(d
)+"\n")
425 os
.rename(tmpfile
, opts
.use_file
)
426 fcntl
.lockf(rfile
, fcntl
.LOCK_UN
)
429 normalised_name
= d
.name
.rstrip('.') + '.'
431 (tmp_fd
, tmpfile
) = tempfile
.mkstemp()
432 f
= os
.fdopen(tmp_fd
, 'w')
433 if d
.nameservers
!= []:
434 f
.write('server %s\n' % d
.nameservers
[0])
436 f
.write("update %s %s %u A %s\n" % (op
, normalised_name
, default_ttl
, d
.ip
))
438 f
.write("update %s %s %u AAAA %s\n" % (op
, normalised_name
, default_ttl
, d
.ip
))
440 if op
== "add" and d
.existing_port
is not None:
441 f
.write("update delete %s SRV 0 %s %s %s\n" % (normalised_name
, d
.existing_weight
,
442 d
.existing_port
, d
.dest
))
443 f
.write("update %s %s %u SRV 0 100 %s %s\n" % (op
, normalised_name
, default_ttl
, d
.port
, d
.dest
))
444 if d
.type == "CNAME":
445 f
.write("update %s %s %u CNAME %s\n" % (op
, normalised_name
, default_ttl
, d
.dest
))
447 f
.write("update %s %s %u NS %s\n" % (op
, normalised_name
, default_ttl
, d
.dest
))
453 # Set a bigger MTU size to work around a bug in nsupdate's doio_send()
454 os
.environ
["SOCKET_WRAPPER_MTU"] = "2000"
458 os
.environ
["KRB5CCNAME"] = ccachename
460 cmd
= nsupdate_cmd
[:]
464 env
["KRB5_CONFIG"] = krb5conf
466 env
["KRB5CCNAME"] = ccachename
467 ret
= subprocess
.call(cmd
, shell
=False, env
=env
)
469 if opts
.fail_immediately
:
471 print("Failed update with %s" % tmpfile
)
473 error_count
= error_count
+ 1
475 print("Failed nsupdate: %d" % ret
)
476 except Exception, estr
:
477 if opts
.fail_immediately
:
479 error_count
= error_count
+ 1
481 print("Failed nsupdate: %s : %s" % (str(d
), estr
))
484 # Let socket_wrapper set the default MTU size
485 os
.environ
["SOCKET_WRAPPER_MTU"] = "0"
488 def call_samba_tool(d
, op
="add", zone
=None):
489 """call samba-tool dns to update an entry."""
491 assert(op
in ["add", "delete"])
493 if (sub_vars
['DNSFOREST'] != sub_vars
['DNSDOMAIN']) and \
494 sub_vars
['DNSFOREST'].endswith('.' + sub_vars
['DNSDOMAIN']):
495 print "Refusing to use samba-tool when forest %s is under domain %s" \
496 % (sub_vars
['DNSFOREST'], sub_vars
['DNSDOMAIN'])
499 print "Calling samba-tool dns for %s (%s)" % (d
, op
)
501 normalised_name
= d
.name
.rstrip('.') + '.'
503 if normalised_name
== (sub_vars
['DNSDOMAIN'] + '.'):
505 zone
= sub_vars
['DNSDOMAIN']
506 elif normalised_name
== (sub_vars
['DNSFOREST'] + '.'):
508 zone
= sub_vars
['DNSFOREST']
509 elif normalised_name
== ('_msdcs.' + sub_vars
['DNSFOREST'] + '.'):
511 zone
= '_msdcs.' + sub_vars
['DNSFOREST']
513 if not normalised_name
.endswith('.' + sub_vars
['DNSDOMAIN'] + '.'):
514 print "Not Calling samba-tool dns for %s (%s), %s not in %s" % (d
, op
, normalised_name
, sub_vars
['DNSDOMAIN'] + '.')
516 elif normalised_name
.endswith('._msdcs.' + sub_vars
['DNSFOREST'] + '.'):
517 zone
= '_msdcs.' + sub_vars
['DNSFOREST']
519 zone
= sub_vars
['DNSDOMAIN']
520 len_zone
= len(zone
)+2
521 short_name
= normalised_name
[:-len_zone
]
523 len_zone
= len(zone
)+2
524 short_name
= normalised_name
[:-len_zone
]
527 args
= [rpc_server_ip
, zone
, short_name
, "A", d
.ip
]
529 args
= [rpc_server_ip
, zone
, short_name
, "AAAA", d
.ip
]
531 if op
== "add" and d
.existing_port
is not None:
532 print "Not handling modify of exising SRV %s using samba-tool" % d
535 args
= [rpc_server_ip
, zone
, short_name
, "SRV",
536 "%s %s %s %s" % (d
.existing_weight
,
537 d
.existing_port
, "0", "100"),
538 "%s %s %s %s" % (d
.dest
, d
.port
, "0", "100")]
540 args
= [rpc_server_ip
, zone
, short_name
, "SRV", "%s %s %s %s" % (d
.dest
, d
.port
, "0", "100")]
541 if d
.type == "CNAME":
542 if d
.existing_cname_target
is None:
543 args
= [rpc_server_ip
, zone
, short_name
, "CNAME", d
.dest
]
546 args
= [rpc_server_ip
, zone
, short_name
, "CNAME",
547 d
.existing_cname_target
.rstrip('.'), d
.dest
]
550 args
= [rpc_server_ip
, zone
, short_name
, "NS", d
.dest
]
556 print "Calling samba-tool dns %s -k no -P %s" % (op
, args
)
557 ret
= cmd
._run
("dns", op
, "-k", "no", "-P", *args
)
559 if opts
.fail_immediately
:
561 error_count
= error_count
+ 1
563 print("Failed 'samba-tool dns' based update: %s" % (str(d
)))
564 except Exception, estr
:
565 if opts
.fail_immediately
:
567 error_count
= error_count
+ 1
569 print("Failed 'samba-tool dns' based update: %s : %s" % (str(d
), estr
))
572 def rodc_dns_update(d
, t
, op
):
573 '''a single DNS update via the RODC netlogon call'''
576 assert(op
in ["add", "delete"])
579 print "Calling netlogon RODC update for %s" % d
582 netlogon
.NlDnsLdapAtSite
: netlogon
.NlDnsInfoTypeNone
,
583 netlogon
.NlDnsGcAtSite
: netlogon
.NlDnsDomainNameAlias
,
584 netlogon
.NlDnsDsaCname
: netlogon
.NlDnsDomainNameAlias
,
585 netlogon
.NlDnsKdcAtSite
: netlogon
.NlDnsInfoTypeNone
,
586 netlogon
.NlDnsDcAtSite
: netlogon
.NlDnsInfoTypeNone
,
587 netlogon
.NlDnsRfc1510KdcAtSite
: netlogon
.NlDnsInfoTypeNone
,
588 netlogon
.NlDnsGenericGcAtSite
: netlogon
.NlDnsDomainNameAlias
591 w
= winbind
.winbind("irpc:winbind_server", lp
)
592 dns_names
= netlogon
.NL_DNS_NAME_INFO_ARRAY()
594 name
= netlogon
.NL_DNS_NAME_INFO()
596 name
.dns_domain_info_type
= typemap
[t
]
599 if d
.port
is not None:
600 name
.port
= int(d
.port
)
602 name
.dns_register
= True
604 name
.dns_register
= False
605 dns_names
.names
= [ name
]
606 site_name
= sub_vars
['SITE'].decode('utf-8')
611 ret_names
= w
.DsrUpdateReadOnlyServerDnsRecords(site_name
, default_ttl
, dns_names
)
612 if ret_names
.names
[0].status
!= 0:
613 print("Failed to set DNS entry: %s (status %u)" % (d
, ret_names
.names
[0].status
))
614 error_count
= error_count
+ 1
615 except RuntimeError, reason
:
616 print("Error setting DNS entry of type %u: %s: %s" % (t
, d
, reason
))
617 error_count
= error_count
+ 1
619 if error_count
!= 0 and opts
.fail_immediately
:
623 def call_rodc_update(d
, op
="add"):
624 '''RODCs need to use the netlogon API for nsupdate'''
627 assert(op
in ["add", "delete"])
629 # we expect failure for 3268 if we aren't a GC
630 if d
.port
is not None and int(d
.port
) == 3268:
633 # map the DNS request to a netlogon update type
635 netlogon
.NlDnsLdapAtSite
: '_ldap._tcp.${SITE}._sites.${DNSDOMAIN}',
636 netlogon
.NlDnsGcAtSite
: '_ldap._tcp.${SITE}._sites.gc._msdcs.${DNSDOMAIN}',
637 netlogon
.NlDnsDsaCname
: '${NTDSGUID}._msdcs.${DNSFOREST}',
638 netlogon
.NlDnsKdcAtSite
: '_kerberos._tcp.${SITE}._sites.dc._msdcs.${DNSDOMAIN}',
639 netlogon
.NlDnsDcAtSite
: '_ldap._tcp.${SITE}._sites.dc._msdcs.${DNSDOMAIN}',
640 netlogon
.NlDnsRfc1510KdcAtSite
: '_kerberos._tcp.${SITE}._sites.${DNSDOMAIN}',
641 netlogon
.NlDnsGenericGcAtSite
: '_gc._tcp.${SITE}._sites.${DNSFOREST}'
645 subname
= samba
.substitute_var(map[t
], sub_vars
)
646 if subname
.lower() == d
.name
.lower():
647 # found a match - do the update
648 rodc_dns_update(d
, t
, op
)
651 print("Unable to map to netlogon DNS update: %s" % d
)
654 # get the list of DNS entries we should have
656 dns_update_list
= opts
.update_list
658 dns_update_list
= lp
.private_path('dns_update_list')
660 if opts
.update_cache
:
661 dns_update_cache
= opts
.update_cache
663 dns_update_cache
= lp
.private_path('dns_update_cache')
665 # use our private krb5.conf to avoid problems with the wrong domain
666 # bind9 nsupdate wants the default domain set
667 krb5conf
= lp
.private_path('krb5.conf')
668 os
.environ
['KRB5_CONFIG'] = krb5conf
670 file = open(dns_update_list
, "r")
675 samdb
= SamDB(url
=lp
.samdb_url(), session_info
=system_session(), lp
=lp
)
677 # get the substitution dictionary
678 sub_vars
= get_subst_vars(samdb
)
680 # build up a list of update commands to pass to nsupdate
689 rebuild_cache
= False
691 cfile
= open(dns_update_cache
, 'r+')
694 cfile
= open(dns_update_cache
, 'w+')
695 # Open it for reading again, in case someone else got to it first
696 cfile
= open(dns_update_cache
, 'r+')
697 fcntl
.lockf(cfile
, fcntl
.LOCK_EX
)
700 if line
== '' or line
[0] == "#":
702 c
= parse_dns_line(line
, {})
705 if str(c
) not in cache_set
:
707 cache_set
.add(str(c
))
709 # read each line, and check that the DNS name exists
712 if line
== '' or line
[0] == "#":
714 d
= parse_dns_line(line
, sub_vars
)
717 if d
.type == 'A' and len(IP4s
) == 0:
719 if d
.type == 'AAAA' and len(IP6s
) == 0:
721 if str(d
) not in dup_set
:
725 # now expand the entries, if any are A record with ip set to $IP
726 # then replace with multiple entries, one for each interface IP
732 for i
in range(len(IP4s
)-1):
738 for i
in range(len(IP6s
)-1):
743 # now check if the entries already exist on the DNS server
747 if str(c
).lower() == str(d
).lower():
753 print "need cache add: %s" % d
755 update_list
.append(d
)
757 print "force update: %s" % d
758 elif not check_dns_name(d
):
759 update_list
.append(d
)
761 print "need update: %s" % d
767 if str(c
).lower() == str(d
).lower():
774 print "need cache remove: %s" % c
775 if not opts
.all_names
and not check_dns_name(c
):
777 delete_list
.append(c
)
779 print "need delete: %s" % c
781 if len(delete_list
) == 0 and len(update_list
) == 0 and not rebuild_cache
:
783 print "No DNS updates needed"
787 print "%d DNS updates and %d DNS deletes needed" % (len(update_list
), len(delete_list
))
789 use_samba_tool
= opts
.use_samba_tool
790 use_nsupdate
= opts
.use_nsupdate
792 if len(delete_list
) != 0 or len(update_list
) != 0 and not opts
.nocreds
:
794 creds
= get_credentials(lp
)
795 except RuntimeError as e
:
798 if sub_vars
['IF_RWDNS_DOMAIN'] == "# ":
804 print "Failed to get Kerberos credentials, falling back to samba-tool: %s" % e
805 use_samba_tool
= True
808 # ask nsupdate to delete entries as needed
809 for d
in delete_list
:
810 if d
.rpc
or (not use_nsupdate
and use_samba_tool
):
812 print "update (samba-tool): %s" % d
813 call_samba_tool(d
, op
="delete", zone
=d
.zone
)
816 if d
.name
.lower() == domain
.lower():
818 print "skip delete (rodc): %s" % d
820 if not d
.type in [ 'A', 'AAAA' ]:
822 print "delete (rodc): %s" % d
823 call_rodc_update(d
, op
="delete")
826 print "delete (nsupdate): %s" % d
827 call_nsupdate(d
, op
="delete")
830 print "delete (nsupdate): %s" % d
831 call_nsupdate(d
, op
="delete")
833 # ask nsupdate to add entries as needed
834 for d
in update_list
:
835 if d
.rpc
or (not use_nsupdate
and use_samba_tool
):
837 print "update (samba-tool): %s" % d
838 call_samba_tool(d
, zone
=d
.zone
)
841 if d
.name
.lower() == domain
.lower():
843 print "skip (rodc): %s" % d
845 if not d
.type in [ 'A', 'AAAA' ]:
847 print "update (rodc): %s" % d
851 print "update (nsupdate): %s" % d
855 print "update(nsupdate): %s" % d
859 print "Rebuilding cache at %s" % dns_update_cache
860 (file_dir
, file_name
) = os
.path
.split(dns_update_cache
)
861 (tmp_fd
, tmpfile
) = tempfile
.mkstemp(dir=file_dir
, prefix
=file_name
, suffix
="XXXXXX")
862 wfile
= os
.fdopen(tmp_fd
, 'a')
865 print "Adding %s to %s" % (str(d
), file_name
)
866 wfile
.write(str(d
)+"\n")
867 os
.rename(tmpfile
, dns_update_cache
)
868 fcntl
.lockf(cfile
, fcntl
.LOCK_UN
)
870 # delete the ccache if we created it
871 if ccachename
is not None:
872 os
.unlink(ccachename
)
875 print("Failed update of %u entries" % error_count
)
876 sys
.exit(error_count
)