From 8fbcc69b6b98edec2f36f7d3fe6d6355155f008e Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 4 Jun 2013 19:57:06 +1000 Subject: [PATCH 1/4] dsdb: Improve DRS deleted link source/target handing in repl_meta_data We now correctly ignore the link updates if the source or target is deleted locally. This fixes the long-standing failure in the vampire_dc dbcheck test. Pair-Programmed-With: Stefan Metzmacher Andrew Bartlett Signed-off-by: Andrew Bartlett Signed-off-by: Stefan Metzmacher (cherry picked from commit 0162be32ab4f9716a4300d1f1a0caae8b0133f7c) --- selftest/knownfail | 1 - source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 105 +++++++++++++++++++++-- 2 files changed, 97 insertions(+), 9 deletions(-) diff --git a/selftest/knownfail b/selftest/knownfail index 313d6c9..3943e60 100644 --- a/selftest/knownfail +++ b/selftest/knownfail @@ -175,7 +175,6 @@ ^samba4.ntvfs.cifs.krb5.base.createx_access.createx_access\(.*\)$ ^samba4.rpc.lsa.forest.trust #Not fully provided by Samba4 ^samba4.blackbox.kinit\(.*\).kinit with user password for expired password\(.*\) # We need to work out why this fails only during the pw change -^samba4.blackbox.dbcheck\(vampire_dc\).dbcheck\(vampire_dc:local\) # Due to replicating with --domain-critical-only we fail dbcheck on this database ^samba4.blackbox.upgradeprovision.alpha13.ldapcmp_sd\(none\) # Due to something rewriting the NT ACL on DNS objects ^samba4.blackbox.upgradeprovision.alpha13.ldapcmp_full_sd\(none\) # Due to something rewriting the NT ACL on DNS objects ^samba4.blackbox.upgradeprovision.release-4-0-0.ldapcmp_sd\(none\) # Due to something rewriting the NT ACL on DNS objects diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index 591f071..0bfdd42 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -5182,6 +5182,7 @@ static int replmd_process_linked_attribute(struct ldb_module *module, struct drsuapi_DsReplicaLinkedAttribute *la = la_entry->la; struct ldb_context *ldb = ldb_module_get_ctx(module); struct ldb_message *msg; + struct ldb_message *target_msg = NULL; TALLOC_CTX *tmp_ctx = talloc_new(la_entry); const struct dsdb_schema *schema = dsdb_get_schema(ldb, tmp_ctx); int ret; @@ -5192,13 +5193,18 @@ static int replmd_process_linked_attribute(struct ldb_module *module, WERROR status; time_t t = time(NULL); struct ldb_result *res; - const char *attrs[2]; + struct ldb_result *target_res; + const char *attrs[4]; + const char *attrs2[] = { "isDeleted", "isRecycled", NULL }; struct parsed_dn *pdn_list, *pdn; struct GUID guid = GUID_zero(); NTSTATUS ntstatus; bool active = (la->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE)?true:false; const struct GUID *our_invocation_id; + enum deletion_state deletion_state = OBJECT_NOT_DELETED; + enum deletion_state target_deletion_state = OBJECT_NOT_DELETED; + /* linked_attributes[0]: &objs->linked_attributes[i]: struct drsuapi_DsReplicaLinkedAttribute @@ -5243,7 +5249,9 @@ linked_attributes[0]: } attrs[0] = attr->lDAPDisplayName; - attrs[1] = NULL; + attrs[1] = "isDeleted"; + attrs[1] = "isRecycled"; + attrs[2] = NULL; /* get the existing message from the db for the object with this GUID, returning attribute being modified. We will then @@ -5268,7 +5276,23 @@ linked_attributes[0]: } msg = res->msgs[0]; - if (msg->num_elements == 0) { + /* + * Check for deleted objects per MS-DRSR 4.1.10.6.13 + * ProcessLinkValue, because link updates are not applied to + * recycled and tombstone objects. We don't have to delete + * any existing link, that should have happened when the + * object deletion was replicated or initiated. + */ + + replmd_deletion_state(module, msg, &deletion_state, NULL); + + if (deletion_state >= OBJECT_RECYCLED) { + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } + + old_el = ldb_msg_find_element(msg, attr->lDAPDisplayName); + if (old_el == NULL) { ret = ldb_msg_add_empty(msg, attr->lDAPDisplayName, LDB_FLAG_MOD_REPLACE, &old_el); if (ret != LDB_SUCCESS) { ldb_module_oom(module); @@ -5276,7 +5300,6 @@ linked_attributes[0]: return LDB_ERR_OPERATIONS_ERROR; } } else { - old_el = &msg->elements[0]; old_el->flags = LDB_FLAG_MOD_REPLACE; } @@ -5305,25 +5328,91 @@ linked_attributes[0]: if (!W_ERROR_IS_OK(status)) { ldb_asprintf_errstring(ldb, "Failed to parsed linked attribute blob for %s on %s - %s\n", old_el->name, ldb_dn_get_linearized(msg->dn), win_errstr(status)); + talloc_free(tmp_ctx); return LDB_ERR_OPERATIONS_ERROR; } ntstatus = dsdb_get_extended_dn_guid(dsdb_dn->dn, &guid, "GUID"); - if (!NT_STATUS_IS_OK(ntstatus) && active) { + if (!NT_STATUS_IS_OK(ntstatus) && !active) { + /* + * This strange behaviour (allowing a NULL/missing + * GUID) originally comes from: + * + * commit e3054ce0fe0f8f62d2f5b2a77893e7a1479128bd + * Author: Andrew Tridgell + * Date: Mon Dec 21 21:21:55 2009 +1100 + * + * s4-drs: cope better with NULL GUIDS from DRS + * + * It is valid to get a NULL GUID over DRS for a deleted forward link. We + * need to match by DN if possible when seeing if we should update an + * existing link. + * + * Pair-Programmed-With: Andrew Bartlett + */ + + ret = dsdb_module_search_dn(module, tmp_ctx, &target_res, + dsdb_dn->dn, attrs2, + DSDB_FLAG_NEXT_MODULE | + DSDB_SEARCH_SHOW_RECYCLED | + DSDB_SEARCH_SEARCH_ALL_PARTITIONS | + DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT, + parent); + } else if (!NT_STATUS_IS_OK(ntstatus)) { ldb_asprintf_errstring(ldb, "Failed to find GUID in linked attribute blob for %s on %s from %s", old_el->name, ldb_dn_get_linearized(dsdb_dn->dn), ldb_dn_get_linearized(msg->dn)); + talloc_free(tmp_ctx); return LDB_ERR_OPERATIONS_ERROR; + } else { + ret = dsdb_module_search(module, tmp_ctx, &target_res, + NULL, LDB_SCOPE_SUBTREE, + attrs2, + DSDB_FLAG_NEXT_MODULE | + DSDB_SEARCH_SHOW_RECYCLED | + DSDB_SEARCH_SEARCH_ALL_PARTITIONS | + DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT, + parent, + "objectGUID=%s", + GUID_string(tmp_ctx, &guid)); } - /* re-resolve the DN by GUID, as the DRS server may give us an - old DN value */ - ret = dsdb_module_dn_by_guid(module, dsdb_dn, &guid, &dsdb_dn->dn, parent); if (ret != LDB_SUCCESS) { + ldb_asprintf_errstring(ldb_module_get_ctx(module), "Failed to re-resolve GUID %s: %s\n", + GUID_string(tmp_ctx, &guid), + ldb_errstring(ldb_module_get_ctx(module))); + talloc_free(tmp_ctx); + return ret; + } + + if (target_res->count == 0) { DEBUG(2,(__location__ ": WARNING: Failed to re-resolve GUID %s - using %s\n", GUID_string(tmp_ctx, &guid), ldb_dn_get_linearized(dsdb_dn->dn))); + } else if (target_res->count != 1) { + ldb_asprintf_errstring(ldb_module_get_ctx(module), "More than one object found matching objectGUID %s\n", + GUID_string(tmp_ctx, &guid)); + talloc_free(tmp_ctx); + return LDB_ERR_OPERATIONS_ERROR; + } else { + target_msg = target_res->msgs[0]; + dsdb_dn->dn = talloc_steal(dsdb_dn, target_msg->dn); + } + + /* + * Check for deleted objects per MS-DRSR 4.1.10.6.13 + * ProcessLinkValue, because link updates are not applied to + * recycled and tombstone objects. We don't have to delete + * any existing link, that should have happened when the + * object deletion was replicated or initiated. + */ + replmd_deletion_state(module, target_msg, + &target_deletion_state, NULL); + + if (target_deletion_state >= OBJECT_RECYCLED) { + talloc_free(tmp_ctx); + return LDB_SUCCESS; } /* see if this link already exists */ -- 1.7.10.4 From 2525d3679442e1e2778f1986bb32b21717268422 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Mon, 3 Jun 2013 17:51:41 +1000 Subject: [PATCH 2/4] dsdb tests: Add member/memberOf checking to delete_objects testing Pair-Programmed-With: Stefan Metzmacher Signed-off-by: Andrew Bartlett Signed-off-by: Stefan Metzmacher (cherry picked from commit a9e565a5a4478f7b923f35311e170de2044ff848) --- source4/torture/drs/python/delete_object.py | 278 +++++++++++++++++++++++++-- 1 file changed, 257 insertions(+), 21 deletions(-) diff --git a/source4/torture/drs/python/delete_object.py b/source4/torture/drs/python/delete_object.py index f36232e..3e20b6d 100644 --- a/source4/torture/drs/python/delete_object.py +++ b/source4/torture/drs/python/delete_object.py @@ -30,10 +30,10 @@ import time from ldb import ( - SCOPE_SUBTREE + SCOPE_SUBTREE, ) -import drs_base +import drs_base, ldb class DrsDeleteObjectTestCase(drs_base.DrsBaseTestCase): @@ -55,9 +55,10 @@ class DrsDeleteObjectTestCase(drs_base.DrsBaseTestCase): def _make_username(self): return "DrsDelObjUser_" + time.strftime("%s", time.gmtime()) - def _check_user(self, sam_ldb, user_orig, is_deleted): + # now also used to check the group + def _check_obj(self, sam_ldb, obj_orig, is_deleted): # search the user by guid as it may be deleted - guid_str = self._GUID_string(user_orig["objectGUID"][0]) + guid_str = self._GUID_string(obj_orig["objectGUID"][0]) expression = "(objectGUID=%s)" % guid_str res = sam_ldb.search(base=self.domain_dn, expression=expression, @@ -67,29 +68,38 @@ class DrsDeleteObjectTestCase(drs_base.DrsBaseTestCase): # Deleted Object base DN dodn = self._deleted_objects_dn(sam_ldb) # now check properties of the user - name_orig = user_orig["cn"][0] + name_orig = obj_orig["cn"][0] name_cur = user_cur["cn"][0] if is_deleted: self.assertEquals(user_cur["isDeleted"][0],"TRUE") - self.assertTrue(not("objectCategory" in user_cur)) - self.assertTrue(not("sAMAccountType" in user_cur)) + self.assertFalse("objectCategory" in user_cur) + self.assertFalse("sAMAccountType" in user_cur) + self.assertFalse("description" in user_cur) + self.assertFalse("memberOf" in user_cur) + self.assertFalse("member" in user_cur) self.assertTrue(dodn in str(user_cur["dn"]), "User %s is deleted but it is not located under %s (found at %s)!" % (name_orig, dodn, user_cur["dn"])) self.assertEquals(name_cur, name_orig + "\nDEL:" + guid_str) else: - self.assertTrue(not("isDeleted" in user_cur)) + self.assertFalse("isDeleted" in user_cur) self.assertEquals(name_cur, name_orig) - self.assertEquals(user_orig["dn"], user_cur["dn"]) + self.assertEquals(obj_orig["dn"], user_cur["dn"]) self.assertTrue(dodn not in str(user_cur["dn"])) + return user_cur - def test_ReplicateDeteleteObject(self): + def test_ReplicateDeletedObject1(self): """Verifies how a deleted-object is replicated between two DCs. This test should verify that: - deleted-object is replicated properly - TODO: We should verify that after replication, - object's state to conform to a deleted-object state - or tombstone -object, depending on DC's features - It will also be great if check replPropertyMetaData.""" + - We verify that after replication, + object's state to conform to a tombstone-object state + - This test replicates the object modifications to + the server with the user deleted first + + TODO: It will also be great if check replPropertyMetaData. + TODO: Check for deleted-object state, depending on DC's features + when recycle-bin is enabled + """ # work-out unique username to test with username = self._make_username() @@ -104,7 +114,7 @@ class DrsDeleteObjectTestCase(drs_base.DrsBaseTestCase): # check user info on DC1 print "Testing for %s with GUID %s" % (username, self._GUID_string(user_orig["objectGUID"][0])) - self._check_user(sam_ldb=self.ldb_dc1, user_orig=user_orig, is_deleted=False) + self._check_obj(sam_ldb=self.ldb_dc1, obj_orig=user_orig, is_deleted=False) # trigger replication from DC1 to DC2 self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1, forced=True) @@ -112,23 +122,249 @@ class DrsDeleteObjectTestCase(drs_base.DrsBaseTestCase): # delete user on DC1 self.ldb_dc1.delete(user_dn) # check user info on DC1 - should be deleted - self._check_user(sam_ldb=self.ldb_dc1, user_orig=user_orig, is_deleted=True) + self._check_obj(sam_ldb=self.ldb_dc1, obj_orig=user_orig, is_deleted=True) + # check user info on DC2 - should be valid user + user_cur = self._check_obj(sam_ldb=self.ldb_dc2, obj_orig=user_orig, is_deleted=False) + + # The user should not have a description or memberOf yet + self.assertFalse("description" in user_cur) + self.assertFalse("memberOf" in user_cur) + + self.ldb_dc2.newgroup("group_%s" % username) + + self.ldb_dc2.newgroup("group2_%s" % username) + + ldb_res = self.ldb_dc2.search(base=self.domain_dn, + scope=SCOPE_SUBTREE, + expression="(samAccountName=group_%s)" % username) + self.assertTrue(len(ldb_res) == 1) + self.assertTrue("sAMAccountName" in ldb_res[0]) + group_orig = ldb_res[0] + group_dn = ldb_res[0]["dn"] + + # modify user on DC2 to have a description and be a member of the group + m = ldb.Message() + m.dn = user_dn + m["description"] = ldb.MessageElement("a description", + ldb.FLAG_MOD_ADD, "description") + self.ldb_dc2.modify(m) + m = ldb.Message() + m.dn = group_dn + m["member"] = ldb.MessageElement(str(user_dn), + ldb.FLAG_MOD_ADD, "member") + self.ldb_dc2.modify(m) + + ldb_res = self.ldb_dc2.search(base=self.domain_dn, + scope=SCOPE_SUBTREE, + expression="(samAccountName=group2_%s)" % username) + self.assertTrue(len(ldb_res) == 1) + self.assertTrue("sAMAccountName" in ldb_res[0]) + group2_dn = ldb_res[0]["dn"] + group2_orig = ldb_res[0] + + m = ldb.Message() + m.dn = group2_dn + m["member"] = ldb.MessageElement(str(group_dn), + ldb.FLAG_MOD_ADD, "member") + self.ldb_dc2.modify(m) + # check user info on DC2 - should be valid user - self._check_user(sam_ldb=self.ldb_dc2, user_orig=user_orig, is_deleted=False) + user_cur = self._check_obj(sam_ldb=self.ldb_dc2, obj_orig=user_orig, is_deleted=False) + + # The user should not have a description yet + self.assertTrue("description" in user_cur) + self.assertTrue("memberOf" in user_cur) + + ldb_res = self.ldb_dc2.search(base=self.domain_dn, + scope=SCOPE_SUBTREE, + expression="(samAccountName=group_%s)" % username) + self.assertTrue(len(ldb_res) == 1) + + # This group is a member of another group + self.assertTrue("memberOf" in ldb_res[0]) + + # The user was deleted on DC1, but check the modify we just did on DC2 + self.assertTrue("member" in ldb_res[0]) # trigger replication from DC2 to DC1 # to check if deleted object gets restored self._net_drs_replicate(DC=self.dnsname_dc1, fromDC=self.dnsname_dc2, forced=True) # check user info on DC1 - should be deleted - self._check_user(sam_ldb=self.ldb_dc1, user_orig=user_orig, is_deleted=True) + self._check_obj(sam_ldb=self.ldb_dc1, obj_orig=user_orig, is_deleted=True) # check user info on DC2 - should be valid user - self._check_user(sam_ldb=self.ldb_dc2, user_orig=user_orig, is_deleted=False) + self._check_obj(sam_ldb=self.ldb_dc2, obj_orig=user_orig, is_deleted=False) + + ldb_res = self.ldb_dc1.search(base=self.domain_dn, + scope=SCOPE_SUBTREE, + expression="(samAccountName=group_%s)" % username) + self.assertTrue(len(ldb_res) == 1) + + # This group is a member of another group + self.assertTrue("memberOf" in ldb_res[0]) + + # The user was deleted on DC1, but the modify we did on DC2, check it never replicated in + self.assertFalse("member" in ldb_res[0]) # trigger replication from DC1 to DC2 # to check if deleted object is replicated self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1, forced=True) # check user info on DC1 - should be deleted - self._check_user(sam_ldb=self.ldb_dc1, user_orig=user_orig, is_deleted=True) + self._check_obj(sam_ldb=self.ldb_dc1, obj_orig=user_orig, is_deleted=True) # check user info on DC2 - should be deleted - self._check_user(sam_ldb=self.ldb_dc2, user_orig=user_orig, is_deleted=True) + self._check_obj(sam_ldb=self.ldb_dc2, obj_orig=user_orig, is_deleted=True) + + # delete group on DC1 + self.ldb_dc1.delete(group_dn) + + # trigger replication from DC1 to DC2 + # to check if deleted object is replicated + self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1, forced=True) + + # check group info on DC1 - should be deleted + self._check_obj(sam_ldb=self.ldb_dc1, obj_orig=group_orig, is_deleted=True) + # check group info on DC2 - should be deleted + self._check_obj(sam_ldb=self.ldb_dc2, obj_orig=group_orig, is_deleted=True) + + ldb_res = self.ldb_dc2.search(base=self.domain_dn, + scope=SCOPE_SUBTREE, + expression="(samAccountName=group2_%s)" % username) + self.assertTrue(len(ldb_res) == 1) + self.assertFalse("member" in ldb_res[0]) + + # delete group on DC1 + self.ldb_dc1.delete(group2_dn) + + # trigger replication from DC1 to DC2 + # to check if deleted object is replicated + self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1, forced=True) + + # check group info on DC1 - should be deleted + self._check_obj(sam_ldb=self.ldb_dc1, obj_orig=group2_orig, is_deleted=True) + # check group info on DC2 - should be deleted + self._check_obj(sam_ldb=self.ldb_dc2, obj_orig=group2_orig, is_deleted=True) + + def test_ReplicateDeletedObject2(self): + """Verifies how a deleted-object is replicated between two DCs. + This test should verify that: + - deleted-object is replicated properly + - We verify that after replication, + object's state to conform to a tombstone-object state + - This test replicates the delete to the server with the + object modifications first + + TODO: It will also be great if check replPropertyMetaData. + TODO: Check for deleted-object state, depending on DC's features + when recycle-bin is enabled + """ + # work-out unique username to test with + username = self._make_username() + + # create user on DC1 + self.ldb_dc1.newuser(username=username, password="P@sswOrd!") + ldb_res = self.ldb_dc1.search(base=self.domain_dn, + scope=SCOPE_SUBTREE, + expression="(samAccountName=%s)" % username) + self.assertEquals(len(ldb_res), 1) + user_orig = ldb_res[0] + user_dn = ldb_res[0]["dn"] + + # check user info on DC1 + print "Testing for %s with GUID %s" % (username, self._GUID_string(user_orig["objectGUID"][0])) + self._check_obj(sam_ldb=self.ldb_dc1, obj_orig=user_orig, is_deleted=False) + + # trigger replication from DC1 to DC2 + self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1, forced=True) + + # delete user on DC1 + self.ldb_dc1.delete(user_dn) + # check user info on DC1 - should be deleted + self._check_obj(sam_ldb=self.ldb_dc1, obj_orig=user_orig, is_deleted=True) + # check user info on DC2 - should be valid user + user_cur = self._check_obj(sam_ldb=self.ldb_dc2, obj_orig=user_orig, is_deleted=False) + + # The user should not have a description or memberOf yet + self.assertFalse("description" in user_cur) + self.assertFalse("memberOf" in user_cur) + + self.ldb_dc2.newgroup("group_%s" % username) + + self.ldb_dc2.newgroup("group2_%s" % username) + + ldb_res = self.ldb_dc2.search(base=self.domain_dn, + scope=SCOPE_SUBTREE, + expression="(samAccountName=group_%s)" % username) + self.assertTrue(len(ldb_res) == 1) + self.assertTrue("sAMAccountName" in ldb_res[0]) + group_orig = ldb_res[0] + group_dn = ldb_res[0]["dn"] + + # modify user on DC2 to have a description and be a member of the group + m = ldb.Message() + m.dn = user_dn + m["description"] = ldb.MessageElement("a description", + ldb.FLAG_MOD_ADD, "description") + self.ldb_dc2.modify(m) + m = ldb.Message() + m.dn = group_dn + m["member"] = ldb.MessageElement(str(user_dn), + ldb.FLAG_MOD_ADD, "member") + self.ldb_dc2.modify(m) + + ldb_res = self.ldb_dc2.search(base=self.domain_dn, + scope=SCOPE_SUBTREE, + expression="(samAccountName=group2_%s)" % username) + self.assertTrue(len(ldb_res) == 1) + self.assertTrue("sAMAccountName" in ldb_res[0]) + group2_dn = ldb_res[0]["dn"] + group2_orig = ldb_res[0] + + m = ldb.Message() + m.dn = group2_dn + m["member"] = ldb.MessageElement(str(group_dn), + ldb.FLAG_MOD_ADD, "member") + self.ldb_dc2.modify(m) + + # check user info on DC2 - should be valid user + user_cur = self._check_obj(sam_ldb=self.ldb_dc2, obj_orig=user_orig, is_deleted=False) + + # The user should not have a description yet + self.assertTrue("description" in user_cur) + self.assertTrue("memberOf" in user_cur) + + # trigger replication from DC1 to DC2 + # to check if deleted object gets restored + self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1, forced=True) + # check user info on DC1 - should be deleted + self._check_obj(sam_ldb=self.ldb_dc1, obj_orig=user_orig, is_deleted=True) + # check user info on DC2 - should be deleted + self._check_obj(sam_ldb=self.ldb_dc2, obj_orig=user_orig, is_deleted=True) + + ldb_res = self.ldb_dc2.search(base=self.domain_dn, + scope=SCOPE_SUBTREE, + expression="(samAccountName=group_%s)" % username) + self.assertTrue(len(ldb_res) == 1) + self.assertTrue("memberOf" in ldb_res[0]) + self.assertFalse("member" in ldb_res[0]) + + # trigger replication from DC2 to DC1 + # to check if deleted object is replicated + self._net_drs_replicate(DC=self.dnsname_dc1, fromDC=self.dnsname_dc2, forced=True) + # check user info on DC1 - should be deleted + self._check_obj(sam_ldb=self.ldb_dc1, obj_orig=user_orig, is_deleted=True) + # check user info on DC2 - should be deleted + self._check_obj(sam_ldb=self.ldb_dc2, obj_orig=user_orig, is_deleted=True) + + ldb_res = self.ldb_dc1.search(base=self.domain_dn, + scope=SCOPE_SUBTREE, + expression="(samAccountName=group_%s)" % username) + self.assertTrue(len(ldb_res) == 1) + self.assertTrue("memberOf" in ldb_res[0]) + self.assertFalse("member" in ldb_res[0]) + + # delete group on DC1 + self.ldb_dc1.delete(group_dn) + self.ldb_dc1.delete(group2_dn) + + # trigger replication from DC1 to DC2, for cleanup + self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1, forced=True) -- 1.7.10.4 From 2739a7e497c7ac4e665eaff0600866db1ef281fb Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Tue, 4 Jun 2013 20:22:31 +1000 Subject: [PATCH 3/4] dsdb: Include MS-ADTS doc references on deleted object contstraints Signed-off-by: Andrew Bartlett Reviewed-by: Stefan Metzmacher (cherry picked from commit f2afdb61698c37389be286f9443471d4aeba49b8) --- source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index 0bfdd42..c8cdfec 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -3147,6 +3147,17 @@ static int replmd_delete_internals(struct ldb_module *module, struct ldb_request case OBJECT_TOMBSTONE: /* + * MS-ADTS 3.1.1.5.5.1.1 Tombstone Requirements + * describes what must be removed from a tombstone + * object + * + * MS-ADTS 3.1.1.5.5.1.3 Recycled-Object Requirements + * describes what must be removed from a recycled + * object + * + */ + + /* * we also mark it as recycled, meaning this object can't be * recovered (we are stripping its attributes). * This is done only if we have this schema object of course ... @@ -3223,6 +3234,11 @@ static int replmd_delete_internals(struct ldb_module *module, struct ldb_request break; case OBJECT_DELETED: + /* + * MS-ADTS 3.1.1.5.5.1.2 Deleted-Object Requirements + * describes what must be removed from a deleted + * object + */ ret = ldb_msg_add_empty(msg, "objectCategory", LDB_FLAG_MOD_REPLACE, NULL); if (ret != LDB_SUCCESS) { -- 1.7.10.4 From 800fd7d2ae6de1578aa48f3f5ce4f918e35a45be Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Sun, 14 Apr 2013 13:32:49 +1000 Subject: [PATCH 4/4] samba-tool dbcheck: Correctly remove deleted DNs in dbcheck The previous pattern never matched, as it was a typo. Andrew Bartlett Signed-off-by: Andrew Bartlett Reviewed-by: Stefan Metzmacher Autobuild-User(master): Stefan Metzmacher Autobuild-Date(master): Tue Jul 30 12:55:00 CEST 2013 on sn-devel-104 (cherry picked from commit 7615b2549d9549683978cb3e85b926e2ba63e294) --- python/samba/dbchecker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/samba/dbchecker.py b/python/samba/dbchecker.py index e88f876..8b175c2 100644 --- a/python/samba/dbchecker.py +++ b/python/samba/dbchecker.py @@ -271,7 +271,7 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) """handle a missing target DN (both GUID and DN string form are missing)""" # check if its a backlink linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname) - if (linkID & 1 == 0) and str(dsdb_dn).find('DEL\\0A') == -1: + if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1: self.report("Not removing dangling forward link") return self.err_deleted_dn(dn, attrname, val, dsdb_dn, dsdb_dn) -- 1.7.10.4