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):
+ promote_existing=False, clone_only=False):
+ ctx.clone_only=clone_only
+
ctx.logger = logger
ctx.creds = creds
ctx.lp = lp
ctx.site = site
- ctx.netbios_name = netbios_name
ctx.targetdir = targetdir
ctx.use_ntvfs = use_ntvfs
raise DCJoinException(estr)
- ctx.myname = netbios_name
- ctx.samname = "%s$" % ctx.myname
ctx.base_dn = str(ctx.samdb.get_default_basedn())
ctx.root_dn = str(ctx.samdb.get_root_basedn())
ctx.schema_dn = str(ctx.samdb.get_schema_basedn())
else:
ctx.acct_pass = samba.generate_random_password(32, 40)
- # work out the DNs of all the objects we will be adding
- ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
- ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
- topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
- if ctx.dn_exists(topology_base):
- ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
+ ctx.dnsdomain = ctx.samdb.domain_dns_name()
+ if clone_only:
+ # As we don't want to create or delete these DNs, we set them to None
+ ctx.server_dn = None
+ ctx.ntds_dn = None
+ ctx.acct_dn = None
+ ctx.myname = ctx.server.split('.')[0]
+ ctx.ntds_guid = None
else:
- ctx.topology_dn = None
+ # work out the DNs of all the objects we will be adding
+ ctx.myname = netbios_name
+ ctx.samname = "%s$" % ctx.myname
+ ctx.server_dn = "CN=%s,CN=Servers,CN=%s,CN=Sites,%s" % (ctx.myname, ctx.site, ctx.config_dn)
+ ctx.ntds_dn = "CN=NTDS Settings,%s" % ctx.server_dn
+ ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
+ ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
+ ctx.dnsforest = ctx.samdb.forest_dns_name()
+
+ topology_base = "CN=Topology,CN=Domain System Volume,CN=DFSR-GlobalSettings,CN=System,%s" % ctx.base_dn
+ if ctx.dn_exists(topology_base):
+ ctx.topology_dn = "CN=%s,%s" % (ctx.myname, topology_base)
+ else:
+ ctx.topology_dn = None
+
+ ctx.SPNs = [ "HOST/%s" % ctx.myname,
+ "HOST/%s" % ctx.dnshostname,
+ "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
- ctx.dnsdomain = ctx.samdb.domain_dns_name()
- ctx.dnsforest = ctx.samdb.forest_dns_name()
ctx.domaindns_zone = 'DC=DomainDnsZones,%s' % ctx.base_dn
ctx.forestdns_zone = 'DC=ForestDnsZones,%s' % ctx.root_dn
else:
ctx.dns_backend = dns_backend
- ctx.dnshostname = "%s.%s" % (ctx.myname.lower(), ctx.dnsdomain)
-
ctx.realm = ctx.dnsdomain
- ctx.acct_dn = "CN=%s,OU=Domain Controllers,%s" % (ctx.myname, ctx.base_dn)
-
ctx.tmp_samdb = None
- ctx.SPNs = [ "HOST/%s" % ctx.myname,
- "HOST/%s" % ctx.dnshostname,
- "GC/%s/%s" % (ctx.dnshostname, ctx.dnsforest) ]
-
# these elements are optional
ctx.never_reveal_sid = None
ctx.reveal_sid = None
if ctx.krbtgt_dn:
ctx.add_krbtgt_account()
- print "Adding %s" % ctx.server_dn
- 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:
- rec["serverReference"] = ctx.acct_dn
+ if ctx.server_dn:
+ print "Adding %s" % ctx.server_dn
+ 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:
+ rec["serverReference"] = ctx.acct_dn
- ctx.samdb.add(rec)
+ ctx.samdb.add(rec)
if ctx.subdomain:
# the rest is done after replication
ctx.ntds_guid = None
return
- ctx.join_add_ntdsdsa()
+ if ctx.ntds_dn:
+ ctx.join_add_ntdsdsa()
if ctx.connection_dn is not None:
print "Adding %s" % ctx.connection_dn
"""Finalise the join, mark us synchronised and setup secrets db."""
# FIXME we shouldn't do this in all cases
+
# If for some reasons we joined in another site than the one of
# DC we just replicated from then we don't need to send the updatereplicateref
# as replication between sites is time based and on the initiative of the
# requesting DC
- ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
- for nc in ctx.nc_list:
- ctx.send_DsReplicaUpdateRefs(nc)
+ if not ctx.clone_only:
+ ctx.logger.info("Sending DsReplicaUpdateRefs for all the replicated partitions")
+ for nc in ctx.nc_list:
+ ctx.send_DsReplicaUpdateRefs(nc)
- if ctx.RODC:
+ if not ctx.clone_only and ctx.RODC:
print "Setting RODC invocationId"
ctx.local_samdb.set_invocation_id(str(ctx.invocation_id))
ctx.local_samdb.set_opaque_integer("domainFunctionality",
m = ldb.Message()
m.dn = ldb.Dn(ctx.local_samdb, '@ROOTDSE')
m["isSynchronized"] = ldb.MessageElement("TRUE", ldb.FLAG_MOD_REPLACE, "isSynchronized")
- m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(ctx.ntds_guid),
+
+ # We want to appear to be the server we just cloned
+ if ctx.clone_only:
+ guid = ctx.samdb.get_ntds_GUID()
+ else:
+ guid = ctx.ntds_guid
+
+ m["dsServiceName"] = ldb.MessageElement("<GUID=%s>" % str(guid),
ldb.FLAG_MOD_REPLACE, "dsServiceName")
ctx.local_samdb.modify(m)
- if ctx.subdomain:
+ if ctx.clone_only or ctx.subdomain:
return
secrets_ldb = Ldb(ctx.paths.secrets, session_info=system_session(), lp=ctx.lp)
ctx.full_nc_list += [ctx.domaindns_zone]
ctx.full_nc_list += [ctx.forestdns_zone]
- if ctx.promote_existing:
- ctx.promote_possible()
- else:
- ctx.cleanup_old_join()
+ if not ctx.clone_only:
+ if ctx.promote_existing:
+ ctx.promote_possible()
+ else:
+ ctx.cleanup_old_join()
try:
- ctx.join_add_objects()
+ if not ctx.clone_only:
+ ctx.join_add_objects()
ctx.join_provision()
ctx.join_replicate()
- if ctx.subdomain:
+ if (not ctx.clone_only and ctx.subdomain):
ctx.join_add_objects2()
ctx.join_provision_own_domain()
ctx.join_setup_trusts()
ctx.join_finalise()
except:
print "Join failed - cleaning up"
- ctx.cleanup_old_join()
+ if not ctx.clone_only:
+ ctx.cleanup_old_join()
raise
ctx.do_join()
logger.info("Joined domain %s (SID %s) as a DC" % (ctx.domain_name, ctx.domsid))
+def join_clone(logger=None, server=None, creds=None, lp=None,
+ targetdir=None, domain=None):
+ """Join as a DC."""
+ ctx = dc_join(logger, server, creds, lp, site=None, netbios_name=None, targetdir=targetdir, domain=domain,
+ machinepass=None, use_ntvfs=False, dns_backend="NONE", promote_existing=False, clone_only=True)
+
+ lp.set("workgroup", ctx.domain_name)
+ logger.info("workgroup is %s" % ctx.domain_name)
+
+ lp.set("realm", ctx.realm)
+ logger.info("realm is %s" % ctx.realm)
+
+ ctx.replica_flags = (drsuapi.DRSUAPI_DRS_WRIT_REP |
+ drsuapi.DRSUAPI_DRS_INIT_SYNC |
+ drsuapi.DRSUAPI_DRS_PER_SYNC |
+ drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS |
+ drsuapi.DRSUAPI_DRS_NEVER_SYNCED)
+ ctx.domain_replica_flags = ctx.replica_flags
+
+ ctx.do_join()
+ logger.info("Cloned domain %s (SID %s)" % (ctx.domain_name, ctx.domsid))
+
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,
import samba.getopt as options
import ldb
+import logging
from samba.auth import system_session
from samba.netcmd import (
from samba import drs_utils, nttime2string, dsdb
from samba.dcerpc import drsuapi, misc
import common
+from samba.join import join_clone
def drsuapi_connect(ctx):
'''make a DRSUAPI connection to the server'''
self.message("New DSA options: " + ", ".join(cur_opts))
+class cmd_drs_clone_dc_database(Command):
+ """Replicate an initial clone of domain, but DO NOT JOIN it."""
+
+ synopsis = "%prog <dnsdomain> [options]"
+
+ takes_optiongroups = {
+ "sambaopts": options.SambaOptions,
+ "versionopts": options.VersionOptions,
+ "credopts": options.CredentialsOptions,
+ }
+
+ takes_options = [
+ Option("--server", help="DC to join", type=str),
+ Option("--targetdir", help="where to store provision", type=str),
+ Option("--quiet", help="Be quiet", action="store_true"),
+ Option("--verbose", help="Be verbose", action="store_true")
+ ]
+
+ takes_args = ["domain"]
+
+ def run(self, domain, sambaopts=None, credopts=None,
+ versionopts=None, server=None, targetdir=None,
+ quiet=False, verbose=False):
+ lp = sambaopts.get_loadparm()
+ creds = credopts.get_credentials(lp)
+
+ logger = self.get_logger()
+ if verbose:
+ logger.setLevel(logging.DEBUG)
+ elif quiet:
+ logger.setLevel(logging.WARNING)
+ else:
+ logger.setLevel(logging.INFO)
+
+ join_clone(logger=logger, server=server, creds=creds, lp=lp, domain=domain,
+ targetdir=targetdir)
+
+
class cmd_drs(SuperCommand):
"""Directory Replication Services (DRS) management."""
subcommands["replicate"] = cmd_drs_replicate()
subcommands["showrepl"] = cmd_drs_showrepl()
subcommands["options"] = cmd_drs_options()
+ subcommands["clone-dc-database"] = cmd_drs_clone_dc_database()
"""Blackbox tests for samba-tool drs."""
import samba.tests
-
+import shutil
+import os
class SambaToolDrsTests(samba.tests.BlackboxTestCase):
"""Blackbox test case for samba-tool drs."""
self.cmdline_creds = "-U%s/%s%%%s" % (creds.get_domain(),
creds.get_username(), creds.get_password())
- def _get_rootDSE(self, dc):
+ def _get_rootDSE(self, dc, ldap_only=True):
samdb = samba.tests.connect_samdb(dc, lp=self.get_loadparm(),
credentials=self.get_credentials(),
- ldap_only=True)
+ ldap_only=ldap_only)
return samdb.search(base="", scope=samba.tests.ldb.SCOPE_BASE)[0]
def test_samba_tool_bind(self):
self.cmdline_creds))
self.assertTrue("Replicate from" in out)
self.assertTrue("was successful" in out)
+
+ def test_samba_tool_drs_clone_dc(self):
+ """Tests 'samba-tool drs clone-dc-database' command."""
+ server_rootdse = self._get_rootDSE(self.dc1)
+ server_nc_name = server_rootdse["defaultNamingContext"]
+ server_ds_name = server_rootdse["dsServiceName"]
+ server_ldap_service_name = str(server_rootdse["ldapServiceName"][0])
+ server_realm = server_ldap_service_name.split(":")[0]
+ creds = self.get_credentials()
+ out = self.check_output("samba-tool drs clone-dc-database %s --server=%s %s --targetdir=%s"
+ % (server_realm,
+ self.dc1,
+ self.cmdline_creds,
+ self.tempdir))
+ ldb_rootdse = self._get_rootDSE("tdb://" + os.path.join(self.tempdir, "private", "sam.ldb"), ldap_only=False)
+ nc_name = ldb_rootdse["defaultNamingContext"]
+ ds_name = ldb_rootdse["dsServiceName"]
+ ldap_service_name = str(server_rootdse["ldapServiceName"][0])
+ self.assertEqual(nc_name, server_nc_name)
+ # The clone should pretend to be the source server
+ self.assertEqual(ds_name, server_ds_name)
+ self.assertEqual(ldap_service_name, server_ldap_service_name)
+ shutil.rmtree(os.path.join(self.tempdir, "private"))
+ shutil.rmtree(os.path.join(self.tempdir, "etc"))
+ shutil.rmtree(os.path.join(self.tempdir, "msg.lock"))
+ os.remove(os.path.join(self.tempdir, "names.tdb"))
+ shutil.rmtree(os.path.join(self.tempdir, "state"))