From d1f2201943994873ed28e1b2fb54c9a5aa7f7ee2 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 17 Aug 2015 15:33:31 +1200 Subject: [PATCH 1/5] samba-tool: Add new command 'samba-tool drs clone-dc-database' This command makes a clone of an existing AD Domain, but does not join the domain. This allows us to test if the join would work without adding objects to the target DC. The server password will need to be reset for the clone to be any use, see the source4/scripting/devel/chgtdcpass (Based on patches written with Garming Sam) Andrew Bartlett Signed-off-by: Andrew Bartlett Signed-off-by: Garming Sam --- python/samba/join.py | 142 +++++++++++++++++--------- python/samba/netcmd/drs.py | 41 ++++++++ python/samba/tests/__init__.py | 2 +- python/samba/tests/blackbox/samba_tool_drs.py | 33 +++++- 4 files changed, 165 insertions(+), 53 deletions(-) diff --git a/python/samba/join.py b/python/samba/join.py index c356145..74c14c2 100644 --- a/python/samba/join.py +++ b/python/samba/join.py @@ -54,12 +54,13 @@ 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): + 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 @@ -89,8 +90,6 @@ def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None, 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()) @@ -110,17 +109,34 @@ def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None, 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 @@ -137,18 +153,10 @@ def __init__(ctx, logger=None, server=None, creds=None, lp=None, site=None, 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 @@ -538,28 +546,30 @@ def join_add_objects(ctx): 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 @@ -876,15 +886,17 @@ def join_finalise(ctx): """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", @@ -914,11 +926,18 @@ def join_finalise(ctx): 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("" % 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("" % 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) @@ -1077,23 +1096,26 @@ def do_join(ctx): 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 @@ -1183,6 +1205,28 @@ def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_na 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, diff --git a/python/samba/netcmd/drs.py b/python/samba/netcmd/drs.py index e8e9ec8..f1d4970 100644 --- a/python/samba/netcmd/drs.py +++ b/python/samba/netcmd/drs.py @@ -20,6 +20,7 @@ import samba.getopt as options import ldb +import logging from samba.auth import system_session from samba.netcmd import ( @@ -32,6 +33,7 @@ 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''' @@ -513,6 +515,44 @@ def run(self, DC=None, dsa_option=None, 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 [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.""" @@ -522,3 +562,4 @@ class cmd_drs(SuperCommand): subcommands["replicate"] = cmd_drs_replicate() subcommands["showrepl"] = cmd_drs_showrepl() subcommands["options"] = cmd_drs_options() + subcommands["clone-dc-database"] = cmd_drs_clone_dc_database() diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py index b53c4ea..87b6943 100644 --- a/python/samba/tests/__init__.py +++ b/python/samba/tests/__init__.py @@ -253,7 +253,7 @@ def __str__(self): return "Command '%s'; exit status %d; stdout: '%s'; stderr: '%s'" % (self.cmd, self.returncode, self.stdout, self.stderr) -class BlackboxTestCase(TestCase): +class BlackboxTestCase(TestCaseInTempDir): """Base test case for blackbox tests.""" def _make_cmdline(self, line): diff --git a/python/samba/tests/blackbox/samba_tool_drs.py b/python/samba/tests/blackbox/samba_tool_drs.py index 9b7106f..a7bcb4f 100644 --- a/python/samba/tests/blackbox/samba_tool_drs.py +++ b/python/samba/tests/blackbox/samba_tool_drs.py @@ -18,7 +18,8 @@ """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.""" @@ -33,10 +34,10 @@ def setUp(self): 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): @@ -100,3 +101,29 @@ def test_samba_tool_replicate(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")) + shutil.rmtree(os.path.join(self.tempdir, "state")) From 60d3ab88406e61150509a255c69ac227c0f50618 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Wed, 19 Aug 2015 13:26:41 +1200 Subject: [PATCH 2/5] repl: Give an error if we get a secret when not expecting one We should never get a secret from a server when we specify DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING This asserts that this is the case. Signed-off-by: Andrew Bartlett --- libcli/drsuapi/drsuapi.h | 1 + libcli/drsuapi/repl_decrypt.c | 6 ++++++ source3/libnet/libnet_dssync.c | 1 + source4/dsdb/repl/drepl_out_helpers.c | 3 +++ source4/dsdb/repl/replicated_objects.c | 23 ++++++++++++++++++++--- source4/dsdb/samdb/samdb.h | 1 + source4/libnet/libnet_vampire.c | 7 ++++++- 7 files changed, 38 insertions(+), 4 deletions(-) diff --git a/libcli/drsuapi/drsuapi.h b/libcli/drsuapi/drsuapi.h index a4fb15f..7c6cf2f 100644 --- a/libcli/drsuapi/drsuapi.h +++ b/libcli/drsuapi/drsuapi.h @@ -29,6 +29,7 @@ WERROR drsuapi_decrypt_attribute_value(TALLOC_CTX *mem_ctx, WERROR drsuapi_decrypt_attribute(TALLOC_CTX *mem_ctx, const DATA_BLOB *gensec_skey, uint32_t rid, + uint32_t dsdb_repl_flags, struct drsuapi_DsReplicaAttribute *attr); diff --git a/libcli/drsuapi/repl_decrypt.c b/libcli/drsuapi/repl_decrypt.c index 00b8db8..4a2a28f 100644 --- a/libcli/drsuapi/repl_decrypt.c +++ b/libcli/drsuapi/repl_decrypt.c @@ -28,6 +28,7 @@ #include "../lib/crypto/crypto.h" #include "../libcli/drsuapi/drsuapi.h" #include "libcli/auth/libcli_auth.h" +#include "dsdb/samdb/samdb.h" WERROR drsuapi_decrypt_attribute_value(TALLOC_CTX *mem_ctx, const DATA_BLOB *gensec_skey, @@ -134,6 +135,7 @@ WERROR drsuapi_decrypt_attribute_value(TALLOC_CTX *mem_ctx, WERROR drsuapi_decrypt_attribute(TALLOC_CTX *mem_ctx, const DATA_BLOB *gensec_skey, uint32_t rid, + uint32_t dsdb_repl_flags, struct drsuapi_DsReplicaAttribute *attr) { WERROR status; @@ -164,6 +166,10 @@ WERROR drsuapi_decrypt_attribute(TALLOC_CTX *mem_ctx, return WERR_OK; } + if (dsdb_repl_flags & DSDB_REPL_FLAG_EXPECT_NO_SECRETS) { + return WERR_TOO_MANY_SECRETS; + } + if (attr->value_ctr.num_values > 1) { return WERR_DS_DRA_INVALID_PARAMETER; } diff --git a/source3/libnet/libnet_dssync.c b/source3/libnet/libnet_dssync.c index 94f0628..267709e 100644 --- a/source3/libnet/libnet_dssync.c +++ b/source3/libnet/libnet_dssync.c @@ -113,6 +113,7 @@ static void libnet_dssync_decrypt_attributes(TALLOC_CTX *mem_ctx, drsuapi_decrypt_attribute(mem_ctx, session_key, rid, + 0, attr); } } diff --git a/source4/dsdb/repl/drepl_out_helpers.c b/source4/dsdb/repl/drepl_out_helpers.c index a047881..a1e8dcb 100644 --- a/source4/dsdb/repl/drepl_out_helpers.c +++ b/source4/dsdb/repl/drepl_out_helpers.c @@ -740,6 +740,9 @@ static void dreplsrv_op_pull_source_apply_changes_trigger(struct tevent_req *req if (state->op->options & DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS) { dsdb_repl_flags |= DSDB_REPL_FLAG_PRIORITISE_INCOMING; } + if (state->op->options & DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING) { + dsdb_repl_flags |= DSDB_REPL_FLAG_EXPECT_NO_SECRETS; + } status = dsdb_replicated_objects_convert(service->samdb, working_schema ? working_schema : schema, diff --git a/source4/dsdb/repl/replicated_objects.c b/source4/dsdb/repl/replicated_objects.c index df880ad..1afdb36 100644 --- a/source4/dsdb/repl/replicated_objects.c +++ b/source4/dsdb/repl/replicated_objects.c @@ -347,7 +347,7 @@ WERROR dsdb_convert_object_ex(struct ldb_context *ldb, struct dsdb_extended_replicated_object *out) { NTSTATUS nt_status; - WERROR status; + WERROR status = WERR_OK; uint32_t i; struct ldb_message *msg; struct replPropertyMetaDataBlob *md; @@ -444,8 +444,25 @@ WERROR dsdb_convert_object_ex(struct ldb_context *ldb, } for (j=0; jvalue_ctr.num_values; j++) { - status = drsuapi_decrypt_attribute(a->value_ctr.values[j].blob, gensec_skey, rid, a); - W_ERROR_NOT_OK_RETURN(status); + status = drsuapi_decrypt_attribute(a->value_ctr.values[j].blob, + gensec_skey, rid, + dsdb_repl_flags, a); + if (!W_ERROR_IS_OK(status)) { + break; + } + } + if (W_ERROR_EQUAL(status, WERR_TOO_MANY_SECRETS)) { + WERROR get_name_status = dsdb_attribute_drsuapi_to_ldb(ldb, schema, pfm_remote, + a, msg->elements, e); + if (W_ERROR_IS_OK(get_name_status)) { + DEBUG(0, ("Unxpectedly got secret value %s on %s from DRS server\n", + e->name, ldb_dn_get_linearized(msg->dn))); + } else { + DEBUG(0, ("Unxpectedly got secret value on %s from DRS server", + ldb_dn_get_linearized(msg->dn))); + } + } else if (!W_ERROR_IS_OK(status)) { + return status; } status = dsdb_attribute_drsuapi_to_ldb(ldb, schema, pfm_remote, diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h index 324045a..0a1d90d 100644 --- a/source4/dsdb/samdb/samdb.h +++ b/source4/dsdb/samdb/samdb.h @@ -62,6 +62,7 @@ struct dsdb_control_current_partition { #define DSDB_REPL_FLAG_PRIORITISE_INCOMING 1 #define DSDB_REPL_FLAG_PARTIAL_REPLICA 2 #define DSDB_REPL_FLAG_ADD_NCNAME 4 +#define DSDB_REPL_FLAG_EXPECT_NO_SECRETS 8 #define DSDB_CONTROL_REPLICATED_UPDATE_OID "1.3.6.1.4.1.7165.4.3.3" diff --git a/source4/libnet/libnet_vampire.c b/source4/libnet/libnet_vampire.c index 69195af..01f2487 100644 --- a/source4/libnet/libnet_vampire.c +++ b/source4/libnet/libnet_vampire.c @@ -553,6 +553,7 @@ NTSTATUS libnet_vampire_cb_store_chunk(void *private_data, const struct drsuapi_DsReplicaCursor2CtrEx *uptodateness_vector; struct dsdb_extended_replicated_objects *objs; uint32_t req_replica_flags; + uint32_t dsdb_repl_flags = 0; struct repsFromTo1 *s_dsa; char *tmp_dns_name; uint32_t i; @@ -679,6 +680,10 @@ NTSTATUS libnet_vampire_cb_store_chunk(void *private_data, return NT_STATUS_INTERNAL_ERROR; } + if (req_replica_flags & DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING) { + dsdb_repl_flags |= DSDB_REPL_FLAG_EXPECT_NO_SECRETS; + } + status = dsdb_replicated_objects_convert(s->ldb, schema, c->partition->nc.dn, @@ -690,7 +695,7 @@ NTSTATUS libnet_vampire_cb_store_chunk(void *private_data, s_dsa, uptodateness_vector, c->gensec_skey, - 0, + dsdb_repl_flags, s, &objs); if (!W_ERROR_IS_OK(status)) { DEBUG(0,("Failed to convert objects: %s\n", win_errstr(status))); From ea471dbe0a65964ce960808f9fb429a2a2e72028 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Wed, 19 Aug 2015 13:29:35 +1200 Subject: [PATCH 3/5] samba-tool drs clone-dc: Add --include-secrets option This allows the creation of domain clones that have no secrets, and so make it safer to examine databases that demonstrate issues Signed-off-by: Andrew Bartlett --- python/samba/join.py | 4 ++- python/samba/netcmd/drs.py | 5 ++-- python/samba/tests/blackbox/samba_tool_drs.py | 40 ++++++++++++++++++++++++++- 3 files changed, 45 insertions(+), 4 deletions(-) diff --git a/python/samba/join.py b/python/samba/join.py index 74c14c2..45e3e40 100644 --- a/python/samba/join.py +++ b/python/samba/join.py @@ -1206,7 +1206,7 @@ def join_DC(logger=None, server=None, creds=None, lp=None, site=None, netbios_na 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): + targetdir=None, domain=None, include_secrets=False): """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) @@ -1222,6 +1222,8 @@ def join_clone(logger=None, server=None, creds=None, lp=None, drsuapi.DRSUAPI_DRS_PER_SYNC | drsuapi.DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS | drsuapi.DRSUAPI_DRS_NEVER_SYNCED) + if not include_secrets: + ctx.replica_flags |= drsuapi.DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING ctx.domain_replica_flags = ctx.replica_flags ctx.do_join() diff --git a/python/samba/netcmd/drs.py b/python/samba/netcmd/drs.py index f1d4970..c624357 100644 --- a/python/samba/netcmd/drs.py +++ b/python/samba/netcmd/drs.py @@ -530,6 +530,7 @@ class cmd_drs_clone_dc_database(Command): 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("--include-secrets", help="Also replicate secret values", action="store_true"), Option("--verbose", help="Be verbose", action="store_true") ] @@ -537,7 +538,7 @@ class cmd_drs_clone_dc_database(Command): def run(self, domain, sambaopts=None, credopts=None, versionopts=None, server=None, targetdir=None, - quiet=False, verbose=False): + quiet=False, verbose=False, include_secrets=False): lp = sambaopts.get_loadparm() creds = credopts.get_credentials(lp) @@ -550,7 +551,7 @@ def run(self, domain, sambaopts=None, credopts=None, logger.setLevel(logging.INFO) join_clone(logger=logger, server=server, creds=creds, lp=lp, domain=domain, - targetdir=targetdir) + targetdir=targetdir, include_secrets=include_secrets) class cmd_drs(SuperCommand): diff --git a/python/samba/tests/blackbox/samba_tool_drs.py b/python/samba/tests/blackbox/samba_tool_drs.py index a7bcb4f..05fddac 100644 --- a/python/samba/tests/blackbox/samba_tool_drs.py +++ b/python/samba/tests/blackbox/samba_tool_drs.py @@ -123,7 +123,45 @@ def test_samba_tool_drs_clone_dc(self): # 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) + + samdb = samba.tests.connect_samdb("tdb://" + os.path.join(self.tempdir, "private", "sam.ldb"), + ldap_only=False, lp=self.get_loadparm()) + def get_krbtgt_pw(): + krbtgt_pw = samdb.searchone("unicodePwd", "cn=krbtgt,CN=users,%s" % nc_name) + self.assertRaises(KeyError, get_krbtgt_pw) + 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")) + shutil.rmtree(os.path.join(self.tempdir, "state")) + + def test_samba_tool_drs_clone_dc_secrets(self): + """Tests 'samba-tool drs clone-dc-database --include-secrets' 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 --include-secrets" + % (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]) + + samdb = samba.tests.connect_samdb("tdb://" + os.path.join(self.tempdir, "private", "sam.ldb"), + ldap_only=False, lp=self.get_loadparm()) + krbtgt_pw = samdb.searchone("unicodePwd", "cn=krbtgt,CN=users,%s" % nc_name) + self.assertIsNotNone(krbtgt_pw) + + 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")) + shutil.rmtree(os.path.join(self.tempdir, "msg.lock")) shutil.rmtree(os.path.join(self.tempdir, "state")) From 651f1a234243d842d11c11b6b7fb7f68566fcad9 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Wed, 19 Aug 2015 13:30:55 +1200 Subject: [PATCH 4/5] repl: Use DSDB_REPL_FLAG_PRIORITISE_INCOMING in samba-tool drs repliate --local Previously this would only be set when we did server-to-server replication Signed-off-by: Andrew Bartlett --- source4/libnet/libnet_vampire.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/source4/libnet/libnet_vampire.c b/source4/libnet/libnet_vampire.c index 01f2487..d08dc9c 100644 --- a/source4/libnet/libnet_vampire.c +++ b/source4/libnet/libnet_vampire.c @@ -680,6 +680,10 @@ NTSTATUS libnet_vampire_cb_store_chunk(void *private_data, return NT_STATUS_INTERNAL_ERROR; } + if (req_replica_flags & DRSUAPI_DRS_FULL_SYNC_IN_PROGRESS) { + dsdb_repl_flags |= DSDB_REPL_FLAG_PRIORITISE_INCOMING; + } + if (req_replica_flags & DRSUAPI_DRS_SPECIAL_SECRET_PROCESSING) { dsdb_repl_flags |= DSDB_REPL_FLAG_EXPECT_NO_SECRETS; } From 72c4de805cc9115e2755f7381d9072c8c7178bc3 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 12 Oct 2015 17:50:27 +1300 Subject: [PATCH 5/5] samba-tool drs clone-dc-database: Require --targetdir Signed-off-by: Andrew Bartlett --- python/samba/netcmd/drs.py | 6 +++++- python/samba/tests/blackbox/samba_tool_drs.py | 13 +++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/python/samba/netcmd/drs.py b/python/samba/netcmd/drs.py index c624357..230dd33 100644 --- a/python/samba/netcmd/drs.py +++ b/python/samba/netcmd/drs.py @@ -528,7 +528,7 @@ class cmd_drs_clone_dc_database(Command): takes_options = [ Option("--server", help="DC to join", type=str), - Option("--targetdir", help="where to store provision", type=str), + Option("--targetdir", help="where to store provision (required)", type=str), Option("--quiet", help="Be quiet", action="store_true"), Option("--include-secrets", help="Also replicate secret values", action="store_true"), Option("--verbose", help="Be verbose", action="store_true") @@ -550,6 +550,10 @@ def run(self, domain, sambaopts=None, credopts=None, else: logger.setLevel(logging.INFO) + if targetdir is None: + raise CommandError("--targetdir option must be specified") + + join_clone(logger=logger, server=server, creds=creds, lp=lp, domain=domain, targetdir=targetdir, include_secrets=include_secrets) diff --git a/python/samba/tests/blackbox/samba_tool_drs.py b/python/samba/tests/blackbox/samba_tool_drs.py index 05fddac..1cc2b50 100644 --- a/python/samba/tests/blackbox/samba_tool_drs.py +++ b/python/samba/tests/blackbox/samba_tool_drs.py @@ -165,3 +165,16 @@ def test_samba_tool_drs_clone_dc_secrets(self): shutil.rmtree(os.path.join(self.tempdir, "etc")) shutil.rmtree(os.path.join(self.tempdir, "msg.lock")) shutil.rmtree(os.path.join(self.tempdir, "state")) + + def test_samba_tool_drs_clone_dc_secrets_without_targetdir(self): + """Tests 'samba-tool drs clone-dc-database' command without --targetdir.""" + server_rootdse = self._get_rootDSE(self.dc1) + server_ldap_service_name = str(server_rootdse["ldapServiceName"][0]) + server_realm = server_ldap_service_name.split(":")[0] + creds = self.get_credentials() + def attempt_clone(): + out = self.check_output("samba-tool drs clone-dc-database %s --server=%s %s" + % (server_realm, + self.dc1, + self.cmdline_creds)) + self.assertRaises(samba.tests.BlackboxProcessError, attempt_clone)