s4-provision Add initial support for joining as a new subdomain
authorAndrew Bartlett <abartlet@samba.org>
Wed, 24 Aug 2011 05:39:51 +0000 (15:39 +1000)
committerAndrew Tridgell <tridge@samba.org>
Mon, 19 Sep 2011 00:57:02 +0000 (10:57 +1000)
To do this we need to reorganise a lot of the provision code, so that
we can create the framework for the inbound replicaton of the config
and schema partitions and then add in the new subdomain locally.

Andrew Bartlett

source4/scripting/python/samba/join.py
source4/scripting/python/samba/provision/__init__.py

index ccb9f06f285474b4252aa5ce2a09ce3367c7194f..195dfc23120f2c81314a4d3f49add3c960d9f44f 100644 (file)
@@ -304,40 +304,47 @@ class dc_join(object):
         r.value_ctr = 1
 
 
-    def DsAddEntry(ctx, rec):
+    def DsAddEntry(ctx, recs):
         '''add a record via the DRSUAPI DsAddEntry call'''
         if ctx.drsuapi is None:
             ctx.drsuapi_connect()
         if ctx.tmp_samdb is None:
             ctx.create_tmp_samdb()
 
-        id = drsuapi.DsReplicaObjectIdentifier()
-        id.dn = rec['dn']
-
-        attrs = []
-        for a in rec:
-            if a == 'dn':
-                continue
-            if not isinstance(rec[a], list):
-                v = [rec[a]]
-            else:
-                v = rec[a]
-            rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
-            attrs.append(rattr)
-
-        attribute_ctr = drsuapi.DsReplicaAttributeCtr()
-        attribute_ctr.num_attributes = len(attrs)
-        attribute_ctr.attributes = attrs
-
-        object = drsuapi.DsReplicaObject()
-        object.identifier = id
-        object.attribute_ctr = attribute_ctr
-
-        first_object = drsuapi.DsReplicaObjectListItem()
-        first_object.object = object
+        objects = []
+        for rec in recs:
+            id = drsuapi.DsReplicaObjectIdentifier()
+            id.dn = rec['dn']
+
+            attrs = []
+            for a in rec:
+                if a == 'dn':
+                    continue
+                if not isinstance(rec[a], list):
+                    v = [rec[a]]
+                else:
+                    v = rec[a]
+                rattr = ctx.tmp_samdb.dsdb_DsReplicaAttribute(ctx.tmp_samdb, a, v)
+                attrs.append(rattr)
+
+            attribute_ctr = drsuapi.DsReplicaAttributeCtr()
+            attribute_ctr.num_attributes = len(attrs)
+            attribute_ctr.attributes = attrs
+
+            object = drsuapi.DsReplicaObject()
+            object.identifier = id
+            object.attribute_ctr = attribute_ctr
+
+            list_object = drsuapi.DsReplicaObjectListItem()
+            list_object.object = object
+            objects.append(list_object)
 
         req2 = drsuapi.DsAddEntryRequest2()
-        req2.first_object = first_object
+        req2.first_object = objects[0]
+        prev = req2.first_object
+        for o in objects[1:]:
+            prev.next_object = o
+            prev = o
 
         (level, ctr) = ctx.drsuapi.DsAddEntry(ctx.drsuapi_handle, 2, req2)
         if ctr.err_ver != 1:
@@ -349,6 +356,7 @@ class dc_join(object):
         if ctr.err_data.dir_err != drsuapi.DRSUAPI_DIRERR_OK:
             print("DsAddEntry failed with dir_err %u" % ctr.err_data.dir_err)
             raise RuntimeError("DsAddEntry failed")
+        return ctr.objects
 
 
     def join_add_ntdsdsa(ctx):
@@ -361,17 +369,12 @@ class dc_join(object):
             "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
             "dMDLocation" : ctx.schema_dn}
 
-        if ctx.subdomain:
-            # the local subdomain NC doesn't exist at this time
-            # so we have to add the base_dn NC later
-            nc_list = [ ctx.config_dn, ctx.schema_dn ]
-        else:
-            nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
+        nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
 
         if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
             rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
 
-        if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003 and not ctx.subdomain:
+        if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
             rec["msDS-HasDomainNCs"] = ctx.base_dn
 
         if ctx.RODC:
@@ -386,32 +389,13 @@ class dc_join(object):
                 rec["msDS-HasMasterNCs"] = nc_list
             rec["options"] = "1"
             rec["invocationId"] = ndr_pack(ctx.invocation_id)
-            if ctx.subdomain:
-                ctx.samdb.add(rec, ['relax:0'])
-            else:
-                ctx.DsAddEntry(rec)
+            ctx.DsAddEntry([rec])
 
         # find the GUID of our NTDS DN
         res = ctx.samdb.search(base=ctx.ntds_dn, scope=ldb.SCOPE_BASE, attrs=["objectGUID"])
         ctx.ntds_guid = misc.GUID(ctx.samdb.schema_format_value("objectGUID", res[0]["objectGUID"][0]))
 
 
-    def join_modify_ntdsdsa(ctx):
-        '''modify the ntdsdsa object to add local partitions'''
-        print "Modifying %s using system privileges" % ctx.ntds_dn
-
-        # this works around the Enterprise Admins ACL on the NTDSDSA object
-        system_session_info = system_session()
-        ctx.samdb.set_session_info(system_session_info)
-
-        m = ldb.Message()
-        m.dn = ldb.Dn(ctx.samdb, ctx.ntds_dn)
-        m["HasMasterNCs"] = ldb.MessageElement(ctx.base_dn, ldb.FLAG_MOD_ADD, "HasMasterNCs")
-        if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
-            m["msDS-HasDomainNCs"] = ldb.MessageElement(ctx.base_dn, ldb.FLAG_MOD_ADD, "msDS-HasDomainNCs")
-            m["msDS-HasMasterNCs"] = ldb.MessageElement(ctx.base_dn, ldb.FLAG_MOD_ADD, "msDS-HasMasterNCs")
-        ctx.samdb.modify(m, controls=['relax:0'])
-
     def join_add_objects(ctx):
         '''add the various objects needed for the join'''
         if ctx.acct_dn:
@@ -440,9 +424,11 @@ class dc_join(object):
         rec = {
             "dn": ctx.server_dn,
             "objectclass" : "server",
+            # windows uses 50000000 decimal for systemFlags. A windows hex/decimal mixup bug?
             "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_RENAME |
                                 samba.dsdb.SYSTEM_FLAG_CONFIG_ALLOW_LIMITED_MOVE |
                                 samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
+            # windows seems to add the dnsHostName later
             "dnsHostName" : ctx.dnshostname}
 
         if ctx.acct_dn:
@@ -507,9 +493,6 @@ class dc_join(object):
     def join_add_objects2(ctx):
         '''add the various objects needed for the join, for subdomains post replication'''
 
-        if not ctx.subdomain:
-            return
-
         print "Adding %s" % ctx.partition_dn
         # NOTE: windows sends a ntSecurityDescriptor here, we
         # let it default
@@ -524,8 +507,47 @@ class dc_join(object):
             "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_CR_NTDS_NC|samba.dsdb.SYSTEM_FLAG_CR_NTDS_DOMAIN)}
         if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
             rec["msDS-Behavior-Version"] = str(ctx.behavior_version)
-        ctx.DsAddEntry(rec)
 
+        rec2 = {
+            "dn" : ctx.ntds_dn,
+            "objectclass" : "nTDSDSA",
+            "systemFlags" : str(samba.dsdb.SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE),
+            "dMDLocation" : ctx.schema_dn}
+
+        nc_list = [ ctx.base_dn, ctx.config_dn, ctx.schema_dn ]
+
+        if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
+            rec2["msDS-Behavior-Version"] = str(ctx.behavior_version)
+
+        if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
+            rec2["msDS-HasDomainNCs"] = ctx.base_dn
+
+        rec2["objectCategory"] = "CN=NTDS-DSA,%s" % ctx.schema_dn
+        rec2["HasMasterNCs"]      = nc_list
+        if ctx.behavior_version >= samba.dsdb.DS_DOMAIN_FUNCTION_2003:
+            rec2["msDS-HasMasterNCs"] = nc_list
+        rec2["options"] = "1"
+        rec2["invocationId"] = ndr_pack(ctx.invocation_id)
+
+        objects = ctx.DsAddEntry([rec, rec2])
+        if len(objects) != 2:
+            raise DCJoinException("Expected 2 objects from DsAddEntry")
+
+        ctx.ntds_guid = objects[1].guid
+
+        print("Replicating partition DN")
+        ctx.repl.replicate(ctx.partition_dn,
+                           misc.GUID("00000000-0000-0000-0000-000000000000"),
+                           ctx.ntds_guid,
+                           exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
+                           replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
+
+        print("Replicating NTDS DN")
+        ctx.repl.replicate(ctx.ntds_dn,
+                           misc.GUID("00000000-0000-0000-0000-000000000000"),
+                           ctx.ntds_guid,
+                           exop=drsuapi.DRSUAPI_EXOP_REPL_OBJ,
+                           replica_flags=drsuapi.DRSUAPI_DRS_WRIT_REP)
 
     def join_provision(ctx):
         '''provision the local SAM'''
@@ -565,14 +587,11 @@ class dc_join(object):
         ctx.local_samdb = ctx.samdb
 
         print("Finding domain GUID from ncName")
-        res = ctx.samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
-                               controls=["extended_dn:1:1"])
+        res = ctx.local_samdb.search(base=ctx.partition_dn, scope=ldb.SCOPE_BASE, attrs=['ncName'],
+                                     controls=["extended_dn:1:1"])
         domguid = str(misc.GUID(ldb.Dn(ctx.samdb, res[0]['ncName'][0]).get_extended_component('GUID')))
         print("Got domain GUID %s" % domguid)
 
-
-        ctx.join_add_ntdsdsa()
-
         print("Calling own domain provision")
 
         logger = logging.getLogger("provision")
@@ -597,6 +616,7 @@ class dc_join(object):
         try:
             source_dsa_invocation_id = misc.GUID(ctx.samdb.get_invocation_id())
             if ctx.ntds_guid is None:
+                print("Using DS_BIND_GUID_W2K3")
                 destination_dsa_guid = misc.GUID(drsuapi.DRSUAPI_DS_BIND_GUID_W2K3)
             else:
                 destination_dsa_guid = ctx.ntds_guid
@@ -634,6 +654,9 @@ class dc_join(object):
                 repl.replicate(ctx.new_krbtgt_dn, source_dsa_invocation_id,
                         destination_dsa_guid,
                         exop=drsuapi.DRSUAPI_EXOP_REPL_SECRET, rodc=True)
+            ctx.repl = repl
+            ctx.source_dsa_invocation_id = source_dsa_invocation_id
+            ctx.destination_dsa_guid = destination_dsa_guid
 
             print "Committing SAM database"
         except:
@@ -787,12 +810,11 @@ class dc_join(object):
         try:
             ctx.join_add_objects()
             ctx.join_provision()
-            ctx.join_add_objects2()
             ctx.join_replicate()
             if ctx.subdomain:
+                ctx.join_add_objects2()
                 ctx.join_provision_own_domain()
                 ctx.join_setup_trusts()
-                ctx.join_modify_ntdsdsa()
             ctx.join_finalise()
         except Exception:
             print "Join failed - cleaning up"
index 0b39167df41e584fcad3bb3bee2f3f0cfba2489f..a3633ab1280d534df4c01fc63b6623d3965b925a 100644 (file)
@@ -995,11 +995,11 @@ def setup_secretsdb(paths, session_info, backend_credentials, lp):
                         "LDAPADMINREALM": backend_credentials.get_realm(),
                         "LDAPADMINPASS_B64": b64encode(backend_credentials.get_password())
                         })
-
-        return secrets_ldb
     except Exception:
         secrets_ldb.transaction_cancel()
         raise
+    return secrets_ldb
+
 
 
 def setup_privileges(path, session_info, lp):
@@ -1597,10 +1597,6 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
         setsysvolacl(samdb, paths.netlogon, paths.sysvol, paths.wheel_gid,
                      domainsid, names.dnsdomain, names.domaindn, lp)
 
-        logger.info("Setting up sam.ldb rootDSE marking as synchronized")
-        setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
-                          { 'NTDSGUID' : names.ntdsguid })
-
         secretsdb_self_join(secrets_ldb, domain=names.domain,
                             realm=names.realm, dnsdomain=names.dnsdomain,
                             netbiosname=names.netbiosname, domainsid=domainsid,
@@ -1624,69 +1620,72 @@ def provision_fill(samdb, secrets_ldb, logger, names, paths,
                 # It might be that this attribute does not exist in this schema
                 raise
 
-        if serverrole == "domain controller":
-            secretsdb_setup_dns(secrets_ldb, names,
-                                paths.private_dir, realm=names.realm,
-                                dnsdomain=names.dnsdomain,
-                                dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
-
-            # Default DNS backend is BIND9 using txt files for zone information
-            if not dns_backend:
-                dns_backend = "BIND9"
-
-            setup_ad_dns(samdb, names, logger, hostip=hostip, hostip6=hostip6,
-                         dns_backend=dns_backend, os_level=dom_for_fun_level)
-
-            domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
-                                         attribute="objectGUID")
-            assert isinstance(domainguid, str)
-
-            create_dns_dir(logger, paths)
-
-            # Only make a zone file on the first DC, it should be
-            # replicated with DNS replication
-            if dns_backend == "BIND9":
-                create_zone_file(lp, logger, paths, targetdir,
-                                 dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
-                                 hostname=names.hostname, realm=names.realm,
-                                 domainguid=domainguid, ntdsguid=names.ntdsguid)
-
-            create_named_conf(paths, realm=names.realm,
-                              dnsdomain=names.dnsdomain, dns_backend=dns_backend)
-
-            create_named_txt(paths.namedtxt,
-                             realm=names.realm, dnsdomain=names.dnsdomain,
-                             dnsname = "%s.%s" % (names.hostname, names.dnsdomain),
-                             private_dir=paths.private_dir,
-                             keytab_name=paths.dns_keytab)
-            logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
-            logger.info("and %s for further documentation required for secure DNS "
-                        "updates", paths.namedtxt)
-
-            lastProvisionUSNs = get_last_provision_usn(samdb)
-            maxUSN = get_max_usn(samdb, str(names.rootdn))
-            if lastProvisionUSNs is not None:
-                update_provision_usn(samdb, 0, maxUSN, invocationid, 1)
-            else:
-                set_provision_usn(samdb, 0, maxUSN, invocationid)
-
-        # fix any dangling GUIDs from the provision
-        logger.info("Fixing provision GUIDs")
-        chk = dbcheck(samdb, samdb_schema=samdb,  verbose=False, fix=True, yes=True, quiet=True)
-        samdb.transaction_start()
-        # a small number of GUIDs are missing because of ordering issues in the
-        # provision code
-        for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
-            chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
-                               scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory'])
-        chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
-                           scope=ldb.SCOPE_ONELEVEL,
-                           attrs=['ipsecOwnersReference',
-                                  'ipsecFilterReference',
-                                  'ipsecISAKMPReference',
-                                  'ipsecNegotiationPolicyReference',
-                                  'ipsecNFAReference'])
-        samdb.transaction_commit()
+        secretsdb_setup_dns(secrets_ldb, names,
+                            paths.private_dir, realm=names.realm,
+                            dnsdomain=names.dnsdomain,
+                            dns_keytab_path=paths.dns_keytab, dnspass=dnspass)
+
+        # Default DNS backend is BIND9 using txt files for zone information
+        if not dns_backend:
+            dns_backend = "BIND9"
+
+        setup_ad_dns(samdb, names, logger, hostip=hostip, hostip6=hostip6,
+                     dns_backend=dns_backend, os_level=dom_for_fun_level)
+
+        domainguid = samdb.searchone(basedn=samdb.get_default_basedn(),
+                                     attribute="objectGUID")
+        assert isinstance(domainguid, str)
+
+        create_dns_dir(logger, paths)
+
+        # Only make a zone file on the first DC, it should be
+        # replicated with DNS replication
+        if dns_backend == "BIND9":
+            create_zone_file(lp, logger, paths, targetdir,
+                             dnsdomain=names.dnsdomain, hostip=hostip, hostip6=hostip6,
+                             hostname=names.hostname, realm=names.realm,
+                             domainguid=domainguid, ntdsguid=names.ntdsguid)
+
+        create_named_conf(paths, realm=names.realm,
+                          dnsdomain=names.dnsdomain, dns_backend=dns_backend)
+
+        create_named_txt(paths.namedtxt,
+                         realm=names.realm, dnsdomain=names.dnsdomain,
+                         dnsname = "%s.%s" % (names.hostname, names.dnsdomain),
+                         private_dir=paths.private_dir,
+                         keytab_name=paths.dns_keytab)
+        logger.info("See %s for an example configuration include file for BIND", paths.namedconf)
+        logger.info("and %s for further documentation required for secure DNS "
+                    "updates", paths.namedtxt)
+
+    lastProvisionUSNs = get_last_provision_usn(samdb)
+    maxUSN = get_max_usn(samdb, str(names.rootdn))
+    if lastProvisionUSNs is not None:
+        update_provision_usn(samdb, 0, maxUSN, invocationid, 1)
+    else:
+        set_provision_usn(samdb, 0, maxUSN, invocationid)
+
+    logger.info("Setting up sam.ldb rootDSE marking as synchronized")
+    setup_modify_ldif(samdb, setup_path("provision_rootdse_modify.ldif"),
+                      { 'NTDSGUID' : names.ntdsguid })
+
+    # fix any dangling GUIDs from the provision
+    logger.info("Fixing provision GUIDs")
+    chk = dbcheck(samdb, samdb_schema=samdb,  verbose=False, fix=True, yes=True, quiet=True)
+    samdb.transaction_start()
+    # a small number of GUIDs are missing because of ordering issues in the
+    # provision code
+    for schema_obj in ['CN=Domain', 'CN=Organizational-Person', 'CN=Contact', 'CN=inetOrgPerson']:
+        chk.check_database(DN="%s,%s" % (schema_obj, names.schemadn),
+                           scope=ldb.SCOPE_BASE, attrs=['defaultObjectCategory'])
+    chk.check_database(DN="CN=IP Security,CN=System,%s" % names.domaindn,
+                       scope=ldb.SCOPE_ONELEVEL,
+                       attrs=['ipsecOwnersReference',
+                              'ipsecFilterReference',
+                              'ipsecISAKMPReference',
+                              'ipsecNegotiationPolicyReference',
+                              'ipsecNFAReference'])
+    samdb.transaction_commit()
 
 
 def provision(logger, session_info, credentials, smbconf=None,