STEP06? samba-tool domain join NEWDOMAIN new domain in a forest
authorStefan Metzmacher <metze@samba.org>
Fri, 9 Jan 2015 13:45:18 +0000 (14:45 +0100)
committerStefan Metzmacher <metze@samba.org>
Mon, 17 Feb 2020 11:31:13 +0000 (12:31 +0100)
python/samba/join.py
python/samba/netcmd/domain.py

index 40920f4f8e5c599f98742426e127f78594f9b921..3a275098d400f6b39102a2c1e98089547505d252 100644 (file)
@@ -125,9 +125,11 @@ class DCJoinContext(object):
         ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
         ctx.config_dn = str(ctx.samdb.get_config_basedn())
         ctx.domsid = security.dom_sid(ctx.samdb.get_domain_sid())
-        ctx.forestsid = ctx.domsid
         ctx.domain_name = ctx.get_domain_name()
         ctx.forest_domain_name = ctx.get_forest_domain_name()
+        ctx.forest_dnsdomain = ctx.get_forest_dns_domain()
+        ctx.forestsid = ctx.get_forest_domain_sid()
+        ctx.forest_domain_guid = ctx.get_forest_domain_guid()
         ctx.invocation_id = misc.GUID(str(uuid.uuid4()))
 
         ctx.dc_ntds_dn = ctx.samdb.get_dsServiceName()
@@ -392,14 +394,62 @@ class DCJoinContext(object):
                                expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
         return str(res[0]["nETBIOSName"][0])
 
-    def get_parent_partition_dn(ctx):
-        '''get the parent domain partition DN from parent DNS name'''
-        res = ctx.samdb.search(base=ctx.config_dn, attrs=[],
+    def get_forest_dns_domain(ctx):
+        '''get dns name of the domain from the partitions record'''
+        partitions_dn = ctx.samdb.get_partitions_dn()
+        res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=["dnsRoot"],
+                               expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
+        return res[0]["dnsRoot"][0]
+
+    def get_forest_domain_sid(ctx):
+        '''get netbios name of the domain from the partitions record'''
+        partitions_dn = ctx.samdb.get_partitions_dn()
+        res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL,
+                               attrs=["ncName"], controls=["extended_dn:1:1"]
+                               expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
+        return str(security.dom_sid(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('SID')))
+
+    def get_forest_domain_guid(ctx):
+        '''get netbios name of the domain from the partitions record'''
+        partitions_dn = ctx.samdb.get_partitions_dn()
+        res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL,
+                               attrs=["ncName"], controls=["extended_dn:1:1"]
+                               expression='ncName=%s' % ldb.binary_encode(str(ctx.samdb.get_root_basedn())))
+        return str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('GUID')))
+
+    def get_crossref_dn_from_dnsdomain(ctx, dnsdomain):
+        '''get the domain partition DN from DNS name'''
+        partitions_dn = ctx.samdb.get_partitions_dn()
+        res = ctx.samdb.search(base=partitions_dn, scope=ldb.SCOPE_ONELEVEL, attrs=[],
                                expression='(&(objectclass=crossRef)(dnsRoot=%s)(systemFlags:%s:=%u))' %
-                               (ldb.binary_encode(ctx.parent_dnsdomain),
+                               (ldb.binary_encode(dnsdomain),
                                 ldb.OID_COMPARATOR_AND, samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN))
+        if len(res) == 0:
+            return None
         return str(res[0].dn)
 
+    def get_netbios_name_from_crossref_dn(ctx, crossref_dn):
+        '''get the netbios name for the partition crossRef DN'''
+        res = ctx.samdb.search(base=crossref_dn, scope=ldb.SCOPE_BASE, attrs=["nETBIOSName"])
+        return res[0]["nETBIOSName"][0]
+
+    def get_dns_name_from_crossref_dn(ctx, crossref_dn):
+        '''get the dns name for the partition crossRef DN'''
+        res = ctx.samdb.search(base=crossref_dn, scope=ldb.SCOPE_BASE, attrs=["dnsRoot"])
+        return res[0]["dnsRoot"][0]
+
+    def get_domain_sid_from_crossref_dn(ctx, crossref_dn):
+        '''get the domain sid for the partition crossRef DN'''
+        res = ctx.samdb.search(base=crossref_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
+                               controls=["extended_dn:1:1")
+        return str(security.dom_sid(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('SID')))
+
+    def get_domain_guid_from_crossref_dn(ctx, crossref_dn):
+        '''get the domain guid for the partition crossRef DN'''
+        res = ctx.samdb.search(base=crossref_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
+                               controls=["extended_dn:1:1")
+        return str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('GUID')))
+
     def get_naming_master(ctx):
         '''get the parent domain partition DN from parent DNS name'''
         res = ctx.samdb.search(base='CN=Partitions,%s' % ctx.config_dn, attrs=['fSMORoleOwner'],
@@ -838,11 +888,15 @@ class DCJoinContext(object):
             "nCName": ctx.base_dn,
             "nETBIOSName": ctx.domain_name,
             "dnsRoot": ctx.dnsdomain,
-            "trustParent": ctx.parent_partition_dn,
             "systemFlags": str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC |samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN),
             "ntSecurityDescriptor": sd_binary,
         }
 
+        if ctx.trust_partition_dn == ctx.parent_crossref_dn:
+            rec["trustParent"] = ctx.trust_partition_dn
+        else:
+            rec["rootTrust"] = ctx.trust_partition_dn
+
         if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
             rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
 
@@ -1385,33 +1439,59 @@ class DCJoinContext(object):
         auth_info = lsa.TrustDomainInfoAuthInfoInternal()
         auth_info.auth_blob = auth_blob
 
-        trustdom_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
-                                                         info,
-                                                         auth_info,
-                                                         security.SEC_STD_DELETE)
+        create_handle = lsaconn.CreateTrustedDomainEx2(pol_handle,
+                                                       info,
+                                                       auth_info,
+                                                       security.SEC_STD_DELETE)
 
-        rec = {
-            "dn": "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
-            "objectclass": "trustedDomain",
-            "trustType": str(info.trust_type),
-            "trustAttributes": str(info.trust_attributes),
-            "trustDirection": str(info.trust_direction),
-            "flatname": ctx.forest_domain_name,
-            "trustPartner": ctx.dnsforest,
-            "trustAuthIncoming": ndr_pack(outgoing),
-            "trustAuthOutgoing": ndr_pack(outgoing),
-            "securityIdentifier": ndr_pack(ctx.forestsid)
-        }
-        ctx.local_samdb.add(rec)
+        modify_handle = lsaconn.OpenTrustedDomainByName(pol_handle,
+                                                        ctx.domain_name,
+                                                        security.SEC_FLAG_MAXIMUM_ALLOWED)
+        try:
+            infoclass = lsa.TrustDomainInfoSupportedEncTypes()
+            infoclass.enc_types = security.KERB_ENCTYPE_RC4_HMAC_MD5
+            infoclass.enc_types |= security.KERB_ENCTYPE_AES128_CTS_HMAC_SHA1_96
+            infoclass.enc_types |= security.KERB_ENCTYPE_AES256_CTS_HMAC_SHA1_96
+            lsaconn.SetInformationTrustedDomain(modify_handle,
+                                                lsa.LSA_TRUSTED_DOMAIN_SUPPORTED_ENCRYPTION_TYPES,
+                                                infoclass)
+        except RuntimeError:
+            # We can ignore the error here -- changing enctypes is for
+            # improved security but the trust will work with default values as
+            # well. In particular, the call may fail against Windows 2003
+            # server as that one doesn't support AES encryption types
+            pass
+        lsaconn.Close(modify_handle)
 
-        rec = {
-            "dn": "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
-            "objectclass": "user",
-            "userAccountControl": str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
-            "clearTextPassword": ctx.trustdom_pass.encode('utf-16-le'),
-            "samAccountName": "%s$" % ctx.forest_domain_name
-        }
-        ctx.local_samdb.add(rec)
+        try:
+            rec = {
+                "dn": "cn=%s,cn=system,%s" % (ctx.dnsforest, ctx.base_dn),
+                "objectclass": "trustedDomain",
+                "trustType": str(info.trust_type),
+                "trustAttributes": str(info.trust_attributes),
+                "trustDirection": str(info.trust_direction),
+                "flatname": ctx.forest_domain_name,
+                "trustPartner": ctx.dnsforest,
+                "trustAuthIncoming": ndr_pack(outgoing),
+                "trustAuthOutgoing": ndr_pack(outgoing),
+                "securityIdentifier": ndr_pack(ctx.forestsid)
+            }
+            ctx.local_samdb.add(rec)
+
+            rec = {
+                "dn": "cn=%s$,cn=users,%s" % (ctx.forest_domain_name, ctx.base_dn),
+                "objectclass": "user",
+                "userAccountControl": str(samba.dsdb.UF_INTERDOMAIN_TRUST_ACCOUNT),
+                "clearTextPassword": ctx.trustdom_pass.encode('utf-16-le'),
+                "samAccountName": "%s$" % ctx.forest_domain_name
+            }
+            ctx.local_samdb.add(rec)
+
+        except RuntimeError:
+            lsaconn.DeleteObject(create_handle)
+            create_handle = None
+        if create_handle is not None:
+            lsaconn.Close(create_handle)
 
     def build_nc_lists(ctx):
         # nc_list is the list of naming context (NC) for which we will
@@ -1581,14 +1661,18 @@ def join_clone(logger=None, server=None, creds=None, lp=None,
     return ctx
 
 
-def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
-                   netbios_name=None, targetdir=None, parent_domain=None, dnsdomain=None,
+def join_newdomain(logger=None, server=None, creds=None, lp=None, site=None,
+                   netbios_name=None, targetdir=None, reference_dnsdomain=None, dnsdomain=None,
                    netbios_domain=None, machinepass=None, adminpass=None, use_ntvfs=False,
                    dns_backend=None, plaintext_secrets=False,
                    backend_store=None, backend_store_size=None):
-    """Join as a DC."""
+    """Join as a NEW domain into the existing forest."""
+    if reference_dnsdomain is None:
+        parent_dnsdomain = ".".join(dnsdomain.split(".")[1:])
+        reference_dnsdomain = parent_dnsdomain
+
     ctx = DCJoinContext(logger, server, creds, lp, site, netbios_name,
-                        targetdir, parent_domain, machinepass, use_ntvfs,
+                        targetdir, reference_dnsdomain, machinepass, use_ntvfs,
                         dns_backend, plaintext_secrets,
                         backend_store=backend_store,
                         backend_store_size=backend_store_size)
@@ -1616,17 +1700,66 @@ def join_subdomain(logger=None, server=None, creds=None, lp=None, site=None,
         ctx.server = res[0]["dnsHostName"]
         logger.info("DNS name of new naming master is %s" % ctx.server)
 
-    ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
-    ctx.forestsid = ctx.domsid
+    def is_dns_child(parent_dnsdomain, child_dnsdomain):
+        parent = parent_dnsdomain.lower.split(".")
+        child = child_dnsdomain.lower.split(".")
+
+        if len(parent) >= len(child):
+            return 0
+
+        for i in range(len(parent)):
+            ir = -i
+            il = ir - 1
+            if ir == 0:
+                ir = None
+            pp = parent[il:ir]
+            cp = child[il:ir]
+            if pp != cp:
+                return 0
+
+        return len(child) - len(parent)
+
+    ctx.parent_crossref_dn = ctx.get_crossref_dn_from_dnsdomain(parent_dnsdomain)
+    if ctx.parent_crossref_dn is not None:
+        ctx.trust_dnsdomain = parent_dnsdomain
+        ctx.trust_sid = ctx.domsid
+        ctx.trust_domain_name = ctx.domain_name
+        ctx.trust_partition_dn = ctx.parent_crossref_dn
+    else:
+        if is_dns_child(reference_domain, domain) > 0:
+            raise DCJoinException("""Unsupported dnsdomain[%s] parent_domain[%s]
+                    reference_domain[%s]""" % (domain, parent_domain, reference_domain)
+
+        ctx.trust_dnsdomain = ctx.forest_dnsdomain
+        ctx.trust_sid = ctx.forestsid
+        ctx.trust_domain_name = ctx.forest_domain_name
+        ctx.trust_crossref_dn = ctx.get_crossref_dn_from_dnsdomain(ctx.trust_dnsdomain)
+
+    ctx.subdomain = True
+    if adminpass is None:
+        ctx.adminpass = samba.generate_random_password(12, 32)
+    else:
+        ctx.adminpass = adminpass
+    # Windows uses 240 bytes as UTF16 so we do
+    ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
+
     ctx.domsid = security.random_sid()
+    ctx.domain_name = netbios_domain
+    ctx.realm = dnsdomain
+    ctx.dnsdomain = dnsdomain
+    ctx.partition_dn = "CN=%s,CN=Partitions,%s" % (ctx.domain_name, ctx.config_dn)
+    ctx.base_dn = samba.dn_from_dns_name(dnsdomain)
+
     ctx.acct_dn = None
     ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
-    # Windows uses 240 bytes as UTF16 so we do
-    ctx.trustdom_pass = samba.generate_random_machine_password(120, 120)
 
     ctx.userAccountControl = samba.dsdb.UF_SERVER_TRUST_ACCOUNT | samba.dsdb.UF_TRUSTED_FOR_DELEGATION
-
-    ctx.SPNs.append('E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s' % ctx.dnsdomain)
+    ctx.SPNs = [
+        "HOST/%s" % ctx.myname,
+        "HOST/%s" % ctx.dnshostname,
+        "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest),
+        "E3514235-4B06-11D1-AB04-00C04FC2DCD2/$NTDSGUID/%s" % ctx.dnsdomain)
+    ]
     ctx.secure_channel_type = misc.SEC_CHAN_BDC
 
     ctx.replica_flags |= (drsuapi.DRSUAPI_DRS_WRIT_REP |
index bea6482f99e699ac4b3b9e03b31829fbac47cbcd..13ca1c1cb5909b94a8ebdd0c18be448d31dd5221 100644 (file)
@@ -610,7 +610,7 @@ class cmd_domain_dcpromo(Command):
 class cmd_domain_join(Command):
     """Join domain as either member or backup domain controller."""
 
-    synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER] [options]"
+    synopsis = "%prog <dnsdomain> [DC|RODC|MEMBER|NEWDOMAIN] [options]"
 
     takes_optiongroups = {
         "sambaopts": options.SambaOptions,
@@ -619,7 +619,7 @@ class cmd_domain_join(Command):
     }
 
     takes_options = [
-        Option("--parent-domain", help="parent domain to create subdomain under", type=str),
+        Option("--reference-domain", help="any domain in the forest where to create newdomain in", type=str),
         Option("--adminpass", type="string", metavar="PASSWORD",
                help="choose adminstrator password when joining as a subdomain (otherwise random)"),
     ]
@@ -678,11 +678,22 @@ class cmd_domain_join(Command):
                       plaintext_secrets=plaintext_secrets,
                       backend_store=backend_store,
                       backend_store_size=backend_store_size)
-        # elif role == "SUBDOMAIN":
-        # subdomain command removed by Gary Lockyer <gary@catalyst.net.nz>
-        # on the 28th June 2019.
+        elif role == "NEWDOMAIN":
+            if not adminpass:
+                logger.info("Administrator password will be set randomly!")
+
+            netbios_domain = lp.get("workgroup")
+            join_newdomain(logger=logger, server=server, creds=creds, lp=lp, dnsdomain=domain,
+                           reference_domain=reference_domain, site=site,
+                           netbios_name=netbios_name, netbios_domain=netbios_domain,
+                           targetdir=targetdir, machinepass=machinepass,
+                           use_ntvfs=use_ntvfs, dns_backend=dns_backend,
+                           adminpass=adminpass,
+                           plaintext_secrets=plaintext_secrets,
+                           backend_store=backend_store,
+                           backend_store_size=backend_store_size)
         else:
-            raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC)" % role)
+            raise CommandError("Invalid role '%s' (possible values: MEMBER, DC, RODC, NEWDOMAIN)" % role)
 
 
 class cmd_domain_demote(Command):