s4:kdc: Implement KDC plugin hardware authentication policy
[samba.git] / source4 / scripting / bin / samba_dnsupdate
index 3fb540b202c2b6e742c143f696e32f86a60b4ee7..6d9d5bc3deebcdd002d38d40425e5deac9050621 100755 (executable)
@@ -29,7 +29,7 @@ import subprocess
 # and don't get swallowed by a timeout
 os.environ['PYTHONUNBUFFERED'] = '1'
 
-# forcing GMT avoids a problem in some timezones with kerberos. Both MIT
+# forcing GMT avoids a problem in some timezones with kerberos. Both MIT and
 # heimdal can get mutual authentication errors due to the 24 second difference
 # between UTC and GMT when using some zone files (eg. the PDT zone from
 # the US)
@@ -49,10 +49,10 @@ from samba.dcerpc import netlogon, winbind
 from samba.netcmd.dns import cmd_dns
 from samba import gensec
 from samba.kcc import kcc_utils
-from samba.compat import get_string
-from samba.compat import text_type
+from samba.common import get_string
 import ldb
 
+from samba.dnsresolver import DNSResolver
 import dns.resolver
 import dns.exception
 
@@ -60,7 +60,7 @@ default_ttl = 900
 am_rodc = False
 error_count = 0
 
-parser = optparse.OptionParser("samba_dnsupdate")
+parser = optparse.OptionParser("samba_dnsupdate [options]")
 sambaopts = options.SambaOptions(parser)
 parser.add_option_group(sambaopts)
 parser.add_option_group(options.VersionOptions(parser))
@@ -91,35 +91,22 @@ lp = sambaopts.get_loadparm()
 
 domain = lp.get("realm")
 host = lp.get("netbios name")
-if opts.all_interfaces:
-    all_interfaces = True
-else:
-    all_interfaces = False
+all_interfaces = opts.all_interfaces
 
-if opts.current_ip:
-    IPs = opts.current_ip
-else:
-    IPs = samba.interface_ips(lp, all_interfaces)
+IPs = opts.current_ip or samba.interface_ips(lp, bool(all_interfaces)) or []
 
 nsupdate_cmd = lp.get('nsupdate command')
 dns_zone_scavenging = lp.get("dns zone scavenging")
 
 if len(IPs) == 0:
-    print("No IP interfaces - skipping DNS updates")
+    print("No IP interfaces - skipping DNS updates\n")
+    parser.print_usage()
     sys.exit(0)
 
-if opts.rpc_server_ip:
-    rpc_server_ip = opts.rpc_server_ip
-else:
-    rpc_server_ip = IPs[0]
+rpc_server_ip = opts.rpc_server_ip or IPs[0]
 
-IP6s = []
-IP4s = []
-for i in IPs:
-    if i.find(':') != -1:
-        IP6s.append(i)
-    else:
-        IP4s.append(i)
+IP6s = [ip for ip in IPs if ':' in ip]
+IP4s = [ip for ip in IPs if ':' not in ip]
 
 smb_conf = sambaopts.get_loadparm_path()
 
@@ -132,27 +119,22 @@ def get_possible_rw_dns_server(creds, domain):
        (4.6 and prior) do not maintain this value, so add NS servers
        as well"""
 
-    hostnames = []
     ans_soa = check_one_dns_name(domain, 'SOA')
-
     # Actually there is only one
-    for i in range(len(ans_soa)):
-        hostnames.append(str(ans_soa[i].mname).rstrip('.'))
+    hosts_soa = [str(a.mname).rstrip('.') for a in ans_soa]
 
     # This is not strictly legit, but old Samba domains may have an
     # unmaintained SOA record, so go for any NS that we can get a
     # ticket to.
     ans_ns = check_one_dns_name(domain, 'NS')
-
     # Actually there is only one
-    for i in range(len(ans_ns)):
-        hostnames.append(str(ans_ns[i].target).rstrip('.'))
+    hosts_ns = [str(a.target).rstrip('.') for a in ans_ns]
 
-    return hostnames
+    return hosts_soa + hosts_ns
 
 def get_krb5_rw_dns_server(creds, domain):
     """Get a list of read-write DNS servers that we can obtain a ticket
-       for, starting with the SOA.  The SOA is the correct answer, but
+       to, starting with the SOA.  The SOA is the correct answer, but
        old Samba domains (4.6 and prior) do not maintain this value,
        so continue with the NS servers as well until we get one that
        the KDC will issue a ticket to.
@@ -160,8 +142,7 @@ def get_krb5_rw_dns_server(creds, domain):
 
     rw_dns_servers = get_possible_rw_dns_server(creds, domain)
     # Actually there is only one
-    for i in range(len(rw_dns_servers)):
-        target_hostname = str(rw_dns_servers[i])
+    for i, target_hostname in enumerate(rw_dns_servers):
         settings = {}
         settings["lp_ctx"] = lp
         settings["target_hostname"] = target_hostname
@@ -181,9 +162,8 @@ def get_krb5_rw_dns_server(creds, domain):
             return target_hostname
         except RuntimeError:
             # Only raise an exception if they all failed
-            if i != len(rw_dns_servers) - 1:
-                pass
-            raise
+            if i == len(rw_dns_servers) - 1:
+                raise
 
 def get_credentials(lp):
     """# get credentials if we haven't got them already."""
@@ -278,10 +258,8 @@ def hostname_match(h1, h2):
     return h1.lower().rstrip('.') == h2.lower().rstrip('.')
 
 def get_resolver(d=None):
-    resolv_conf = os.getenv('RESOLV_CONF')
-    if not resolv_conf:
-        resolv_conf = '/etc/resolv.conf'
-    resolver = dns.resolver.Resolver(filename=resolv_conf, configure=True)
+    resolv_conf = os.getenv('RESOLV_CONF', default='/etc/resolv.conf')
+    resolver = DNSResolver(filename=resolv_conf, configure=True)
 
     if d is not None and d.nameservers != []:
         resolver.nameservers = d.nameservers
@@ -290,11 +268,10 @@ def get_resolver(d=None):
 
 def check_one_dns_name(name, name_type, d=None):
     resolver = get_resolver(d)
-    if d is not None and len(d.nameservers) == 0:
+    if d and not d.nameservers:
         d.nameservers = resolver.nameservers
-
-    ans = resolver.query(name, name_type)
-    return ans
+    # dns.resolver.Answer
+    return resolver.resolve(name, name_type)
 
 def check_dns_name(d):
     """check that a DNS entry exists."""
@@ -308,12 +285,13 @@ def check_dns_name(d):
         except IOError:
             return False
 
-        for line in dns_file:
-            line = line.strip()
-            if line == '' or line[0] == "#":
-                continue
-            if line.lower() == str(d).lower():
-                return True
+        with dns_file:
+            for line in dns_file:
+                line = line.strip()
+                if line == '' or line[0] == "#":
+                    continue
+                if line.lower() == str(d).lower():
+                    return True
         return False
 
     try:
@@ -451,14 +429,13 @@ def call_nsupdate(d, op="add"):
             rfile = open(opts.use_file, 'r+')
         except IOError:
             # Perhaps create it
-            rfile = open(opts.use_file, 'w+')
+            open(opts.use_file, 'w+').close()
             # Open it for reading again, in case someone else got to it first
             rfile = open(opts.use_file, 'r+')
         fcntl.lockf(rfile, fcntl.LOCK_EX)
         (file_dir, file_name) = os.path.split(opts.use_file)
         (tmp_fd, tmpfile) = tempfile.mkstemp(dir=file_dir, prefix=file_name, suffix="XXXXXX")
         wfile = os.fdopen(tmp_fd, 'a')
-        rfile.seek(0)
         for line in rfile:
             if op == "delete":
                 l = parse_dns_line(line, {})
@@ -467,8 +444,9 @@ def call_nsupdate(d, op="add"):
             wfile.write(line)
         if op == "add":
             wfile.write(str(d)+"\n")
+        rfile.close()
+        wfile.close()
         os.rename(tmpfile, opts.use_file)
-        fcntl.lockf(rfile, fcntl.LOCK_UN)
         return
 
     if opts.verbose:
@@ -590,15 +568,10 @@ def call_samba_tool(d, op="add", zone=None):
         args = [rpc_server_ip, zone, short_name, "AAAA", d.ip]
     if d.type == "SRV":
         if op == "add" and d.existing_port is not None:
-            print("Not handling modify of exising SRV %s using samba-tool" % d)
+            print("Not handling modify of existing SRV %s using samba-tool" % d)
             return False
-            op = "update"
-            args = [rpc_server_ip, zone, short_name, "SRV",
-                    "%s %s %s %s" % (d.existing_weight,
-                                     d.existing_port, "0", "100"),
-                    "%s %s %s %s" % (d.dest, d.port, "0", "100")]
-        else:
-            args = [rpc_server_ip, zone, short_name, "SRV", "%s %s %s %s" % (d.dest, d.port, "0", "100")]
+        args = [rpc_server_ip, zone, short_name, "SRV",
+                "%s %s %s %s" % (d.dest, d.port, "0", "100")]
     if d.type == "CNAME":
         if d.existing_cname_target is None:
             args = [rpc_server_ip, zone, short_name, "CNAME", d.dest]
@@ -617,8 +590,9 @@ def call_samba_tool(d, op="add", zone=None):
     try:
         cmd = cmd_dns()
         if opts.verbose:
-            print("Calling samba-tool dns %s -k no -P %s" % (op, args))
-        ret = cmd._run("dns", op, "-k", "no", "-P", *args)
+            print(f'Calling samba-tool dns {op} --use-kerberos off -P {args}')
+        command, args = cmd._resolve("dns", op, "--use-kerberos", "off", "-P", *args)
+        ret = command._run(*args)
         if ret == -1:
             if opts.fail_immediately:
                 sys.exit(1)
@@ -675,7 +649,7 @@ def rodc_dns_update(d, t, op):
     else:
         name.dns_register = False
     dns_names.names = [ name ]
-    site_name = text_type(sub_vars['SITE'])
+    site_name = sub_vars['SITE']
 
     global error_count
 
@@ -727,15 +701,9 @@ def call_rodc_update(d, op="add"):
 
 
 # get the list of DNS entries we should have
-if opts.update_list:
-    dns_update_list = opts.update_list
-else:
-    dns_update_list = lp.private_path('dns_update_list')
+dns_update_list = opts.update_list or lp.private_path('dns_update_list')
 
-if opts.update_cache:
-    dns_update_cache = opts.update_cache
-else:
-    dns_update_cache = lp.private_path('dns_update_cache')
+dns_update_cache = opts.update_cache or lp.private_path('dns_update_cache')
 
 krb5conf = None
 # only change the krb5.conf if we are not in selftest
@@ -745,7 +713,18 @@ if 'SOCKET_WRAPPER_DIR' not in os.environ:
     krb5conf = lp.private_path('krb5.conf')
     os.environ['KRB5_CONFIG'] = krb5conf
 
-file = open(dns_update_list, "r")
+try:
+    file = open(dns_update_list, "r")
+except OSError as e:
+    if opts.update_list:
+        print("The specified update list does not exist")
+    else:
+        print("The server update list was not found, "
+              "and --update-list was not provided.")
+    print(e)
+    print()
+    parser.print_usage()
+    sys.exit(1)
 
 if opts.nosubs:
     sub_vars = {}
@@ -769,7 +748,7 @@ try:
     cfile = open(dns_update_cache, 'r+')
 except IOError:
     # Perhaps create it
-    cfile = open(dns_update_cache, 'w+')
+    open(dns_update_cache, 'w+').close()
     # Open it for reading again, in case someone else got to it first
     cfile = open(dns_update_cache, 'r+')
 fcntl.lockf(cfile, fcntl.LOCK_EX)
@@ -784,6 +763,8 @@ for line in cfile:
         cache_list.append(c)
         cache_set.add(str(c))
 
+cfile.close()
+
 site_specific_rec = []
 
 # read each line, and check that the DNS name exists
@@ -806,6 +787,8 @@ for line in file:
         dns_list.append(d)
         dup_set.add(str(d))
 
+file.close()
+
 # Perform automatic site coverage by default
 auto_coverage = True
 
@@ -895,7 +878,7 @@ else:
 use_samba_tool = opts.use_samba_tool
 use_nsupdate = opts.use_nsupdate
 # get our krb5 creds
-if len(delete_list) != 0 or len(update_list) != 0 and not opts.nocreds:
+if (delete_list or update_list) and not opts.nocreds:
     try:
         creds = get_credentials(lp)
     except RuntimeError as e:
@@ -915,7 +898,7 @@ if len(delete_list) != 0 or len(update_list) != 0 and not opts.nocreds:
 for d in delete_list:
     if d.rpc or (not use_nsupdate and use_samba_tool):
         if opts.verbose:
-            print("update (samba-tool): %s" % d)
+            print("delete (samba-tool): %s" % d)
         call_samba_tool(d, op="delete", zone=d.zone)
 
     elif am_rodc:
@@ -923,7 +906,7 @@ for d in delete_list:
             if opts.verbose:
                 print("skip delete (rodc): %s" % d)
             continue
-        if not d.type in [ 'A', 'AAAA' ]:
+        if d.type not in [ 'A', 'AAAA' ]:
             if opts.verbose:
                 print("delete (rodc): %s" % d)
             call_rodc_update(d, op="delete")
@@ -948,7 +931,7 @@ for d in update_list:
             if opts.verbose:
                 print("skip (rodc): %s" % d)
             continue
-        if not d.type in [ 'A', 'AAAA' ]:
+        if d.type not in [ 'A', 'AAAA' ]:
             if opts.verbose:
                 print("update (rodc): %s" % d)
             call_rodc_update(d)
@@ -970,8 +953,8 @@ if rebuild_cache:
         if opts.verbose:
             print("Adding %s to %s" % (str(d), file_name))
         wfile.write(str(d)+"\n")
+    wfile.close()
     os.rename(tmpfile, dns_update_cache)
-fcntl.lockf(cfile, fcntl.LOCK_UN)
 
 # delete the ccache if we created it
 if ccachename is not None: