samba python libs: convert 'except X, (tuple)' to 'except X as e'
[bbaumbach/samba-autobuild/.git] / python / samba / join.py
index 3e70db08d2ad826deaa92b2e89fb214cbf5f72f5..22deff0d6a7fb6aba32476e6ed3f7c78e69bf3c6 100644 (file)
@@ -22,8 +22,8 @@ from samba.auth import system_session
 from samba.samdb import SamDB
 from samba import gensec, Ldb, drs_utils, arcfour_encrypt, string_to_byte_array
 import ldb, samba, sys, uuid
-from samba.ndr import ndr_pack
-from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs
+from samba.ndr import ndr_pack, ndr_unpack
+from samba.dcerpc import security, drsuapi, misc, nbt, lsa, drsblobs, dnsserver, dnsp
 from samba.dsdb import DS_DOMAIN_FUNCTION_2003
 from samba.credentials import Credentials, DONT_USE_KERBEROS
 from samba.provision import secretsdb_self_join, provision, provision_fill, FILL_DRS, FILL_SUBDOMAIN
@@ -35,6 +35,9 @@ from samba.provision.sambadns import setup_bind9_dns
 from samba import read_and_sub_file
 from samba import werror
 from base64 import b64encode
+from samba import WERRORError
+from samba.dnsserver import ARecord, AAAARecord, PTRRecord, CNameRecord, NSRecord, MXRecord, SOARecord, SRVRecord, TXTRecord
+from samba import sd_utils
 import logging
 import talloc
 import random
@@ -52,7 +55,11 @@ class dc_join(object):
     def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None,
                  netbios_name=None, targetdir=None, domain=None,
                  machinepass=None, use_ntvfs=False, dns_backend=None,
-                 promote_existing=False, clone_only=False):
+                 promote_existing=False, clone_only=False,
+                 plaintext_secrets=False):
+        if site is None:
+            site = "Default-First-Site-Name"
+
         ctx.clone_only=clone_only
 
         ctx.logger = logger
@@ -61,6 +68,7 @@ class dc_join(object):
         ctx.site = site
         ctx.targetdir = targetdir
         ctx.use_ntvfs = use_ntvfs
+        ctx.plaintext_secrets = plaintext_secrets
 
         ctx.promote_existing = promote_existing
         ctx.promote_from_dn = None
@@ -84,7 +92,8 @@ class dc_join(object):
 
         try:
             ctx.samdb.search(scope=ldb.SCOPE_ONELEVEL, attrs=["dn"])
-        except ldb.LdbError, (enum, estr):
+        except ldb.LdbError as e4:
+            (enum, estr) = e4.args
             raise DCJoinException(estr)
 
 
@@ -184,6 +193,12 @@ class dc_join(object):
         ctx.adminpass = None
         ctx.partition_dn = None
 
+        ctx.dns_a_dn = None
+        ctx.dns_cname_dn = None
+
+        # Do not normally register 127. addresses but allow override for selftest
+        ctx.force_all_ips = False
+
     def del_noerror(ctx, dn, recursive=False):
         if recursive:
             try:
@@ -198,32 +213,33 @@ class dc_join(object):
         except Exception:
             pass
 
-    def cleanup_old_accounts(ctx):
+    def cleanup_old_accounts(ctx, force=False):
         res = ctx.samdb.search(base=ctx.samdb.get_default_basedn(),
                                expression='sAMAccountName=%s' % ldb.binary_encode(ctx.samname),
                                attrs=["msDS-krbTgtLink", "objectSID"])
         if len(res) == 0:
             return
 
-        creds = Credentials()
-        creds.guess(ctx.lp)
-        try:
-            creds.set_machine_account(ctx.lp)
-            creds.set_kerberos_state(ctx.creds.get_kerberos_state())
-            machine_samdb = SamDB(url="ldap://%s" % ctx.server,
-                                  session_info=system_session(),
-                                credentials=creds, lp=ctx.lp)
-        except:
-            pass
-        else:
-            token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
-            if token_res[0]["tokenGroups"][0] \
-               == res[0]["objectSID"][0]:
-                raise DCJoinException("Not removing account %s which "
-                                   "looks like a Samba DC account "
-                                   "maching the password we already have.  "
-                                   "To override, remove secrets.ldb and secrets.tdb"
-                                % ctx.samname)
+        if not force:
+            creds = Credentials()
+            creds.guess(ctx.lp)
+            try:
+                creds.set_machine_account(ctx.lp)
+                creds.set_kerberos_state(ctx.creds.get_kerberos_state())
+                machine_samdb = SamDB(url="ldap://%s" % ctx.server,
+                                      session_info=system_session(),
+                                    credentials=creds, lp=ctx.lp)
+            except:
+                pass
+            else:
+                token_res = machine_samdb.search(scope=ldb.SCOPE_BASE, base="", attrs=["tokenGroups"])
+                if token_res[0]["tokenGroups"][0] \
+                   == res[0]["objectSID"][0]:
+                    raise DCJoinException("Not removing account %s which "
+                                       "looks like a Samba DC account "
+                                       "maching the password we already have.  "
+                                       "To override, remove secrets.ldb and secrets.tdb"
+                                    % ctx.samname)
 
         ctx.del_noerror(res[0].dn, recursive=True)
 
@@ -250,11 +266,11 @@ class dc_join(object):
                                 ldb.binary_encode("dns/%s" % ctx.dnshostname)))
 
 
-    def cleanup_old_join(ctx):
+    def cleanup_old_join(ctx, force=False):
         """Remove any DNs from a previous join."""
         # find the krbtgt link
         if not ctx.subdomain:
-            ctx.cleanup_old_accounts()
+            ctx.cleanup_old_accounts(force=force)
 
         if ctx.connection_dn is not None:
             ctx.del_noerror(ctx.connection_dn)
@@ -290,6 +306,13 @@ class dc_join(object):
 
             lsaconn.DeleteTrustedDomain(pol_handle, info.info_ex.sid)
 
+        if ctx.dns_a_dn:
+            ctx.del_noerror(ctx.dns_a_dn)
+
+        if ctx.dns_cname_dn:
+            ctx.del_noerror(ctx.dns_cname_dn)
+
+
 
     def promote_possible(ctx):
         """confirm that the account is just a bare NT4 BDC or a member server, so can be safely promoted"""
@@ -314,6 +337,9 @@ class dc_join(object):
         """find a writeable DC for the given domain"""
         try:
             ctx.cldap_ret = ctx.net.finddc(domain=domain, flags=nbt.NBT_SERVER_LDAP | nbt.NBT_SERVER_DS | nbt.NBT_SERVER_WRITABLE)
+        except NTSTATUSError as error:
+            raise Exception("Failed to find a writeable DC for domain '%s': %s" %
+                            (domain, error[1]))
         except Exception:
             raise Exception("Failed to find a writeable DC for domain '%s'" % domain)
         if ctx.cldap_ret.client_site is not None and ctx.cldap_ret.client_site != "":
@@ -379,7 +405,8 @@ class dc_join(object):
         '''check if a DN exists'''
         try:
             res = ctx.samdb.search(base=dn, scope=ldb.SCOPE_BASE, attrs=[])
-        except ldb.LdbError, (enum, estr):
+        except ldb.LdbError as e5:
+            (enum, estr) = e5.args
             if enum == ldb.ERR_NO_SUCH_OBJECT:
                 return False
             raise
@@ -417,7 +444,7 @@ class dc_join(object):
     def drsuapi_connect(ctx):
         '''make a DRSUAPI connection to the naming master'''
         binding_options = "seal"
-        if ctx.lp.log_level() >= 4:
+        if ctx.lp.log_level() >= 9:
             binding_options += ",print"
         binding_string = "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options)
         ctx.drsuapi = drsuapi.drsuapi(binding_string, ctx.lp, ctx.creds)
@@ -680,7 +707,8 @@ class dc_join(object):
                                       ctx.acct_pass,
                                       force_change_at_next_login=False,
                                       username=ctx.samname)
-            except ldb.LdbError, (num, _):
+            except ldb.LdbError as e2:
+                (num, _) = e2.args
                 if num != ldb.ERR_UNWILLING_TO_PERFORM:
                     pass
                 ctx.net.set_password(account_name=ctx.samname,
@@ -688,12 +716,16 @@ class dc_join(object):
                                      newpassword=ctx.acct_pass.encode('utf-8'))
 
             res = ctx.samdb.search(base=ctx.acct_dn, scope=ldb.SCOPE_BASE,
-                                   attrs=["msDS-KeyVersionNumber"])
+                                   attrs=["msDS-KeyVersionNumber",
+                                          "objectSID"])
             if "msDS-KeyVersionNumber" in res[0]:
                 ctx.key_version_number = int(res[0]["msDS-KeyVersionNumber"][0])
             else:
                 ctx.key_version_number = None
 
+            ctx.new_dc_account_sid = ndr_unpack(security.dom_sid,
+                                                res[0]["objectSid"][0])
+
             print("Enabling account")
             m = ldb.Message()
             m.dn = ldb.Dn(ctx.samdb, ctx.acct_dn)
@@ -725,7 +757,8 @@ class dc_join(object):
                                                 samba.dsdb.UF_ACCOUNTDISABLE)
                 try:
                     ctx.samdb.add(msg)
-                except ldb.LdbError, (num, _):
+                except ldb.LdbError as e:
+                    (num, _) = e.args
                     if num != ldb.ERR_ENTRY_ALREADY_EXISTS:
                         raise
 
@@ -741,7 +774,8 @@ class dc_join(object):
                                       ctx.dnspass,
                                       force_change_at_next_login=False,
                                       username=ctx.samname)
-            except ldb.LdbError, (num, _):
+            except ldb.LdbError as e3:
+                (num, _) = e3.args
                 if num != ldb.ERR_UNWILLING_TO_PERFORM:
                     raise
                 ctx.net.set_password(account_name="dns-%s" % ctx.myname,
@@ -813,7 +847,8 @@ class dc_join(object):
                 hostname=ctx.myname, domainsid=ctx.domsid,
                 machinepass=ctx.acct_pass, serverrole="active directory domain controller",
                 sitename=ctx.site, lp=ctx.lp, ntdsguid=ctx.ntds_guid,
-                use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend)
+                use_ntvfs=ctx.use_ntvfs, dns_backend=ctx.dns_backend,
+                plaintext_secrets=ctx.plaintext_secrets)
         print "Provision OK for domain DN %s" % presult.domaindn
         ctx.local_samdb = presult.samdb
         ctx.lp          = presult.lp
@@ -886,7 +921,7 @@ class dc_join(object):
                 repl_creds = ctx.creds
 
             binding_options = "seal"
-            if ctx.lp.log_level() >= 5:
+            if ctx.lp.log_level() >= 9:
                 binding_options += ",print"
             repl = drs_utils.drs_Replicate(
                 "ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
@@ -935,7 +970,8 @@ class dc_join(object):
                     repl.replicate(ctx.rid_manager_dn, source_dsa_invocation_id,
                                    destination_dsa_guid,
                                    exop=drsuapi.DRSUAPI_EXOP_FSMO_RID_ALLOC)
-                except samba.DsExtendedError, (enum, estr):
+                except samba.DsExtendedError as e1:
+                    (enum, estr) = e1.args
                     if enum == drsuapi.DRSUAPI_EXOP_ERR_FSMO_NOT_OWNER:
                         print "WARNING: Unable to replicate own RID Set, as server %s (the server we joined) is not the RID Master." % ctx.server
                         print "NOTE: This is normal and expected, Samba will be able to create users after it contacts the RID Master at first startup."
@@ -970,6 +1006,175 @@ class dc_join(object):
 
         ctx.drsuapi.DsReplicaUpdateRefs(ctx.drsuapi_handle, 1, r)
 
+    def join_add_dns_records(ctx):
+        """Remotely Add a DNS record to the target DC.  We assume that if we
+           replicate DNS that the server holds the DNS roles and can accept
+           updates.
+
+           This avoids issues getting replication going after the DC
+           first starts as the rest of the domain does not have to
+           wait for samba_dnsupdate to run successfully.
+
+           Specifically, we add the records implied by the DsReplicaUpdateRefs
+           call above.
+
+           We do not just run samba_dnsupdate as we want to strictly
+           operate against the DC we just joined:
+            - We do not want to query another DNS server
+            - We do not want to obtain a Kerberos ticket
+              (as the KDC we select may not be the DC we just joined,
+              and so may not be in sync with the password we just set)
+            - We do not wish to set the _ldap records until we have started
+            - We do not wish to use NTLM (the --use-samba-tool mode forces
+              NTLM)
+
+        """
+
+        client_version = dnsserver.DNS_CLIENT_VERSION_LONGHORN
+        record_type = dnsp.DNS_TYPE_A
+        select_flags = dnsserver.DNS_RPC_VIEW_AUTHORITY_DATA |\
+                       dnsserver.DNS_RPC_VIEW_NO_CHILDREN
+
+        zone = ctx.dnsdomain
+        msdcs_zone = "_msdcs.%s" % ctx.dnsforest
+        name = ctx.myname
+        msdcs_cname = str(ctx.ntds_guid)
+        cname_target = "%s.%s" % (name, zone)
+        IPs = samba.interface_ips(ctx.lp, ctx.force_all_ips)
+
+        ctx.logger.info("Adding %d remote DNS records for %s.%s" % \
+                        (len(IPs), name, zone))
+
+        binding_options = "sign"
+        dns_conn = dnsserver.dnsserver("ncacn_ip_tcp:%s[%s]" % (ctx.server, binding_options),
+                                      ctx.lp, ctx.creds)
+
+
+        name_found = True
+
+        sd_helper = samba.sd_utils.SDUtils(ctx.samdb)
+
+        change_owner_sd = security.descriptor()
+        change_owner_sd.owner_sid = ctx.new_dc_account_sid
+        change_owner_sd.group_sid = security.dom_sid("%s-%d" %
+                                                     (str(ctx.domsid),
+                                                      security.DOMAIN_RID_DCS))
+
+        # TODO: Remove any old records from the primary DNS name
+        try:
+            (buflen, res) \
+                = dns_conn.DnssrvEnumRecords2(client_version,
+                                              0,
+                                              ctx.server,
+                                              zone,
+                                              name,
+                                              None,
+                                              dnsp.DNS_TYPE_ALL,
+                                              select_flags,
+                                              None,
+                                              None)
+        except WERRORError as e:
+            if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
+                name_found = False
+                pass
+
+        if name_found:
+            for rec in res.rec:
+                for record in rec.records:
+                    if record.wType == dnsp.DNS_TYPE_A or \
+                       record.wType == dnsp.DNS_TYPE_AAAA:
+                        # delete record
+                        del_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+                        del_rec_buf.rec = record
+                        try:
+                            dns_conn.DnssrvUpdateRecord2(client_version,
+                                                         0,
+                                                         ctx.server,
+                                                         zone,
+                                                         name,
+                                                         None,
+                                                         del_rec_buf)
+                        except WERRORError as e:
+                            if e.args[0] == werror.WERR_DNS_ERROR_NAME_DOES_NOT_EXIST:
+                                pass
+                            else:
+                                raise
+
+        for IP in IPs:
+            if IP.find(':') != -1:
+                ctx.logger.info("Adding DNS AAAA record %s.%s for IPv6 IP: %s"
+                                % (name, zone, IP))
+                rec = AAAARecord(IP)
+            else:
+                ctx.logger.info("Adding DNS A record %s.%s for IPv4 IP: %s"
+                                % (name, zone, IP))
+                rec = ARecord(IP)
+
+            # Add record
+            add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+            add_rec_buf.rec = rec
+            dns_conn.DnssrvUpdateRecord2(client_version,
+                                         0,
+                                         ctx.server,
+                                         zone,
+                                         name,
+                                         add_rec_buf,
+                                         None)
+
+        if (len(IPs) > 0):
+            domaindns_zone_dn = ldb.Dn(ctx.samdb, ctx.domaindns_zone)
+            (ctx.dns_a_dn, ldap_record) \
+                = ctx.samdb.dns_lookup("%s.%s" % (name, zone),
+                                       dns_partition=domaindns_zone_dn)
+
+            # Make the DC own the DNS record, not the administrator
+            sd_helper.modify_sd_on_dn(ctx.dns_a_dn, change_owner_sd,
+                                      controls=["sd_flags:1:%d"
+                                                % (security.SECINFO_OWNER
+                                                   | security.SECINFO_GROUP)])
+
+
+            # Add record
+            ctx.logger.info("Adding DNS CNAME record %s.%s for %s"
+                            % (msdcs_cname, msdcs_zone, cname_target))
+
+            add_rec_buf = dnsserver.DNS_RPC_RECORD_BUF()
+            rec = CNameRecord(cname_target)
+            add_rec_buf.rec = rec
+            dns_conn.DnssrvUpdateRecord2(client_version,
+                                         0,
+                                         ctx.server,
+                                         msdcs_zone,
+                                         msdcs_cname,
+                                         add_rec_buf,
+                                         None)
+
+            forestdns_zone_dn = ldb.Dn(ctx.samdb, ctx.forestdns_zone)
+            (ctx.dns_cname_dn, ldap_record) \
+                = ctx.samdb.dns_lookup("%s.%s" % (msdcs_cname, msdcs_zone),
+                                       dns_partition=forestdns_zone_dn)
+
+            # Make the DC own the DNS record, not the administrator
+            sd_helper.modify_sd_on_dn(ctx.dns_cname_dn, change_owner_sd,
+                                      controls=["sd_flags:1:%d"
+                                                % (security.SECINFO_OWNER
+                                                   | security.SECINFO_GROUP)])
+
+        ctx.logger.info("All other DNS records (like _ldap SRV records) " +
+                        "will be created samba_dnsupdate on first startup")
+
+
+    def join_replicate_new_dns_records(ctx):
+        for nc in (ctx.domaindns_zone, ctx.forestdns_zone):
+            if nc in ctx.nc_list:
+                ctx.logger.info("Replicating new DNS records in %s" % (str(nc)))
+                ctx.repl.replicate(nc, ctx.source_dsa_invocation_id,
+                                   ctx.ntds_guid, rodc=ctx.RODC,
+                                   replica_flags=ctx.replica_flags,
+                                   full_sync=False)
+
+
+
     def join_finalise(ctx):
         """Finalise the join, mark us synchronised and setup secrets db."""
 
@@ -1186,6 +1391,11 @@ class dc_join(object):
                 ctx.join_add_objects2()
                 ctx.join_provision_own_domain()
                 ctx.join_setup_trusts()
+
+            if not ctx.clone_only and ctx.dns_backend != "NONE":
+                ctx.join_add_dns_records()
+                ctx.join_replicate_new_dns_records()
+
             ctx.join_finalise()
         except:
             try:
@@ -1200,11 +1410,12 @@ class dc_join(object):
 def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
               targetdir=None, domain=None, domain_critical_only=False,
               machinepass=None, use_ntvfs=False, dns_backend=None,
-              promote_existing=False):
+              promote_existing=False, plaintext_secrets=False):
     """Join as a RODC."""
 
     ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
-                  machinepass, use_ntvfs, dns_backend, promote_existing)
+                  machinepass, use_ntvfs, dns_backend, promote_existing,
+                  plaintext_secrets)
 
     lp.set("workgroup", ctx.domain_name)
     logger.info("workgroup is %s" % ctx.domain_name)
@@ -1251,10 +1462,11 @@ def join_RODC(logger=None, server=None, creds=None, lp=None, site=None, netbios_
 def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_name=None,
             targetdir=None, domain=None, domain_critical_only=False,
             machinepass=None, use_ntvfs=False, dns_backend=None,
-            promote_existing=False):
+            promote_existing=False, plaintext_secrets=False):
     """Join as a DC."""
     ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, domain,
-                  machinepass, use_ntvfs, dns_backend, promote_existing)
+                  machinepass, use_ntvfs, dns_backend, promote_existing,
+                  plaintext_secrets)
 
     lp.set("workgroup", ctx.domain_name)
     logger.info("workgroup is %s" % ctx.domain_name)
@@ -1300,10 +1512,10 @@ def join_clone(logger=None, server=None, creds=None, lp=None,
 def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
         netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
         netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
-        dns_backend=None):
+        dns_backend=None, plaintext_secrets=False):
     """Join as a DC."""
     ctx = dc_join(logger, server, creds, lp, site, netbios_name, targetdir, parent_domain,
-                  machinepass, use_ntvfs, dns_backend)
+                  machinepass, use_ntvfs, dns_backend, plaintext_secrets)
     ctx.subdomain = True
     if adminpass is None:
         ctx.adminpass = samba.generate_random_password(12, 32)