The Samba-Bugzilla – Attachment 10878 Details for
Bug 10371
Tombstone reanimation not supported
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
4.2 patch cherry-picked from master (with ldap server patch)
tombstone-renaimation-4.2.v2.patch (text/plain), 202.31 KB, created by
Andrew Bartlett
on 2015-03-18 03:53:09 UTC
(
hide
)
Description:
4.2 patch cherry-picked from master (with ldap server patch)
Filename:
MIME Type:
Creator:
Andrew Bartlett
Created:
2015-03-18 03:53:09 UTC
Size:
202.31 KB
patch
obsolete
>From ba7880e478e645e5662e412a838c9b21b690bf16 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Tue, 2 Dec 2014 01:53:18 +0100 >Subject: [PATCH 01/51] s4-ldap: Pass on-wire transmitted control down the > chain > >Change-Id: Ic3a117f74e8a67c45584fdb520d92e4f0cb01c5e >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >(cherry picked from commit 6214dbd5741e1e3e0e02b7e8eb42a76168a44192) >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >--- > source4/ldap_server/ldap_backend.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > >diff --git a/source4/ldap_server/ldap_backend.c b/source4/ldap_server/ldap_backend.c >index b0877d2..fd2b579 100644 >--- a/source4/ldap_server/ldap_backend.c >+++ b/source4/ldap_server/ldap_backend.c >@@ -459,7 +459,7 @@ static int ldapsrv_rename_with_controls(struct ldapsrv_call *call, > ret = ldb_build_rename_req(&req, ldb, ldb, > olddn, > newdn, >- NULL, >+ controls, > res, > ldb_modify_default_callback, > NULL); >-- >2.1.4 > > >From f0b82731de9e0ae4bbe90e66f53a0e836b79cfd9 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Tue, 2 Dec 2014 05:04:40 +0100 >Subject: [PATCH 02/51] s4-tests/env_loadparm: Throw KeyError in case > SMB_CONF_PATH > >A bit more specific for the caller to "know" that env key is missing > >Change-Id: I4d4c2121af868d79f46f865f420336222bc67347 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Jelmer Vernooij <jelmer@samba.org> > >Autobuild-User(master): Kamen Mazdrashki <kamenim@samba.org> >Autobuild-Date(master): Mon Dec 8 05:27:34 CET 2014 on sn-devel-104 > >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 29732b0d427472041bf3a586f3eeb281ccd408d5) >--- > python/samba/tests/__init__.py | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > >diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py >index 8d3b4dd..f3f17c8 100644 >--- a/python/samba/tests/__init__.py >+++ b/python/samba/tests/__init__.py >@@ -89,7 +89,7 @@ def env_loadparm(): > try: > lp.load(os.environ["SMB_CONF_PATH"]) > except KeyError: >- raise Exception("SMB_CONF_PATH not set") >+ raise KeyError("SMB_CONF_PATH not set") > return lp > > >-- >2.1.4 > > >From d3cf97a56b81e0b1a3466956bb25e2f0bae9002b Mon Sep 17 00:00:00 2001 >From: Nadezhda Ivanova <nivanova@symas.com> >Date: Tue, 21 Oct 2014 16:35:30 +0300 >Subject: [PATCH 03/51] s4-dsdb-tests: Some tests for deleted objects undelete > operation > >Based on MS-ADTS 3.1.1.5.3.7.2 > >Signed-off-by: Nadezhda Ivanova <nivanova@symas.com> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 > >Change-Id: I650b315601fce574f9302435f812d1dd4b177e68 >(cherry picked from commit b881da6584333e63737baaa8f90b518f0e0f639d) >--- > source4/dsdb/tests/python/deletetest.py | 203 +++++++++++++++++++++++++++++++- > 1 file changed, 198 insertions(+), 5 deletions(-) > >diff --git a/source4/dsdb/tests/python/deletetest.py b/source4/dsdb/tests/python/deletetest.py >index 370f56c..cb08db4 100755 >--- a/source4/dsdb/tests/python/deletetest.py >+++ b/source4/dsdb/tests/python/deletetest.py >@@ -13,9 +13,9 @@ from samba.tests.subunitrun import SubunitOptions, TestProgram > import samba.getopt as options > > from samba.auth import system_session >-from ldb import SCOPE_BASE, LdbError >-from ldb import ERR_NO_SUCH_OBJECT, ERR_NOT_ALLOWED_ON_NON_LEAF >-from ldb import ERR_UNWILLING_TO_PERFORM >+from ldb import SCOPE_BASE, LdbError, Message, MessageElement, Dn, FLAG_MOD_ADD, FLAG_MOD_DELETE, FLAG_MOD_REPLACE >+from ldb import ERR_NO_SUCH_OBJECT, ERR_NOT_ALLOWED_ON_NON_LEAF, ERR_ENTRY_ALREADY_EXISTS, ERR_ATTRIBUTE_OR_VALUE_EXISTS >+from ldb import ERR_UNWILLING_TO_PERFORM, ERR_OPERATIONS_ERROR > from samba.samdb import SamDB > from samba.tests import delete_force > >@@ -39,13 +39,13 @@ host = args[0] > lp = sambaopts.get_loadparm() > creds = credopts.get_credentials(lp) > >-class BasicDeleteTests(samba.tests.TestCase): >+class BaseDeleteTests(samba.tests.TestCase): > > def GUID_string(self, guid): > return self.ldb.schema_format_value("objectGUID", guid) > > def setUp(self): >- super(BasicDeleteTests, self).setUp() >+ super(BaseDeleteTests, self).setUp() > self.ldb = SamDB(host, credentials=creds, session_info=system_session(lp), lp=lp) > > self.base_dn = self.ldb.domain_dn() >@@ -69,6 +69,12 @@ class BasicDeleteTests(samba.tests.TestCase): > self.assertEquals(len(res), 1) > return res[0] > >+ >+class BasicDeleteTests(BaseDeleteTests): >+ >+ def setUp(self): >+ super(BasicDeleteTests, self).setUp() >+ > def del_attr_values(self, delObj): > print "Checking attributes for %s" % delObj["dn"] > >@@ -373,6 +379,193 @@ class BasicDeleteTests(samba.tests.TestCase): > self.assertFalse("CN=Deleted Objects" in str(objDeleted6.dn)) > self.assertFalse("CN=Deleted Objects" in str(objDeleted7.dn)) > >+class BasicUndeleteTests(BaseDeleteTests): >+ >+ def setUp(self): >+ super(BasicUndeleteTests, self).setUp() >+ >+ def enable_recycle_bin(self): >+ msg = Message() >+ msg.dn = Dn(ldb, "") >+ msg["enableOptionalFeature"] = MessageElement( >+ "CN=Partitions," + self.configuration_dn + ":766ddcd8-acd0-445e-f3b9-a7f9b6744f2a", >+ FLAG_MOD_ADD, "enableOptionalFeature") >+ try: >+ ldb.modify(msg) >+ except LdbError, (num, _): >+ self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) >+ >+ def get_ldb_connection(self, target_username, target_password): >+ creds_tmp = Credentials() >+ creds_tmp.set_username(target_username) >+ creds_tmp.set_password(target_password) >+ creds_tmp.set_domain(creds.get_domain()) >+ creds_tmp.set_realm(creds.get_realm()) >+ creds_tmp.set_workstation(creds.get_workstation()) >+ creds_tmp.set_gensec_features(creds_tmp.get_gensec_features() >+ | gensec.FEATURE_SEAL) >+ creds_tmp.set_kerberos_state(DONT_USE_KERBEROS) # kinit is too expensive to use in a tight loop >+ ldb_target = SamDB(url=ldaphost, credentials=creds_tmp, lp=lp) >+ return ldb_target >+ >+ def undelete_deleted(self, olddn, newdn, samldb): >+ msg = Message() >+ msg.dn = Dn(ldb, olddn) >+ msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") >+ msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName") >+ res = samldb.modify(msg, ["show_deleted:1"]) >+ >+ def undelete_deleted_with_mod(self, olddn, newdn): >+ msg = Message() >+ msg.dn = Dn(ldb, olddn) >+ msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") >+ msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName") >+ msg["url"] = MessageElement(["www.samba.org"], FLAG_MOD_REPLACE, "url") >+ res = ldb.modify(msg, ["show_deleted:1"]) >+ >+ >+ def test_undelete(self): >+ print "Testing standard undelete operation" >+ usr1="cn=testuser,cn=users," + self.base_dn >+ delete_force(self.ldb, usr1) >+ ldb.add({ >+ "dn": usr1, >+ "objectclass": "user", >+ "description": "test user description", >+ "samaccountname": "testuser"}) >+ objLive1 = self.search_dn(usr1) >+ guid1=objLive1["objectGUID"][0] >+ ldb.delete(usr1) >+ objDeleted1 = self.search_guid(guid1) >+ self.undelete_deleted(str(objDeleted1.dn), usr1, ldb) >+ objLive2 = self.search_dn(usr1) >+ self.assertEqual(str(objLive2.dn),str(objLive1.dn)) >+ delete_force(self.ldb, usr1) >+ >+ def test_rename(self): >+ print "Testing attempt to rename deleted object" >+ usr1="cn=testuser,cn=users," + self.base_dn >+ ldb.add({ >+ "dn": usr1, >+ "objectclass": "user", >+ "description": "test user description", >+ "samaccountname": "testuser"}) >+ objLive1 = self.search_dn(usr1) >+ guid1=objLive1["objectGUID"][0] >+ ldb.delete(usr1) >+ objDeleted1 = self.search_guid(guid1) >+ #just to make sure we get the correct error if the show deleted is missing >+ try: >+ ldb.rename(str(objDeleted1.dn), usr1) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num,ERR_NO_SUCH_OBJECT) >+ >+ try: >+ ldb.rename(str(objDeleted1.dn), usr1, ["show_deleted:1"]) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num,ERR_UNWILLING_TO_PERFORM) >+ >+ def test_undelete_with_mod(self): >+ print "Testing standard undelete operation with modification of additional attributes" >+ usr1="cn=testuser,cn=users," + self.base_dn >+ ldb.add({ >+ "dn": usr1, >+ "objectclass": "user", >+ "description": "test user description", >+ "samaccountname": "testuser"}) >+ objLive1 = self.search_dn(usr1) >+ guid1=objLive1["objectGUID"][0] >+ ldb.delete(usr1) >+ objDeleted1 = self.search_guid(guid1) >+ self.undelete_deleted_with_mod(str(objDeleted1.dn), usr1) >+ objLive2 = self.search_dn(usr1) >+ self.assertEqual(objLive2["url"][0],"www.samba.org") >+ delete_force(self.ldb, usr1) >+ >+ def test_undelete_newuser(self): >+ print "Testing undelete user with a different dn" >+ usr1="cn=testuser,cn=users," + self.base_dn >+ usr2="cn=testuser2,cn=users," + self.base_dn >+ delete_force(self.ldb, usr1) >+ ldb.add({ >+ "dn": usr1, >+ "objectclass": "user", >+ "description": "test user description", >+ "samaccountname": "testuser"}) >+ objLive1 = self.search_dn(usr1) >+ guid1=objLive1["objectGUID"][0] >+ ldb.delete(usr1) >+ objDeleted1 = self.search_guid(guid1) >+ self.undelete_deleted(str(objDeleted1.dn), usr2, ldb) >+ objLive2 = self.search_dn(usr2) >+ delete_force(self.ldb, usr1) >+ delete_force(self.ldb, usr2) >+ >+ def test_undelete_existing(self): >+ print "Testing undelete user after a user with the same dn has been created" >+ usr1="cn=testuser,cn=users," + self.base_dn >+ ldb.add({ >+ "dn": usr1, >+ "objectclass": "user", >+ "description": "test user description", >+ "samaccountname": "testuser"}) >+ objLive1 = self.search_dn(usr1) >+ guid1=objLive1["objectGUID"][0] >+ ldb.delete(usr1) >+ ldb.add({ >+ "dn": usr1, >+ "objectclass": "user", >+ "description": "test user description", >+ "samaccountname": "testuser"}) >+ objDeleted1 = self.search_guid(guid1) >+ try: >+ self.undelete_deleted(str(objDeleted1.dn), usr1, ldb) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num,ERR_ENTRY_ALREADY_EXISTS) >+ >+ def test_undelete_cross_nc(self): >+ print "Cross NC undelete" >+ c1 = "cn=ldaptestcontainer," + self.base_dn; >+ c2 = "cn=ldaptestcontainer2," + self.configuration_dn >+ c3 = "cn=ldaptestcontainer," + self.configuration_dn >+ c4 = "cn=ldaptestcontainer2," + self.base_dn; >+ ldb.add({ >+ "dn": c1, >+ "objectclass": "container"}) >+ ldb.add({ >+ "dn": c2, >+ "objectclass": "container"}) >+ objLive1 = self.search_dn(c1) >+ objLive2 = self.search_dn(c2) >+ guid1=objLive1["objectGUID"][0] >+ guid2=objLive2["objectGUID"][0] >+ ldb.delete(c1) >+ ldb.delete(c2) >+ objDeleted1 = self.search_guid(guid1) >+ objDeleted2 = self.search_guid(guid2) >+ #try to undelete from base dn to config >+ try: >+ self.undelete_deleted(str(objDeleted1.dn), c3, ldb) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num, ERR_OPERATIONS_ERROR) >+ #try to undelete from config to base dn >+ try: >+ self.undelete_deleted(str(objDeleted2.dn), c4, ldb) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num, ERR_OPERATIONS_ERROR) >+ #assert undeletion will work in same nc >+ self.undelete_deleted(str(objDeleted1.dn), c4, ldb) >+ self.undelete_deleted(str(objDeleted2.dn), c3, ldb) >+ delete_force(self.ldb, c3) >+ delete_force(self.ldb, c4) >+ >+ >+ > if not "://" in host: > if os.path.isfile(host): > host = "tdb://%s" % host >-- >2.1.4 > > >From 11a63db8a9817f38b7687bfa164aa7cfc1e13398 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sat, 27 Dec 2014 21:14:25 +0200 >Subject: [PATCH 04/51] s4-dsdb: Initial implementation for Tombstone > reanimation module > >At the moment it works for basic scenario: > - add user > - delete user > - restore deleted user > >TODO: > - security checks > - flags verification > - cross-NC checks > - asynchronous implementation (may not be needed, but anyway) > >Change-Id: If396a6dfc766c224acfeb7e93ca75703e08c26e6 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 886a352bf70b7ad3cdaceea90703c4f912397b8d) >--- > .../dsdb/samdb/ldb_modules/tombstone_reanimate.c | 256 +++++++++++++++++++++ > .../dsdb/samdb/ldb_modules/wscript_build_server | 11 +- > 2 files changed, 266 insertions(+), 1 deletion(-) > create mode 100644 source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >new file mode 100644 >index 0000000..989a664 >--- /dev/null >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -0,0 +1,256 @@ >+/* >+ ldb database library >+ >+ Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2014 >+ >+ This program is free software; you can redistribute it and/or modify >+ it under the terms of the GNU General Public License as published by >+ the Free Software Foundation; either version 3 of the License, or >+ (at your option) any later version. >+ >+ This program is distributed in the hope that it will be useful, >+ but WITHOUT ANY WARRANTY; without even the implied warranty of >+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+ GNU General Public License for more details. >+ >+ You should have received a copy of the GNU General Public License >+ along with this program. If not, see <http://www.gnu.org/licenses/>. >+*/ >+ >+/* >+ * Name: tombstone_reanimate >+ * >+ * Component: Handle Tombstone reanimation requests >+ * >+ * Description: >+ * Tombstone reanimation requests are plain ldap modify request like: >+ * dn: CN=tombi 1\0ADEL:e6e17ff7-8986-4cdd-87ad-afb683ccbb89,CN=Deleted Objects,DC=samba4,DC=devel >+ * changetype: modify >+ * delete: isDeleted >+ * - >+ * replace: distinguishedName >+ * distinguishedName: CN=Tombi 1,CN=Users,DC=samba4,DC=devel >+ * - >+ * >+ * Usually we don't allow distinguishedName modifications (see rdn_name.c) >+ * Reanimating Tombstones is described here: >+ * - TBD >+ * >+ * Author: Kamen Mazdrashki >+ */ >+ >+ >+#include "includes.h" >+#include "ldb_module.h" >+#include "dsdb/samdb/samdb.h" >+#include "librpc/ndr/libndr.h" >+#include "librpc/gen_ndr/ndr_security.h" >+#include "libcli/security/security.h" >+#include "auth/auth.h" >+#include "param/param.h" >+#include "../libds/common/flags.h" >+#include "dsdb/samdb/ldb_modules/util.h" >+ >+struct tr_context { >+ >+ struct ldb_module *module; >+ struct ldb_request *req; >+ const struct dsdb_schema *schema; >+ >+ struct ldb_reply *search_res; >+ struct ldb_reply *search_res2; >+ >+ int (*step_fn)(struct tr_context *); >+}; >+ >+static struct tr_context *tr_init_context(struct ldb_module *module, >+ struct ldb_request *req) >+{ >+ struct ldb_context *ldb; >+ struct tr_context *ac; >+ >+ ldb = ldb_module_get_ctx(module); >+ >+ ac = talloc_zero(req, struct tr_context); >+ if (ac == NULL) { >+ ldb_oom(ldb); >+ return NULL; >+ } >+ >+ ac->module = module; >+ ac->req = req; >+ ac->schema = dsdb_get_schema(ldb, ac); >+ >+ return ac; >+} >+ >+ >+static bool is_tombstone_reanimate_request(struct ldb_request *req, struct ldb_message_element **pel_dn) >+{ >+ struct ldb_message_element *el_dn; >+ struct ldb_message_element *el_deleted; >+ >+ /* check distinguishedName requirement */ >+ el_dn = ldb_msg_find_element(req->op.mod.message, "distinguishedName"); >+ if (el_dn == NULL || el_dn->flags != LDB_FLAG_MOD_REPLACE) { >+ return false; >+ } >+ >+ /* check isDeleted requirement */ >+ el_deleted = ldb_msg_find_element(req->op.mod.message, "isDeleted"); >+ if (el_deleted == NULL || el_deleted->flags != LDB_FLAG_MOD_DELETE) { >+ return false; >+ } >+ >+ *pel_dn = el_dn; >+ return true; >+} >+ >+static int _tr_make_object_category(struct tr_context *ac, struct ldb_message *obj, >+ TALLOC_CTX *mem_ctx, const char **pobjectcategory) >+{ >+ int ret; >+ struct ldb_context *ldb; >+ const struct dsdb_class *objectclass; >+ struct ldb_message_element *objectclass_element; >+ >+ ldb = ldb_module_get_ctx(ac->module); >+ >+ objectclass_element = ldb_msg_find_element(obj, "objectClass"); >+ if (!objectclass_element) { >+ ldb_asprintf_errstring(ldb, "tombstone_reanimate: Cannot add %s, no objectclass specified!", >+ ldb_dn_get_linearized(obj->dn)); >+ return LDB_ERR_OBJECT_CLASS_VIOLATION; >+ } >+ if (objectclass_element->num_values == 0) { >+ ldb_asprintf_errstring(ldb, "tombstone_reanimate: Cannot add %s, at least one (structural) objectclass has to be specified!", >+ ldb_dn_get_linearized(obj->dn)); >+ return LDB_ERR_CONSTRAINT_VIOLATION; >+ } >+ >+ /* Now do the sorting */ >+ ret = dsdb_sort_objectClass_attr(ldb, ac->schema, >+ objectclass_element, obj, >+ objectclass_element); >+ if (ret != LDB_SUCCESS) { >+ return ret; >+ } >+ >+ /* >+ * Get the new top-most structural object class and check for >+ * unrelated structural classes >+ */ >+ objectclass = dsdb_get_last_structural_class(ac->schema, >+ objectclass_element); >+ if (objectclass == NULL) { >+ ldb_asprintf_errstring(ldb, >+ "Failed to find a structural class for %s", >+ ldb_dn_get_linearized(obj->dn)); >+ return LDB_ERR_UNWILLING_TO_PERFORM; >+ } >+ >+ *pobjectcategory = talloc_strdup(mem_ctx, objectclass->defaultObjectCategory); >+ if (*pobjectcategory == NULL) { >+ return ldb_oom(ldb); >+ } >+ >+ return LDB_SUCCESS; >+} >+ >+static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_request *req) >+{ >+ int ret; >+ struct ldb_context *ldb; >+ struct ldb_dn *dn_new; >+ struct ldb_dn *objectcategory; >+ struct ldb_message_element *el_dn = NULL; >+ struct ldb_message *msg; >+ struct ldb_result *res_obj; >+ struct tr_context *ac; >+ >+ ldb = ldb_module_get_ctx(module); >+ >+ ldb_debug(ldb, LDB_DEBUG_TRACE, "%s\n", __PRETTY_FUNCTION__); >+ >+ /* do not manipulate our control entries */ >+ if (ldb_dn_is_special(req->op.mod.message->dn)) { >+ return ldb_next_request(module, req); >+ } >+ >+ ac = tr_init_context(module, req); >+ if (ac == NULL) { >+ return ldb_operr(ldb); >+ } >+ >+ /* Check if this is a reanimate request */ >+ if (!is_tombstone_reanimate_request(req, &el_dn)) { >+ return ldb_next_request(module, req); >+ } >+ >+ /* Load original object */ >+ ret = dsdb_module_search_dn(module, req, &res_obj, req->op.mod.message->dn, NULL, DSDB_FLAG_TOP_MODULE | DSDB_SEARCH_SHOW_DELETED, req); >+ if (ret != LDB_SUCCESS) { >+ return ldb_operr(ldb); >+ } >+ /* check if it a Deleted Object */ >+ if (!ldb_msg_find_attr_as_bool(res_obj->msgs[0], "isDeleted", false)) { >+ return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM, "Trying to restore not deleted object\n"); >+ } >+ >+ /* Simple implementation */ >+ /* Rename request to modify distinguishedName */ >+ dn_new = ldb_dn_from_ldb_val(req, ldb, &el_dn->values[0]); >+ if (dn_new == NULL) { >+ return ldb_oom(ldb); >+ } >+ ret = dsdb_module_rename(module, req->op.mod.message->dn, dn_new, DSDB_FLAG_TOP_MODULE | DSDB_SEARCH_SHOW_DELETED, req); >+ if (ret != LDB_SUCCESS) { >+ ldb_debug(ldb, LDB_DEBUG_ERROR, "Renaming object to %s has failed with %s\n", el_dn->values[0].data, ldb_strerror(ret)); >+ return ret; >+ } >+ >+ /* Modify request to: */ >+ msg = ldb_msg_copy_shallow(ac, req->op.mod.message); >+ if (msg == NULL) { >+ return ldb_module_oom(ac->module); >+ } >+ msg->dn = dn_new; >+ /* - delete isDeleted */ >+ ldb_msg_remove_attr(msg, "distinguishedName"); >+ >+ /* - restore objectCategory if not present */ >+ objectcategory = ldb_msg_find_attr_as_dn(ldb, ac, msg, >+ "objectCategory"); >+ if (objectcategory == NULL) { >+ const char *value; >+ >+ ret = _tr_make_object_category(ac, res_obj->msgs[0], msg, &value); >+ if (ret != LDB_SUCCESS) { >+ return ret; >+ } >+ >+ ret = ldb_msg_add_string(msg, "objectCategory", value); >+ if (ret != LDB_SUCCESS) { >+ return ret; >+ } >+ msg->elements[msg->num_elements-1].flags = LDB_FLAG_MOD_ADD; >+ } >+ ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE, req); >+ if (ret != LDB_SUCCESS) { >+ return ldb_operr(ldb); >+ } >+ >+ return ldb_module_done(ac->req, NULL, NULL, LDB_SUCCESS); >+} >+ >+ >+static const struct ldb_module_ops ldb_reanimate_module_ops = { >+ .name = "tombstone_reanimate", >+ .modify = tombstone_reanimate_modify, >+}; >+ >+int ldb_tombstone_reanimate_module_init(const char *version) >+{ >+ LDB_MODULE_CHECK_VERSION(version); >+ return ldb_register_module(&ldb_reanimate_module_ops); >+} >diff --git a/source4/dsdb/samdb/ldb_modules/wscript_build_server b/source4/dsdb/samdb/ldb_modules/wscript_build_server >index 8848fd2..b54d27f 100755 >--- a/source4/dsdb/samdb/ldb_modules/wscript_build_server >+++ b/source4/dsdb/samdb/ldb_modules/wscript_build_server >@@ -372,4 +372,13 @@ bld.SAMBA_MODULE('ldb_dns_notify', > module_init_name='ldb_init_module', > internal_module=False, > deps='talloc samdb DSDB_MODULE_HELPERS MESSAGING RPC_NDR_IRPC' >- ) >\ No newline at end of file >+ ) >+ >+bld.SAMBA_MODULE('tombstone_reanimate', >+ source='tombstone_reanimate.c', >+ subsystem='ldb', >+ init_function='ldb_tombstone_reanimate_module_init', >+ module_init_name='ldb_init_module', >+ internal_module=False, >+ deps='talloc samba-util DSDB_MODULE_HELPERS' >+ ) >-- >2.1.4 > > >From 1da2f0b30a6d1f919c4c6730e49a03e628ca047e Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Thu, 23 Oct 2014 08:15:23 +0200 >Subject: [PATCH 05/51] s4-dsdb: Insert tombstone_reanimate module in ldb > modules chain after objectclass > >Change-Id: Id9748f36f0aefe40b1894ecd2e5071e3b9c8a6d6 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 5aaa33694aa12ba61f608db55950d38d5a50a36c) >--- > source4/dsdb/samdb/ldb_modules/samba_dsdb.c | 1 + > 1 file changed, 1 insertion(+) > >diff --git a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c >index 26c583e..75553ad 100644 >--- a/source4/dsdb/samdb/ldb_modules/samba_dsdb.c >+++ b/source4/dsdb/samdb/ldb_modules/samba_dsdb.c >@@ -273,6 +273,7 @@ static int samba_dsdb_init(struct ldb_module *module) > NULL }; > /* extended_dn_in or extended_dn_in_openldap goes here */ > static const char *modules_list1a[] = {"objectclass", >+ "tombstone_reanimate", > "descriptor", > "acl", > "aclread", >-- >2.1.4 > > >From a4a12b29c0834bdbef671757f00620a64fb22b93 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sun, 26 Oct 2014 03:42:45 +0100 >Subject: [PATCH 06/51] s4-dsdb-tests: Remove trailing ';' in deletetest.py > >Change-Id: Ic1ad6bbda55be56cbf7ae78a8ad988b8e479a40c >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 7d2247939cf0c4026480f35301eab648681948ac) >--- > source4/dsdb/tests/python/deletetest.py | 4 ++-- > 1 file changed, 2 insertions(+), 2 deletions(-) > >diff --git a/source4/dsdb/tests/python/deletetest.py b/source4/dsdb/tests/python/deletetest.py >index cb08db4..8833d32 100755 >--- a/source4/dsdb/tests/python/deletetest.py >+++ b/source4/dsdb/tests/python/deletetest.py >@@ -528,10 +528,10 @@ class BasicUndeleteTests(BaseDeleteTests): > > def test_undelete_cross_nc(self): > print "Cross NC undelete" >- c1 = "cn=ldaptestcontainer," + self.base_dn; >+ c1 = "cn=ldaptestcontainer," + self.base_dn > c2 = "cn=ldaptestcontainer2," + self.configuration_dn > c3 = "cn=ldaptestcontainer," + self.configuration_dn >- c4 = "cn=ldaptestcontainer2," + self.base_dn; >+ c4 = "cn=ldaptestcontainer2," + self.base_dn > ldb.add({ > "dn": c1, > "objectclass": "container"}) >-- >2.1.4 > > >From 9d2a53ea113471d92073d58eba74f222267a0dbc Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sun, 26 Oct 2014 03:43:29 +0100 >Subject: [PATCH 07/51] s4-dsdb-tests: Remove unused method > get_ldap_connection() > >Change-Id: Ie50f77dbba724dbd3c2822de5c2cfff41016fac6 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit bb1337170c1059a8dce02d9c3d8f3bad647890dd) >--- > source4/dsdb/tests/python/deletetest.py | 13 ------------- > 1 file changed, 13 deletions(-) > >diff --git a/source4/dsdb/tests/python/deletetest.py b/source4/dsdb/tests/python/deletetest.py >index 8833d32..c4b3822 100755 >--- a/source4/dsdb/tests/python/deletetest.py >+++ b/source4/dsdb/tests/python/deletetest.py >@@ -395,19 +395,6 @@ class BasicUndeleteTests(BaseDeleteTests): > except LdbError, (num, _): > self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) > >- def get_ldb_connection(self, target_username, target_password): >- creds_tmp = Credentials() >- creds_tmp.set_username(target_username) >- creds_tmp.set_password(target_password) >- creds_tmp.set_domain(creds.get_domain()) >- creds_tmp.set_realm(creds.get_realm()) >- creds_tmp.set_workstation(creds.get_workstation()) >- creds_tmp.set_gensec_features(creds_tmp.get_gensec_features() >- | gensec.FEATURE_SEAL) >- creds_tmp.set_kerberos_state(DONT_USE_KERBEROS) # kinit is too expensive to use in a tight loop >- ldb_target = SamDB(url=ldaphost, credentials=creds_tmp, lp=lp) >- return ldb_target >- > def undelete_deleted(self, olddn, newdn, samldb): > msg = Message() > msg.dn = Dn(ldb, olddn) >-- >2.1.4 > > >From 964b956e335f0fa3020e5c65f114dc39cb31eff2 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sun, 26 Oct 2014 04:29:16 +0100 >Subject: [PATCH 08/51] s4-dsdb-tests: Make unique object names to test with in > deletetest > >This way we can re-run the test again and again > >Change-Id: I29bd878b77073d94a279c38bd0afc2f0befa6f9d >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 1afd50fed016841bd4ffedba3674447d08184fa6) >--- > source4/dsdb/tests/python/deletetest.py | 28 ++++++++++++++++++---------- > 1 file changed, 18 insertions(+), 10 deletions(-) > >diff --git a/source4/dsdb/tests/python/deletetest.py b/source4/dsdb/tests/python/deletetest.py >index c4b3822..1d0848b 100755 >--- a/source4/dsdb/tests/python/deletetest.py >+++ b/source4/dsdb/tests/python/deletetest.py >@@ -252,13 +252,21 @@ class BasicDeleteTests(BaseDeleteTests): > > print self.base_dn > >- usr1="cn=testuser,cn=users," + self.base_dn >- usr2="cn=testuser2,cn=users," + self.base_dn >- grp1="cn=testdelgroup1,cn=users," + self.base_dn >- sit1="cn=testsite1,cn=sites," + self.configuration_dn >- ss1="cn=NTDS Site Settings,cn=testsite1,cn=sites," + self.configuration_dn >- srv1="cn=Servers,cn=testsite1,cn=sites," + self.configuration_dn >- srv2="cn=TESTSRV,cn=Servers,cn=testsite1,cn=sites," + self.configuration_dn >+ # user current time in ms to make unique objects >+ import time >+ marker = str(int(round(time.time()*1000))) >+ usr1_name = "u_" + marker >+ usr2_name = "u2_" + marker >+ grp_name = "g1_" + marker >+ site_name = "s1_" + marker >+ >+ usr1 = "cn=%s,cn=users,%s" % (usr1_name, self.base_dn) >+ usr2 = "cn=%s,cn=users,%s" % (usr2_name, self.base_dn) >+ grp1 = "cn=%s,cn=users,%s" % (grp_name, self.base_dn) >+ sit1 = "cn=%s,cn=sites,%s" % (site_name, self.configuration_dn) >+ ss1 = "cn=NTDS Site Settings,cn=%s,cn=sites,%s" % (site_name, self.configuration_dn) >+ srv1 = "cn=Servers,cn=%s,cn=sites,%s" % (site_name, self.configuration_dn) >+ srv2 = "cn=TESTSRV,cn=Servers,cn=%s,cn=sites,%s" % (site_name, self.configuration_dn) > > delete_force(self.ldb, usr1) > delete_force(self.ldb, usr2) >@@ -272,19 +280,19 @@ class BasicDeleteTests(BaseDeleteTests): > "dn": usr1, > "objectclass": "user", > "description": "test user description", >- "samaccountname": "testuser"}) >+ "samaccountname": usr1_name}) > > self.ldb.add({ > "dn": usr2, > "objectclass": "user", > "description": "test user 2 description", >- "samaccountname": "testuser2"}) >+ "samaccountname": usr2_name}) > > self.ldb.add({ > "dn": grp1, > "objectclass": "group", > "description": "test group", >- "samaccountname": "testdelgroup1", >+ "samaccountname": grp_name, > "member": [ usr1, usr2 ], > "isDeleted": "FALSE" }) > >-- >2.1.4 > > >From 1376a155ea7ff37a91a85f0b6bd9b65571d2ebb2 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sun, 26 Oct 2014 04:29:49 +0100 >Subject: [PATCH 09/51] s4-dsdb-tests: Fix whitespace in deletetest.py > >Change-Id: Ic2924b0aa9cffd29fe0c857317ccb65ba53a1c21 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit f84e1989b452738f8cb5c1930e50bd13499c9de6) >--- > source4/dsdb/tests/python/deletetest.py | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > >diff --git a/source4/dsdb/tests/python/deletetest.py b/source4/dsdb/tests/python/deletetest.py >index 1d0848b..6170509 100755 >--- a/source4/dsdb/tests/python/deletetest.py >+++ b/source4/dsdb/tests/python/deletetest.py >@@ -519,7 +519,7 @@ class BasicUndeleteTests(BaseDeleteTests): > self.undelete_deleted(str(objDeleted1.dn), usr1, ldb) > self.fail() > except LdbError, (num, _): >- self.assertEquals(num,ERR_ENTRY_ALREADY_EXISTS) >+ self.assertEquals(num, ERR_ENTRY_ALREADY_EXISTS) > > def test_undelete_cross_nc(self): > print "Cross NC undelete" >-- >2.1.4 > > >From 294e39af38ad728b4bc6f740366c8d93a8c68b71 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sun, 26 Oct 2014 04:31:41 +0100 >Subject: [PATCH 10/51] s4-dsdb: Return error codes as windows does for > Tombstone reanimation > >Tested against Windows Server 2008 R2 >In case we try to restore to already existing object, windows >returns: LDB_ERR_ENTRY_ALREADY_EXISTS >Otherwise it is: LDB_ERR_OPERATIONS_ERROR > >Change-Id: I6b5fea1e327416ccf5069d97a4a378a527a25f80 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit ffdc834bd1433aa100ba57ae9e47fa09e591b8f7) >--- > source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c | 4 ++++ > 1 file changed, 4 insertions(+) > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index 989a664..321f16b 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -206,6 +206,10 @@ static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_requ > ret = dsdb_module_rename(module, req->op.mod.message->dn, dn_new, DSDB_FLAG_TOP_MODULE | DSDB_SEARCH_SHOW_DELETED, req); > if (ret != LDB_SUCCESS) { > ldb_debug(ldb, LDB_DEBUG_ERROR, "Renaming object to %s has failed with %s\n", el_dn->values[0].data, ldb_strerror(ret)); >+ if (ret != LDB_ERR_ENTRY_ALREADY_EXISTS) { >+ /* Windows returns Operations Error in case we can't rename the object */ >+ return LDB_ERR_OPERATIONS_ERROR; >+ } > return ret; > } > >-- >2.1.4 > > >From 62aa7f296984ddb8cd35b6315ccd8feaef769dd7 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Mon, 27 Oct 2014 05:31:54 +0100 >Subject: [PATCH 11/51] s4-dsdb: Initialize module context only we are to > handle Tombstone request > >Change-Id: I73bd2043e96907e3d1a669bdbd943ddee1df8c0a >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 5921bb84ab54123d68691e63154f22ed124f6be4) >--- > source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c | 10 +++++----- > 1 file changed, 5 insertions(+), 5 deletions(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index 321f16b..534b5f8 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -177,16 +177,16 @@ static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_requ > return ldb_next_request(module, req); > } > >- ac = tr_init_context(module, req); >- if (ac == NULL) { >- return ldb_operr(ldb); >- } >- > /* Check if this is a reanimate request */ > if (!is_tombstone_reanimate_request(req, &el_dn)) { > return ldb_next_request(module, req); > } > >+ ac = tr_init_context(module, req); >+ if (ac == NULL) { >+ return ldb_operr(ldb); >+ } >+ > /* Load original object */ > ret = dsdb_module_search_dn(module, req, &res_obj, req->op.mod.message->dn, NULL, DSDB_FLAG_TOP_MODULE | DSDB_SEARCH_SHOW_DELETED, req); > if (ret != LDB_SUCCESS) { >-- >2.1.4 > > >From eb17bffb2ff40caa5eda784d27582c398857f133 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Tue, 28 Oct 2014 06:10:56 +0100 >Subject: [PATCH 12/51] s4-dsdb: Make most specific objectCategory for an > object > >This is lightweight implementation and should be used on objects >with already verified objectClass attribute value - eg. valid classes, >sorted properly, etc. >Checkout objectclass.c module for heavy weight implementation. > >Change-Id: Ifa7880d26246f67e2f982496fcc6c77e6648d56f >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 1154075220da592e160ab357f2669eb4e1266217) >--- > source4/dsdb/samdb/ldb_modules/util.c | 67 +++++++++++++++++++++++++++++++++++ > 1 file changed, 67 insertions(+) > >diff --git a/source4/dsdb/samdb/ldb_modules/util.c b/source4/dsdb/samdb/ldb_modules/util.c >index a71c49b..4c81a1d 100644 >--- a/source4/dsdb/samdb/ldb_modules/util.c >+++ b/source4/dsdb/samdb/ldb_modules/util.c >@@ -1445,3 +1445,70 @@ int dsdb_fix_dn_rdncase(struct ldb_context *ldb, struct ldb_dn *dn) > } > return LDB_SUCCESS; > } >+ >+/** >+ * Make most specific objectCategory for the objectClass of passed object >+ * NOTE: In this implementation we count that it is called on already >+ * verified objectClass attribute value. See objectclass.c thorough >+ * implementation for all the magic that involves >+ * >+ * @param ldb ldb context >+ * @param schema cached schema for ldb. We may get it, but it is very time consuming. >+ * Hence leave the responsibility to the caller. >+ * @param obj AD object to determint objectCategory for >+ * @param mem_ctx Memory context - usually it is obj actually >+ * @param pobjectcategory location to store found objectCategory >+ * >+ * @return LDB_SUCCESS or error including out of memory error >+ */ >+int dsdb_make_object_category(struct ldb_context *ldb, const struct dsdb_schema *schema, >+ struct ldb_message *obj, >+ TALLOC_CTX *mem_ctx, const char **pobjectcategory) >+{ >+ const struct dsdb_class *objectclass; >+ struct ldb_message_element *objectclass_element; >+ struct dsdb_extended_dn_store_format *dn_format; >+ >+ objectclass_element = ldb_msg_find_element(obj, "objectClass"); >+ if (!objectclass_element) { >+ ldb_asprintf_errstring(ldb, "dsdb: Cannot add %s, no objectclass specified!", >+ ldb_dn_get_linearized(obj->dn)); >+ return LDB_ERR_OBJECT_CLASS_VIOLATION; >+ } >+ if (objectclass_element->num_values == 0) { >+ ldb_asprintf_errstring(ldb, "dsdb: Cannot add %s, at least one (structural) objectclass has to be specified!", >+ ldb_dn_get_linearized(obj->dn)); >+ return LDB_ERR_CONSTRAINT_VIOLATION; >+ } >+ >+ /* >+ * Get the new top-most structural object class and check for >+ * unrelated structural classes >+ */ >+ objectclass = dsdb_get_last_structural_class(schema, >+ objectclass_element); >+ if (objectclass == NULL) { >+ ldb_asprintf_errstring(ldb, >+ "Failed to find a structural class for %s", >+ ldb_dn_get_linearized(obj->dn)); >+ return LDB_ERR_UNWILLING_TO_PERFORM; >+ } >+ >+ dn_format = talloc_get_type(ldb_get_opaque(ldb, DSDB_EXTENDED_DN_STORE_FORMAT_OPAQUE_NAME), >+ struct dsdb_extended_dn_store_format); >+ if (dn_format && dn_format->store_extended_dn_in_ldb == false) { >+ /* Strip off extended components */ >+ struct ldb_dn *dn = ldb_dn_new(mem_ctx, ldb, >+ objectclass->defaultObjectCategory); >+ *pobjectcategory = ldb_dn_alloc_linearized(mem_ctx, dn); >+ talloc_free(dn); >+ } else { >+ *pobjectcategory = talloc_strdup(mem_ctx, objectclass->defaultObjectCategory); >+ } >+ >+ if (*pobjectcategory == NULL) { >+ return ldb_oom(ldb); >+ } >+ >+ return LDB_SUCCESS; >+} >-- >2.1.4 > > >From 22157e281774677176fb20c06003b9383d8ec5d3 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Tue, 28 Oct 2014 06:11:31 +0100 >Subject: [PATCH 13/51] s4-dsdb: Make use dsdb_make_object_category() for > objectCategory > >Change-Id: If65c54a653ad7078ca7a535b5c247db2746b5be7 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 4e44a0883e1ac5db84e9318b539322f10e35cf59) >--- > .../dsdb/samdb/ldb_modules/tombstone_reanimate.c | 53 +--------------------- > 1 file changed, 1 insertion(+), 52 deletions(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index 534b5f8..dff18d2 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -106,57 +106,6 @@ static bool is_tombstone_reanimate_request(struct ldb_request *req, struct ldb_m > return true; > } > >-static int _tr_make_object_category(struct tr_context *ac, struct ldb_message *obj, >- TALLOC_CTX *mem_ctx, const char **pobjectcategory) >-{ >- int ret; >- struct ldb_context *ldb; >- const struct dsdb_class *objectclass; >- struct ldb_message_element *objectclass_element; >- >- ldb = ldb_module_get_ctx(ac->module); >- >- objectclass_element = ldb_msg_find_element(obj, "objectClass"); >- if (!objectclass_element) { >- ldb_asprintf_errstring(ldb, "tombstone_reanimate: Cannot add %s, no objectclass specified!", >- ldb_dn_get_linearized(obj->dn)); >- return LDB_ERR_OBJECT_CLASS_VIOLATION; >- } >- if (objectclass_element->num_values == 0) { >- ldb_asprintf_errstring(ldb, "tombstone_reanimate: Cannot add %s, at least one (structural) objectclass has to be specified!", >- ldb_dn_get_linearized(obj->dn)); >- return LDB_ERR_CONSTRAINT_VIOLATION; >- } >- >- /* Now do the sorting */ >- ret = dsdb_sort_objectClass_attr(ldb, ac->schema, >- objectclass_element, obj, >- objectclass_element); >- if (ret != LDB_SUCCESS) { >- return ret; >- } >- >- /* >- * Get the new top-most structural object class and check for >- * unrelated structural classes >- */ >- objectclass = dsdb_get_last_structural_class(ac->schema, >- objectclass_element); >- if (objectclass == NULL) { >- ldb_asprintf_errstring(ldb, >- "Failed to find a structural class for %s", >- ldb_dn_get_linearized(obj->dn)); >- return LDB_ERR_UNWILLING_TO_PERFORM; >- } >- >- *pobjectcategory = talloc_strdup(mem_ctx, objectclass->defaultObjectCategory); >- if (*pobjectcategory == NULL) { >- return ldb_oom(ldb); >- } >- >- return LDB_SUCCESS; >-} >- > static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_request *req) > { > int ret; >@@ -228,7 +177,7 @@ static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_requ > if (objectcategory == NULL) { > const char *value; > >- ret = _tr_make_object_category(ac, res_obj->msgs[0], msg, &value); >+ ret = dsdb_make_object_category(ldb, ac->schema, res_obj->msgs[0], msg, &value); > if (ret != LDB_SUCCESS) { > return ret; > } >-- >2.1.4 > > >From bdeb14206ebc9b4b9d466134e21a0542e03dbd79 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Tue, 28 Oct 2014 15:03:59 +0100 >Subject: [PATCH 14/51] s4-dsdb: Define internal dsdb control to mark Tombstone > reanimation requests > >Tombstone reanimation requries some special handling which is going >to affect several modules. Most notably: > - a bit different access checks in acl.c > - restore certain attributes during modify requests in samldb.c > >Control added also to schema_samba4.ldif by Andrew Bartlett >hence the "pair programmed with" tag. > >Change-Id: Ief4f7dabbbdc2570924fae48c30ac9c531a701f4 >Pair-programmed-with: Andrew Bartlett <abartlet@samba.org> >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 039646b3cb9a5ff244a4fd8928b0edcffaf6255b) >--- > source4/dsdb/samdb/samdb.h | 8 ++++++++ > source4/setup/schema_samba4.ldif | 1 + > 2 files changed, 9 insertions(+) > >diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h >index 4f57343..635ac70 100644 >--- a/source4/dsdb/samdb/samdb.h >+++ b/source4/dsdb/samdb/samdb.h >@@ -141,6 +141,14 @@ struct dsdb_control_password_change { > */ > #define DSDB_CONTROL_PERMIT_INTERDOMAIN_TRUST_UAC_OID "1.3.6.1.4.1.7165.4.3.23" > >+/* >+ * Internal control to mark requests as being part of Tombstone restoring >+ * procedure - it requires slightly special behavior like: >+ * - a bit different security checks >+ * - restoring certain attributes to their default values, etc >+ */ >+#define DSDB_CONTROL_RESTORE_TOMBSTONE_OID "1.3.6.1.4.1.7165.4.3.24" >+ > #define DSDB_EXTENDED_REPLICATED_OBJECTS_OID "1.3.6.1.4.1.7165.4.4.1" > struct dsdb_extended_replicated_object { > struct ldb_message *msg; >diff --git a/source4/setup/schema_samba4.ldif b/source4/setup/schema_samba4.ldif >index 22f0bc1..bdcd625 100644 >--- a/source4/setup/schema_samba4.ldif >+++ b/source4/setup/schema_samba4.ldif >@@ -198,6 +198,7 @@ > #Allocated: DSDB_CONTROL_PASSWORD_BYPASS_LAST_SET_OID 1.3.6.1.4.1.7165.4.3.20 > #Allocated: DSDB_CONTROL_SEC_DESC_PROPAGATION_OID 1.3.6.1.4.1.7165.4.3.21 > #Allocated: DSDB_CONTROL_PERMIT_INTERDOMAIN_TRUST_UAC_OID 1.3.6.1.4.1.7165.4.3.23 >+#Allocated: DSDB_CONTROL_RESTORE_TOMBSTONE_OID 1.3.6.1.4.1.7165.4.3.24 > > # Extended 1.3.6.1.4.1.7165.4.4.x > #Allocated: DSDB_EXTENDED_REPLICATED_OBJECTS_OID 1.3.6.1.4.1.7165.4.4.1 >-- >2.1.4 > > >From f618a0563f5312313992a09a6b012f945c4bda32 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sun, 2 Nov 2014 17:11:20 +0100 >Subject: [PATCH 15/51] s4-tests: Print out what the error is in delete_force() > >Change-Id: Iaa631179dc79fa756416be8eaf8c55e3b0c1a29f >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit e33c54914306ae0fc726d8e066456346aac6ca6c) >--- > python/samba/tests/__init__.py | 4 ++-- > 1 file changed, 2 insertions(+), 2 deletions(-) > >diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py >index f3f17c8..0e9df6c 100644 >--- a/python/samba/tests/__init__.py >+++ b/python/samba/tests/__init__.py >@@ -236,5 +236,5 @@ def connect_samdb_ex(samdb_url, lp=None, session_info=None, credentials=None, > def delete_force(samdb, dn): > try: > samdb.delete(dn) >- except ldb.LdbError, (num, _): >- assert(num == ldb.ERR_NO_SUCH_OBJECT) >+ except ldb.LdbError, (num, errstr): >+ assert num == ldb.ERR_NO_SUCH_OBJECT, "ldb.delete() failed: %s" % errstr >-- >2.1.4 > > >From 7295c5c84f43231a71f3de8b179f99addc3bc838 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Mon, 3 Nov 2014 04:58:20 +0100 >Subject: [PATCH 16/51] s4-dsdb: Add documentation link for Tombstone > Reanimation > >Change-Id: Ib779c8b0839889371f25ad5751c9cda1a510eb54 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 2eef8e95a1d781456f6c5d6a49e21f88c113dc03) >--- > source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index dff18d2..591c322 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -34,7 +34,7 @@ > * > * Usually we don't allow distinguishedName modifications (see rdn_name.c) > * Reanimating Tombstones is described here: >- * - TBD >+ * - http://msdn.microsoft.com/en-us/library/cc223467.aspx > * > * Author: Kamen Mazdrashki > */ >-- >2.1.4 > > >From 7756f422088dd0273ae8b873434c18ab253924fe Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Tue, 4 Nov 2014 04:10:16 +0100 >Subject: [PATCH 17/51] s4-dsdb: Implement rename/modify requests as local for > the module > >The aim is for us to be able to fine tune the implementation >and also add custom LDAP controls to mark all requests as >being part of Reanimation procedure > >Change-Id: I9f1c04cd21bf032146eb2626d6495711fcadf10c >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 78f848419d80fe3184abfc6c06e13934d4d5a97c) >--- > .../dsdb/samdb/ldb_modules/tombstone_reanimate.c | 98 +++++++++++++++++++++- > 1 file changed, 96 insertions(+), 2 deletions(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index 591c322..1f3bba8 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -106,6 +106,100 @@ static bool is_tombstone_reanimate_request(struct ldb_request *req, struct ldb_m > return true; > } > >+/** >+ * Local rename implementation based on dsdb_module_rename() >+ * so we could fine tune it and add more controls >+ */ >+static int _tr_do_rename(struct ldb_module *module, struct ldb_request *parent_req, >+ struct ldb_dn *dn_from, struct ldb_dn *dn_to) >+{ >+ int ret; >+ struct ldb_request *req; >+ struct ldb_context *ldb = ldb_module_get_ctx(module); >+ TALLOC_CTX *tmp_ctx = talloc_new(parent_req); >+ struct ldb_result *res; >+ >+ res = talloc_zero(tmp_ctx, struct ldb_result); >+ if (!res) { >+ talloc_free(tmp_ctx); >+ return ldb_oom(ldb_module_get_ctx(module)); >+ } >+ >+ ret = ldb_build_rename_req(&req, ldb, tmp_ctx, >+ dn_from, >+ dn_to, >+ NULL, >+ res, >+ ldb_modify_default_callback, >+ parent_req); >+ LDB_REQ_SET_LOCATION(req); >+ if (ret != LDB_SUCCESS) { >+ talloc_free(tmp_ctx); >+ return ret; >+ } >+ >+ ret = dsdb_request_add_controls(req, DSDB_SEARCH_SHOW_DELETED); >+ if (ret != LDB_SUCCESS) { >+ talloc_free(tmp_ctx); >+ return ret; >+ } >+ >+ /* >+ * Run request from the top module >+ * so we get show_deleted control OID resolved >+ */ >+ ret = ldb_request(ldb_module_get_ctx(module), req); >+ if (ret == LDB_SUCCESS) { >+ ret = ldb_wait(req->handle, LDB_WAIT_ALL); >+ } >+ >+ talloc_free(tmp_ctx); >+ return ret; >+} >+ >+/** >+ * Local rename implementation based on dsdb_module_modify() >+ * so we could fine tune it and add more controls >+ */ >+static int _tr_do_modify(struct ldb_module *module, struct ldb_request *parent_req, struct ldb_message *msg) >+{ >+ int ret; >+ struct ldb_request *mod_req; >+ struct ldb_context *ldb = ldb_module_get_ctx(module); >+ TALLOC_CTX *tmp_ctx = talloc_new(parent_req); >+ struct ldb_result *res; >+ >+ res = talloc_zero(tmp_ctx, struct ldb_result); >+ if (!res) { >+ talloc_free(tmp_ctx); >+ return ldb_oom(ldb_module_get_ctx(module)); >+ } >+ >+ ret = ldb_build_mod_req(&mod_req, ldb, tmp_ctx, >+ msg, >+ NULL, >+ res, >+ ldb_modify_default_callback, >+ parent_req); >+ LDB_REQ_SET_LOCATION(mod_req); >+ if (ret != LDB_SUCCESS) { >+ talloc_free(tmp_ctx); >+ return ret; >+ } >+ >+ /* Run request from Next module */ >+ ret = ldb_next_request(module, mod_req); >+ if (ret == LDB_SUCCESS) { >+ ret = ldb_wait(mod_req->handle, LDB_WAIT_ALL); >+ } >+ >+ talloc_free(tmp_ctx); >+ return ret; >+} >+ >+/** >+ * Handle special LDAP modify request to restore deleted objects >+ */ > static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_request *req) > { > int ret; >@@ -152,7 +246,7 @@ static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_requ > if (dn_new == NULL) { > return ldb_oom(ldb); > } >- ret = dsdb_module_rename(module, req->op.mod.message->dn, dn_new, DSDB_FLAG_TOP_MODULE | DSDB_SEARCH_SHOW_DELETED, req); >+ ret = _tr_do_rename(module, req, req->op.mod.message->dn, dn_new); > if (ret != LDB_SUCCESS) { > ldb_debug(ldb, LDB_DEBUG_ERROR, "Renaming object to %s has failed with %s\n", el_dn->values[0].data, ldb_strerror(ret)); > if (ret != LDB_ERR_ENTRY_ALREADY_EXISTS) { >@@ -188,7 +282,7 @@ static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_requ > } > msg->elements[msg->num_elements-1].flags = LDB_FLAG_MOD_ADD; > } >- ret = dsdb_module_modify(module, msg, DSDB_FLAG_NEXT_MODULE, req); >+ ret = _tr_do_modify(module, req, msg); > if (ret != LDB_SUCCESS) { > return ldb_operr(ldb); > } >-- >2.1.4 > > >From d173e1838a4ea54c6c280565995c1ea3950af262 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Tue, 4 Nov 2014 04:17:35 +0100 >Subject: [PATCH 18/51] s4-dsdb: Mark request during Tombstone reanimation with > custom LDAP control > >We are going to need this so that underlying modules (acl.c) >can treat those requests properly > >Change-Id: I6c12069aa6e7e01197dddda6c610d930d3fd9cb0 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit def9d268681625c2431e53d842f22a01af72c95c) >--- > source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c | 14 ++++++++++++++ > 1 file changed, 14 insertions(+) > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index 1f3bba8..c175a0c 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -144,6 +144,13 @@ static int _tr_do_rename(struct ldb_module *module, struct ldb_request *parent_r > return ret; > } > >+ /* mark request as part of Tombstone reanimation */ >+ ret = ldb_request_add_control(req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID, false, NULL); >+ if (ret != LDB_SUCCESS) { >+ talloc_free(tmp_ctx); >+ return ret; >+ } >+ > /* > * Run request from the top module > * so we get show_deleted control OID resolved >@@ -187,6 +194,13 @@ static int _tr_do_modify(struct ldb_module *module, struct ldb_request *parent_r > return ret; > } > >+ /* mark request as part of Tombstone reanimation */ >+ ret = ldb_request_add_control(mod_req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID, false, NULL); >+ if (ret != LDB_SUCCESS) { >+ talloc_free(tmp_ctx); >+ return ret; >+ } >+ > /* Run request from Next module */ > ret = ldb_next_request(module, mod_req); > if (ret == LDB_SUCCESS) { >-- >2.1.4 > > >From fdbfa9beaeeebc1cea82f5b9c44b50f2de885bf4 Mon Sep 17 00:00:00 2001 >From: Nadezhda Ivanova <nivanova@symas.com> >Date: Tue, 4 Nov 2014 20:08:58 +0200 >Subject: [PATCH 19/51] s4-dsdb: Tests for security checks on undelete > operation > >Implemented according to MS-ADTS 3.1.1.5.3.7.1. Unfortunately it appears >LC is also necessary, and it is not granted by default to anyone but >System and Administrator, so tests had to be done negatively > >Signed-off-by: Nadezhda Ivanova <nivanova@symas.com> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >Change-Id: Ic03b8fc4e222e7842ec8a9645a1bb33e7df9c438 >(cherry picked from commit ac8b8e5539b79407292a5ef19bdd2aaf86b92884) >--- > python/samba/sd_utils.py | 8 +-- > source4/dsdb/tests/python/acl.py | 132 ++++++++++++++++++++++++++++++++++++++- > 2 files changed, 135 insertions(+), 5 deletions(-) > >diff --git a/python/samba/sd_utils.py b/python/samba/sd_utils.py >index ded9bfc..7592a29 100644 >--- a/python/samba/sd_utils.py >+++ b/python/samba/sd_utils.py >@@ -62,7 +62,7 @@ class SDUtils(object): > def dacl_add_ace(self, object_dn, ace): > """Add an ACE to an objects security descriptor > """ >- desc = self.read_sd_on_dn(object_dn) >+ desc = self.read_sd_on_dn(object_dn,["show_deleted:1"]) > desc_sddl = desc.as_sddl(self.domain_sid) > if ace in desc_sddl: > return >@@ -71,10 +71,10 @@ class SDUtils(object): > desc_sddl[desc_sddl.index("("):]) > else: > desc_sddl = desc_sddl + ace >- self.modify_sd_on_dn(object_dn, desc_sddl) >+ self.modify_sd_on_dn(object_dn, desc_sddl, ["show_deleted:1"]) > >- def get_sd_as_sddl(self, object_dn, controls=None): >+ def get_sd_as_sddl(self, object_dn, controls=[]): > """Return object nTSecutiryDescriptor in SDDL format > """ >- desc = self.read_sd_on_dn(object_dn, controls=controls) >+ desc = self.read_sd_on_dn(object_dn, controls + ["show_deleted:1"]) > return desc.as_sddl(self.domain_sid) >diff --git a/source4/dsdb/tests/python/acl.py b/source4/dsdb/tests/python/acl.py >index 4acc123..d8e8962 100755 >--- a/source4/dsdb/tests/python/acl.py >+++ b/source4/dsdb/tests/python/acl.py >@@ -20,7 +20,7 @@ from ldb import ( > from ldb import ERR_CONSTRAINT_VIOLATION > from ldb import ERR_OPERATIONS_ERROR > from ldb import Message, MessageElement, Dn >-from ldb import FLAG_MOD_REPLACE, FLAG_MOD_ADD >+from ldb import FLAG_MOD_REPLACE, FLAG_MOD_ADD, FLAG_MOD_DELETE > from samba.dcerpc import security, drsuapi, misc > > from samba.auth import system_session >@@ -1637,6 +1637,136 @@ class AclExtendedTests(AclTests): > self.assertEqual(len(res),1) > self.assertTrue("nTSecurityDescriptor" in res[0].keys()) > >+class AclUndeleteTests(AclTests): >+ >+ def setUp(self): >+ super(AclUndeleteTests, self).setUp() >+ self.regular_user = "undeleter1" >+ self.ou1 = "OU=undeleted_ou," >+ self.testuser1 = "to_be_undeleted1" >+ self.testuser2 = "to_be_undeleted2" >+ self.testuser3 = "to_be_undeleted3" >+ self.testuser4 = "to_be_undeleted4" >+ self.testuser5 = "to_be_undeleted5" >+ self.testuser6 = "to_be_undeleted6" >+ >+ self.new_dn_ou = "CN="+ self.testuser4 + "," + self.ou1 + self.base_dn >+ >+ # Create regular user >+ self.testuser1_dn = self.get_user_dn(self.testuser1) >+ self.testuser2_dn = self.get_user_dn(self.testuser2) >+ self.testuser3_dn = self.get_user_dn(self.testuser3) >+ self.testuser4_dn = self.get_user_dn(self.testuser4) >+ self.testuser5_dn = self.get_user_dn(self.testuser5) >+ self.deleted_dn1 = self.create_delete_user(self.testuser1) >+ self.deleted_dn2 = self.create_delete_user(self.testuser2) >+ self.deleted_dn3 = self.create_delete_user(self.testuser3) >+ self.deleted_dn4 = self.create_delete_user(self.testuser4) >+ self.deleted_dn5 = self.create_delete_user(self.testuser5) >+ >+ self.ldb_admin.create_ou(self.ou1 + self.base_dn) >+ >+ self.ldb_admin.newuser(self.regular_user, self.user_pass) >+ self.ldb_admin.add_remove_group_members("Domain Admins", [self.regular_user], >+ add_members_operation=True) >+ self.ldb_user = self.get_ldb_connection(self.regular_user, self.user_pass) >+ self.sid = self.sd_utils.get_object_sid(self.get_user_dn(self.regular_user)) >+ >+ def tearDown(self): >+ super(AclUndeleteTests, self).tearDown() >+ delete_force(self.ldb_admin, self.get_user_dn(self.regular_user)) >+ delete_force(self.ldb_admin, self.get_user_dn(self.testuser1)) >+ delete_force(self.ldb_admin, self.get_user_dn(self.testuser2)) >+ delete_force(self.ldb_admin, self.get_user_dn(self.testuser3)) >+ delete_force(self.ldb_admin, self.get_user_dn(self.testuser4)) >+ delete_force(self.ldb_admin, self.get_user_dn(self.testuser5)) >+ delete_force(self.ldb_admin, self.new_dn_ou) >+ delete_force(self.ldb_admin, self.ou1 + self.base_dn) >+ >+ def GUID_string(self, guid): >+ return ldb.schema_format_value("objectGUID", guid) >+ >+ def create_delete_user(self, new_user): >+ self.ldb_admin.newuser(new_user, self.user_pass) >+ >+ res = self.ldb_admin.search(expression="(objectClass=*)", >+ base=self.get_user_dn(new_user), >+ scope=SCOPE_BASE, >+ controls=["show_deleted:1"]) >+ guid = res[0]["objectGUID"][0] >+ self.ldb_admin.delete(self.get_user_dn(new_user)) >+ res = self.ldb_admin.search(base="<GUID=%s>" % self.GUID_string(guid), >+ scope=SCOPE_BASE, controls=["show_deleted:1"]) >+ self.assertEquals(len(res), 1) >+ return str(res[0].dn) >+ >+ def undelete_deleted(self, olddn, newdn): >+ msg = Message() >+ msg.dn = Dn(self.ldb_user, olddn) >+ msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") >+ msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName") >+ res = self.ldb_user.modify(msg, ["show_recycled:1"]) >+ >+ def undelete_deleted_with_mod(self, olddn, newdn): >+ msg = Message() >+ msg.dn = Dn(ldb, olddn) >+ msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") >+ msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName") >+ msg["url"] = MessageElement(["www.samba.org"], FLAG_MOD_REPLACE, "url") >+ res = self.ldb_user.modify(msg, ["show_deleted:1"]) >+ >+ def test_undelete(self): >+ # it appears the user has to have LC on the old parent to be able to move the object >+ # otherwise we get no such object. Since only System can modify the SD on deleted object >+ # we cannot grant this permission via LDAP, and this leaves us with "negative" tests at the moment >+ >+ # deny write property on rdn, should fail >+ mod = "(OD;;WP;bf967a0e-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.sid) >+ self.sd_utils.dacl_add_ace(self.deleted_dn1, mod) >+ try: >+ self.undelete_deleted(self.deleted_dn1, self.testuser1_dn) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) >+ >+ # seems that permissions on isDeleted and distinguishedName are irrelevant >+ mod = "(OD;;WP;bf96798f-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.sid) >+ self.sd_utils.dacl_add_ace(self.deleted_dn2, mod) >+ mod = "(OD;;WP;bf9679e4-0de6-11d0-a285-00aa003049e2;;%s)" % str(self.sid) >+ self.sd_utils.dacl_add_ace(self.deleted_dn2, mod) >+ self.undelete_deleted(self.deleted_dn2, self.testuser2_dn) >+ >+ # attempt undelete with simultanious addition of url, WP to which is denied >+ mod = "(OD;;WP;9a9a0221-4a5b-11d1-a9c3-0000f80367c1;;%s)" % str(self.sid) >+ self.sd_utils.dacl_add_ace(self.deleted_dn3, mod) >+ try: >+ self.undelete_deleted_with_mod(self.deleted_dn3, self.testuser3_dn) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) >+ >+ # undelete in an ou, in which we have no right to create children >+ mod = "(D;;CC;;;%s)" % str(self.sid) >+ self.sd_utils.dacl_add_ace(self.ou1 + self.base_dn, mod) >+ try: >+ self.undelete_deleted(self.deleted_dn4, self.new_dn_ou) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) >+ >+ # delete is not required >+ mod = "(D;;SD;;;%s)" % str(self.sid) >+ self.sd_utils.dacl_add_ace(self.deleted_dn5, mod) >+ self.undelete_deleted(self.deleted_dn5, self.testuser5_dn) >+ >+ # deny Reanimate-Tombstone, should fail >+ mod = "(OD;;CR;45ec5156-db7e-47bb-b53f-dbeb2d03c40f;;%s)" % str(self.sid) >+ self.sd_utils.dacl_add_ace(self.base_dn, mod) >+ try: >+ self.undelete_deleted(self.deleted_dn4, self.testuser4_dn) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num, ERR_INSUFFICIENT_ACCESS_RIGHTS) > > class AclSPNTests(AclTests): > >-- >2.1.4 > > >From a053dac534b3ed9cd77d66c12d1b109e8cf1deee Mon Sep 17 00:00:00 2001 >From: Nadezhda Ivanova <nivanova@symas.com> >Date: Tue, 4 Nov 2014 20:21:57 +0200 >Subject: [PATCH 20/51] s4-dsdb: Implementation of access checks on a undelete > operation > >Special Reanimate-Tombstone access right is required, as well as most of >the checks on a standard rename. > >Change-Id: Idae5101a5df4cd0d54fe4ab2f7e5ad7fc1c23648 >Signed-off-by: Nadezhda Ivanova <nivanova@symas.com> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit d6334925ab6687bff464fd1a4d4d792a8d37c3a4) >--- > librpc/idl/security.idl | 1 + > source4/dsdb/samdb/ldb_modules/acl.c | 97 +++++++++++++++++++++++++++++------- > 2 files changed, 80 insertions(+), 18 deletions(-) > >diff --git a/librpc/idl/security.idl b/librpc/idl/security.idl >index 78c13c9..1f5390a 100644 >--- a/librpc/idl/security.idl >+++ b/librpc/idl/security.idl >@@ -688,6 +688,7 @@ interface security > const string GUID_DRS_ENABLE_PER_USER_REVERSIBLY_ENCRYPTED_PASSWORD > = "05c74c5e-4deb-43b4-bd9f-86664c2a7fd5"; > const string GUID_DRS_DS_INSTALL_REPLICA = "9923a32a-3607-11d2-b9be-0000f87a36b2"; >+ const string GUID_DRS_REANIMATE_TOMBSTONE = "45ec5156-db7e-47bb-b53f-dbeb2d03c40f"; > > > /***************************************************************/ >diff --git a/source4/dsdb/samdb/ldb_modules/acl.c b/source4/dsdb/samdb/ldb_modules/acl.c >index e75fb2a..78e6461 100644 >--- a/source4/dsdb/samdb/ldb_modules/acl.c >+++ b/source4/dsdb/samdb/ldb_modules/acl.c >@@ -1028,6 +1028,7 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req) > struct security_descriptor *sd; > struct dom_sid *sid = NULL; > struct ldb_control *as_system; >+ struct ldb_control *is_undelete; > bool userPassword; > TALLOC_CTX *tmp_ctx; > const struct ldb_message *msg = req->op.mod.message; >@@ -1047,6 +1048,8 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req) > as_system->critical = 0; > } > >+ is_undelete = ldb_request_get_control(req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID); >+ > /* Don't print this debug statement if elements[0].name is going to be NULL */ > if (msg->num_elements > 0) { > DEBUG(10, ("ldb:acl_modify: %s\n", msg->elements[0].name)); >@@ -1193,6 +1196,14 @@ static int acl_modify(struct ldb_module *module, struct ldb_request *req) > if (ret != LDB_SUCCESS) { > goto fail; > } >+ } else if (is_undelete != NULL && (ldb_attr_cmp("isDeleted", el->name) == 0)) { >+ /* >+ * in case of undelete op permissions on >+ * isDeleted are irrelevant and >+ * distinguishedName is removed by the >+ * tombstone_reanimate module >+ */ >+ continue; > } else { > ret = acl_check_access_on_attribute(module, > tmp_ctx, >@@ -1346,6 +1357,42 @@ static int acl_delete(struct ldb_module *module, struct ldb_request *req) > > return ldb_next_request(module, req); > } >+static int acl_check_reanimate_tombstone(TALLOC_CTX *mem_ctx, >+ struct ldb_module *module, >+ struct ldb_request *req, >+ struct ldb_dn *nc_root) >+{ >+ int ret; >+ struct ldb_result *acl_res; >+ struct security_descriptor *sd = NULL; >+ struct dom_sid *sid = NULL; >+ static const char *acl_attrs[] = { >+ "nTSecurityDescriptor", >+ "objectClass", >+ "objectSid", >+ NULL >+ }; >+ >+ ret = dsdb_module_search_dn(module, mem_ctx, &acl_res, >+ nc_root, acl_attrs, >+ DSDB_FLAG_NEXT_MODULE | >+ DSDB_FLAG_AS_SYSTEM | >+ DSDB_SEARCH_SHOW_RECYCLED, req); >+ if (ret != LDB_SUCCESS) { >+ DEBUG(10,("acl: failed to find object %s\n", >+ ldb_dn_get_linearized(nc_root))); >+ return ret; >+ } >+ >+ ret = dsdb_get_sd_from_ldb_message(mem_ctx, req, acl_res->msgs[0], &sd); >+ sid = samdb_result_dom_sid(mem_ctx, acl_res->msgs[0], "objectSid"); >+ if (ret != LDB_SUCCESS || !sd) { >+ return ldb_operr(ldb_module_get_ctx(module)); >+ } >+ return acl_check_extended_right(mem_ctx, sd, acl_user_token(module), >+ GUID_DRS_REANIMATE_TOMBSTONE, >+ SEC_ADS_CONTROL_ACCESS, sid); >+} > > static int acl_rename(struct ldb_module *module, struct ldb_request *req) > { >@@ -1361,6 +1408,7 @@ static int acl_rename(struct ldb_module *module, struct ldb_request *req) > struct ldb_result *acl_res; > struct ldb_dn *nc_root; > struct ldb_control *as_system; >+ struct ldb_control *is_undelete; > TALLOC_CTX *tmp_ctx; > const char *rdn_name; > static const char *acl_attrs[] = { >@@ -1413,6 +1461,17 @@ static int acl_rename(struct ldb_module *module, struct ldb_request *req) > return ldb_module_done(req, NULL, NULL, > LDB_ERR_UNWILLING_TO_PERFORM); > } >+ >+ /* special check for undelete operation */ >+ is_undelete = ldb_request_get_control(req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID); >+ if (is_undelete != NULL) { >+ is_undelete->critical = 0; >+ ret = acl_check_reanimate_tombstone(tmp_ctx, module, req, nc_root); >+ if (ret != LDB_SUCCESS) { >+ talloc_free(tmp_ctx); >+ return ret; >+ } >+ } > talloc_free(nc_root); > > /* Look for the parent */ >@@ -1526,25 +1585,27 @@ static int acl_rename(struct ldb_module *module, struct ldb_request *req) > } > > /* do we have delete object on the object? */ >- ret = acl_check_access_on_objectclass(module, tmp_ctx, sd, sid, >- SEC_STD_DELETE, >- objectclass); >- if (ret == LDB_SUCCESS) { >- talloc_free(tmp_ctx); >- return ldb_next_request(module, req); >- } >- /* what about delete child on the current parent */ >- ret = dsdb_module_check_access_on_dn(module, req, oldparent, >- SEC_ADS_DELETE_CHILD, >- &objectclass->schemaIDGUID, >- req); >- if (ret != LDB_SUCCESS) { >- ldb_asprintf_errstring(ldb_module_get_ctx(module), >- "acl:access_denied renaming %s", ldb_dn_get_linearized(req->op.rename.olddn)); >- talloc_free(tmp_ctx); >- return ldb_module_done(req, NULL, NULL, ret); >+ /* this access is not necessary for undelete ops */ >+ if (is_undelete == NULL) { >+ ret = acl_check_access_on_objectclass(module, tmp_ctx, sd, sid, >+ SEC_STD_DELETE, >+ objectclass); >+ if (ret == LDB_SUCCESS) { >+ talloc_free(tmp_ctx); >+ return ldb_next_request(module, req); >+ } >+ /* what about delete child on the current parent */ >+ ret = dsdb_module_check_access_on_dn(module, req, oldparent, >+ SEC_ADS_DELETE_CHILD, >+ &objectclass->schemaIDGUID, >+ req); >+ if (ret != LDB_SUCCESS) { >+ ldb_asprintf_errstring(ldb_module_get_ctx(module), >+ "acl:access_denied renaming %s", ldb_dn_get_linearized(req->op.rename.olddn)); >+ talloc_free(tmp_ctx); >+ return ldb_module_done(req, NULL, NULL, ret); >+ } > } >- > talloc_free(tmp_ctx); > > return ldb_next_request(module, req); >-- >2.1.4 > > >From 39c2a1efe422fd2e45d71eaf11a32230d2dbef02 Mon Sep 17 00:00:00 2001 >From: Nadezhda Ivanova <nivanova@symas.com> >Date: Tue, 4 Nov 2014 20:24:11 +0200 >Subject: [PATCH 21/51] s4-dsdb: Some minor fixes in tombstone_reanimate, to > make it work with acl > >Change-Id: Idad221c7ecf778fd24f6017bb4c6eacac541086a >Signed-off-by: Nadezhda Ivanova <nivanova@symas.com> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 2aa2e9afa2fa77480abe43ce51f818c5885c08ff) >--- > source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c | 8 ++++---- > 1 file changed, 4 insertions(+), 4 deletions(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index c175a0c..6ff9800 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -138,7 +138,7 @@ static int _tr_do_rename(struct ldb_module *module, struct ldb_request *parent_r > return ret; > } > >- ret = dsdb_request_add_controls(req, DSDB_SEARCH_SHOW_DELETED); >+ ret = ldb_request_add_control(req, LDB_CONTROL_SHOW_DELETED_OID, false, NULL); > if (ret != LDB_SUCCESS) { > talloc_free(tmp_ctx); > return ret; >@@ -155,7 +155,7 @@ static int _tr_do_rename(struct ldb_module *module, struct ldb_request *parent_r > * Run request from the top module > * so we get show_deleted control OID resolved > */ >- ret = ldb_request(ldb_module_get_ctx(module), req); >+ ret = ldb_next_request(module, req); > if (ret == LDB_SUCCESS) { > ret = ldb_wait(req->handle, LDB_WAIT_ALL); > } >@@ -263,7 +263,7 @@ static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_requ > ret = _tr_do_rename(module, req, req->op.mod.message->dn, dn_new); > if (ret != LDB_SUCCESS) { > ldb_debug(ldb, LDB_DEBUG_ERROR, "Renaming object to %s has failed with %s\n", el_dn->values[0].data, ldb_strerror(ret)); >- if (ret != LDB_ERR_ENTRY_ALREADY_EXISTS) { >+ if (ret != LDB_ERR_ENTRY_ALREADY_EXISTS && ret != LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS ) { > /* Windows returns Operations Error in case we can't rename the object */ > return LDB_ERR_OPERATIONS_ERROR; > } >@@ -298,7 +298,7 @@ static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_requ > } > ret = _tr_do_modify(module, req, msg); > if (ret != LDB_SUCCESS) { >- return ldb_operr(ldb); >+ return ret; > } > > return ldb_module_done(ac->req, NULL, NULL, LDB_SUCCESS); >-- >2.1.4 > > >From 3441e0597ac50796e1d51edef7d7fd4dc0ba2629 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Wed, 5 Nov 2014 06:26:25 +0100 >Subject: [PATCH 22/51] s4-dsdb-test: Implement samdb_connect_env() to rely > solely on environment > >this is to help me port Python tests to be more Unit test alike >and remove all global handling >Starting from a new test suite - tombstone_reanimation.py > >Andrew Bartlett rose his concerns that passing parameters >through environment may make tests hard to trace for >failures. However, passing parameters on command line >is not Unit test alike either. After discussing this with him >offline, we agreed to continue this approach, but prefix >environment variables with "TEST_". So that an env var >should not be used by coincidence. > >Change-Id: I29445c42cdcafede3897c8dd1f1529222a74afc9 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 599187ead61340d8d3bd3e9db7eab034175bfd7b) >--- > python/samba/tests/__init__.py | 21 +++++++++++++++++++++ > 1 file changed, 21 insertions(+) > >diff --git a/python/samba/tests/__init__.py b/python/samba/tests/__init__.py >index 0e9df6c..3e9348f 100644 >--- a/python/samba/tests/__init__.py >+++ b/python/samba/tests/__init__.py >@@ -23,6 +23,7 @@ import samba > import samba.auth > from samba import param > from samba.samdb import SamDB >+from samba import credentials > import subprocess > import tempfile > >@@ -233,6 +234,26 @@ def connect_samdb_ex(samdb_url, lp=None, session_info=None, credentials=None, > return (sam_db, res[0]) > > >+def connect_samdb_env(env_url, env_username, env_password, lp=None): >+ """Connect to SamDB by getting URL and Credentials from environment >+ >+ :param env_url: Environment variable name to get lsb url from >+ :param env_username: Username environment variable >+ :param env_password: Password environment variable >+ :return: sam_db_connection >+ """ >+ samdb_url = env_get_var_value(env_url) >+ creds = credentials.Credentials() >+ if lp is None: >+ # guess Credentials parameters here. Otherwise workstation >+ # and domain fields are NULL and gencache code segfalts >+ lp = param.LoadParm() >+ creds.guess(lp) >+ creds.set_username(env_get_var_value(env_username)) >+ creds.set_password(env_get_var_value(env_password)) >+ return connect_samdb(samdb_url, credentials=creds, lp=lp) >+ >+ > def delete_force(samdb, dn): > try: > samdb.delete(dn) >-- >2.1.4 > > >From e03a8b362ca4e86e6f10656e70d3e1a32906c6b5 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Wed, 21 Jan 2015 00:58:56 +0200 >Subject: [PATCH 23/51] s4-dsdb-test: Initial implementation for Tombstone > restore test suite > >Change-Id: Ib35ff930b6e7cee14317328b6fe25b59eec5262c >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit ea4786875d90d1865c9e45324319865f513d02aa) >--- > source4/dsdb/tests/python/tombstone_reanimation.py | 285 +++++++++++++++++++++ > 1 file changed, 285 insertions(+) > create mode 100644 source4/dsdb/tests/python/tombstone_reanimation.py > >diff --git a/source4/dsdb/tests/python/tombstone_reanimation.py b/source4/dsdb/tests/python/tombstone_reanimation.py >new file mode 100644 >index 0000000..21df42d >--- /dev/null >+++ b/source4/dsdb/tests/python/tombstone_reanimation.py >@@ -0,0 +1,285 @@ >+#!/usr/bin/env python >+# >+# Tombstone reanimation tests >+# >+# Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2014 >+# >+# This program is free software; you can redistribute it and/or modify >+# it under the terms of the GNU General Public License as published by >+# the Free Software Foundation; either version 3 of the License, or >+# (at your option) any later version. >+# >+# This program is distributed in the hope that it will be useful, >+# but WITHOUT ANY WARRANTY; without even the implied warranty of >+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the >+# GNU General Public License for more details. >+# >+# You should have received a copy of the GNU General Public License >+# along with this program. If not, see <http://www.gnu.org/licenses/>. >+ >+import sys >+import optparse >+import unittest >+ >+sys.path.insert(0, "bin/python") >+import samba >+ >+import samba.tests >+import samba.getopt as options >+from ldb import (SCOPE_BASE, FLAG_MOD_DELETE, FLAG_MOD_REPLACE, Dn, Message, MessageElement) >+ >+ >+class RestoredObjectAttributesBaseTestCase(samba.tests.TestCase): >+ """ verify Samba restores required attributes when >+ user restores a Deleted object >+ """ >+ >+ def setUp(self): >+ super(RestoredObjectAttributesBaseTestCase, self).setUp() >+ # load LoadParm >+ lp = options.SambaOptions(optparse.OptionParser()).get_loadparm() >+ self.samdb = samba.tests.connect_samdb_env("TEST_SERVER", "TEST_USERNAME", "TEST_PASSWORD", lp=lp) >+ self.base_dn = self.samdb.domain_dn() >+ self.schema_dn = self.samdb.get_schema_basedn().get_linearized() >+ # Get the old "dSHeuristics" if it was set >+ self.dsheuristics = self.samdb.get_dsheuristics() >+ # Set the "dSHeuristics" to activate the correct "userPassword" behaviour >+ self.samdb.set_dsheuristics("000000001") >+ # Get the old "minPwdAge" >+ self.minPwdAge = self.samdb.get_minPwdAge() >+ # Set it temporary to "0" >+ self.samdb.set_minPwdAge("0") >+ >+ def tearDown(self): >+ super(RestoredObjectAttributesBaseTestCase, self).tearDown() >+ # Reset the "dSHeuristics" as they were before >+ self.samdb.set_dsheuristics(self.dsheuristics) >+ # Reset the "minPwdAge" as it was before >+ self.samdb.set_minPwdAge(self.minPwdAge) >+ >+ def GUID_string(self, guid): >+ return self.samdb.schema_format_value("objectGUID", guid) >+ >+ def search_guid(self, guid): >+ res = self.samdb.search(base="<GUID=%s>" % self.GUID_string(guid), >+ scope=SCOPE_BASE, controls=["show_deleted:1"]) >+ self.assertEquals(len(res), 1) >+ return res[0] >+ >+ def search_dn(self, dn): >+ res = self.samdb.search(expression="(objectClass=*)", >+ base=dn, >+ scope=SCOPE_BASE, >+ controls=["show_recycled:1"]) >+ self.assertEquals(len(res), 1) >+ return res[0] >+ >+ def _create_object(self, msg): >+ """:param msg: dict with dn and attributes to create an object from""" >+ # delete an object if leftover from previous test >+ samba.tests.delete_force(self.samdb, msg['dn']) >+ self.samdb.add(msg) >+ return self.search_dn(msg['dn']) >+ >+ def assertAttributesEqual(self, obj_orig, attrs_orig, obj_restored, attrs_rest): >+ self.assertSetEqual(attrs_orig, attrs_rest) >+ # remove volatile attributes, they can't be equal >+ attrs_orig -= set(["uSNChanged", "dSCorePropagationData", "whenChanged"]) >+ for attr in attrs_orig: >+ # convert original attr value to ldif >+ orig_val = obj_orig.get(attr) >+ if orig_val is None: >+ continue >+ if not isinstance(orig_val, MessageElement): >+ orig_val = MessageElement(str(orig_val), 0, attr ) >+ m = Message() >+ m.add(orig_val) >+ orig_ldif = self.samdb.write_ldif(m, 0) >+ # convert restored attr value to ldif >+ rest_val = obj_restored.get(attr) >+ self.assertIsNotNone(rest_val) >+ m = Message() >+ if not isinstance(rest_val, MessageElement): >+ rest_val = MessageElement(str(rest_val), 0, attr) >+ m.add(rest_val) >+ rest_ldif = self.samdb.write_ldif(m, 0) >+ # compare generated ldif's >+ self.assertEqual(orig_ldif.lower(), rest_ldif.lower()) >+ >+ @staticmethod >+ def restore_deleted_object(samdb, del_dn, new_dn): >+ """Restores a deleted object >+ :param samdb: SamDB connection to SAM >+ :param del_dn: str Deleted object DN >+ :param new_dn: str Where to restore the object >+ """ >+ msg = Message() >+ msg.dn = Dn(samdb, str(del_dn)) >+ msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") >+ msg["distinguishedName"] = MessageElement([str(new_dn)], FLAG_MOD_REPLACE, "distinguishedName") >+ samdb.modify(msg, ["show_deleted:1"]) >+ >+ >+class RestoreUserObjectTestCase(RestoredObjectAttributesBaseTestCase): >+ """Test cases for delete/reanimate user objects""" >+ >+ def test_restore_user(self): >+ print "Test restored user attributes" >+ username = "restore_user" >+ usr_dn = "cn=%s,cn=users,%s" % (username, self.base_dn) >+ samba.tests.delete_force(self.samdb, usr_dn) >+ self.samdb.add({ >+ "dn": usr_dn, >+ "objectClass": "user", >+ "sAMAccountName": username}) >+ obj = self.search_dn(usr_dn) >+ guid = obj["objectGUID"][0] >+ self.samdb.delete(usr_dn) >+ obj_del = self.search_guid(guid) >+ # restore the user and fetch what's restored >+ self.restore_deleted_object(self.samdb, obj_del.dn, usr_dn) >+ obj_restore = self.search_guid(guid) >+ # check original attributes and restored one are same >+ orig_attrs = set(obj.keys()) >+ # windows restore more attributes that originally we have >+ orig_attrs.update(['adminCount', 'operatorCount', 'lastKnownParent']) >+ rest_attrs = set(obj_restore.keys()) >+ self.assertSetEqual(orig_attrs, rest_attrs) >+ >+ >+class RestoreGroupObjectTestCase(RestoredObjectAttributesBaseTestCase): >+ """Test different scenarios for delete/reanimate group objects""" >+ >+ def _make_object_dn(self, name): >+ return "cn=%s,cn=users,%s" % (name, self.base_dn) >+ >+ def _create_test_user(self, user_name): >+ user_dn = self._make_object_dn(user_name) >+ ldif = { >+ "dn": user_dn, >+ "objectClass": "user", >+ "sAMAccountName": user_name, >+ } >+ # delete an object if leftover from previous test >+ samba.tests.delete_force(self.samdb, user_dn) >+ # finally, create the group >+ self.samdb.add(ldif) >+ return self.search_dn(user_dn) >+ >+ def _create_test_group(self, group_name, members=None): >+ group_dn = self._make_object_dn(group_name) >+ ldif = { >+ "dn": group_dn, >+ "objectClass": "group", >+ "sAMAccountName": group_name, >+ } >+ try: >+ ldif["member"] = [str(usr_dn) for usr_dn in members] >+ except TypeError: >+ pass >+ # delete an object if leftover from previous test >+ samba.tests.delete_force(self.samdb, group_dn) >+ # finally, create the group >+ self.samdb.add(ldif) >+ return self.search_dn(group_dn) >+ >+ def test_plain_group(self): >+ print "Test restored Group attributes" >+ # create test group >+ obj = self._create_test_group("r_group") >+ guid = obj["objectGUID"][0] >+ # delete the group >+ self.samdb.delete(str(obj.dn)) >+ obj_del = self.search_guid(guid) >+ # restore the Group and fetch what's restored >+ self.restore_deleted_object(self.samdb, obj_del.dn, obj.dn) >+ obj_restore = self.search_guid(guid) >+ # check original attributes and restored one are same >+ attr_orig = set(obj.keys()) >+ # windows restore more attributes that originally we have >+ attr_orig.update(['adminCount', 'operatorCount', 'lastKnownParent']) >+ attr_rest = set(obj_restore.keys()) >+ self.assertAttributesEqual(obj, attr_orig, obj_restore, attr_rest) >+ >+ def test_group_with_members(self): >+ print "Test restored Group with members attributes" >+ # create test group >+ usr1 = self._create_test_user("r_user_1") >+ usr2 = self._create_test_user("r_user_2") >+ obj = self._create_test_group("r_group", [usr1.dn, usr2.dn]) >+ guid = obj["objectGUID"][0] >+ # delete the group >+ self.samdb.delete(str(obj.dn)) >+ obj_del = self.search_guid(guid) >+ # restore the Group and fetch what's restored >+ self.restore_deleted_object(self.samdb, obj_del.dn, obj.dn) >+ obj_restore = self.search_guid(guid) >+ # check original attributes and restored one are same >+ attr_orig = set(obj.keys()) >+ # windows restore more attributes that originally we have >+ attr_orig.update(['adminCount', 'operatorCount', 'lastKnownParent']) >+ # and does not restore following attributes >+ attr_orig.remove("member") >+ attr_rest = set(obj_restore.keys()) >+ self.assertAttributesEqual(obj, attr_orig, obj_restore, attr_rest) >+ >+ >+class RestoreContainerObjectTestCase(RestoredObjectAttributesBaseTestCase): >+ """Test different scenarios for delete/reanimate OU/container objects""" >+ >+ def _create_test_ou(self, rdn, name=None, description=None): >+ ou_dn = "OU=%s,%s" % (rdn, self.base_dn) >+ # delete an object if leftover from previous test >+ samba.tests.delete_force(self.samdb, ou_dn) >+ # create ou and return created object >+ self.samdb.create_ou(ou_dn, name=name, description=description) >+ return self.search_dn(ou_dn) >+ >+ def test_ou_with_name_description(self): >+ print "Test OU reanimation" >+ # create OU to test with >+ obj = self._create_test_ou(rdn="r_ou", >+ name="r_ou name", >+ description="r_ou description") >+ guid = obj["objectGUID"][0] >+ # delete the object >+ self.samdb.delete(str(obj.dn)) >+ obj_del = self.search_guid(guid) >+ # restore the Object and fetch what's restored >+ self.restore_deleted_object(self.samdb, obj_del.dn, obj.dn) >+ obj_restore = self.search_guid(guid) >+ # check original attributes and restored one are same >+ attr_orig = set(obj.keys()) >+ attr_rest = set(obj_restore.keys()) >+ # windows restore more attributes that originally we have >+ attr_orig.update(["lastKnownParent"]) >+ # and does not restore following attributes >+ attr_orig -= {"description"} >+ self.assertAttributesEqual(obj, attr_orig, obj_restore, attr_rest) >+ >+ def test_container(self): >+ print "Test Container reanimation" >+ # create test Container >+ obj = self._create_object({ >+ "dn": "CN=r_container,CN=Users,%s" % self.base_dn, >+ "objectClass": "container" >+ }) >+ guid = obj["objectGUID"][0] >+ # delete the object >+ self.samdb.delete(str(obj.dn)) >+ obj_del = self.search_guid(guid) >+ # restore the Object and fetch what's restored >+ self.restore_deleted_object(self.samdb, obj_del.dn, obj.dn) >+ obj_restore = self.search_guid(guid) >+ # check original attributes and restored one are same >+ attr_orig = set(obj.keys()) >+ attr_rest = set(obj_restore.keys()) >+ # windows restore more attributes that originally we have >+ attr_orig.update(["lastKnownParent"]) >+ # and does not restore following attributes >+ attr_orig -= {"showInAdvancedViewOnly"} >+ self.assertAttributesEqual(obj, attr_orig, obj_restore, attr_rest) >+ >+ >+if __name__ == '__main__': >+ unittest.main() >-- >2.1.4 > > >From 9e33b7eaa718ba88ae37c6cfcc68ce968373d732 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Thu, 6 Nov 2014 03:01:54 +0100 >Subject: [PATCH 24/51] s4-dsdb-test: Use case insensitive comparison for DNs > in undelete test > >Change-Id: I4a009bb7ed58ab857ac74a235bb5f580911f0d92 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 647c0ea0177703563c485efd67da6a8bebbea418) >--- > source4/dsdb/tests/python/deletetest.py | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > >diff --git a/source4/dsdb/tests/python/deletetest.py b/source4/dsdb/tests/python/deletetest.py >index 6170509..67b37a7 100755 >--- a/source4/dsdb/tests/python/deletetest.py >+++ b/source4/dsdb/tests/python/deletetest.py >@@ -434,7 +434,7 @@ class BasicUndeleteTests(BaseDeleteTests): > objDeleted1 = self.search_guid(guid1) > self.undelete_deleted(str(objDeleted1.dn), usr1, ldb) > objLive2 = self.search_dn(usr1) >- self.assertEqual(str(objLive2.dn),str(objLive1.dn)) >+ self.assertEqual(str(objLive2.dn).lower(),str(objLive1.dn).lower()) > delete_force(self.ldb, usr1) > > def test_rename(self): >-- >2.1.4 > > >From 7c6dc078c1e8fff75dd941ab4269fdc89eda3015 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Thu, 6 Nov 2014 04:10:42 +0100 >Subject: [PATCH 25/51] s4-dsdb-test: Fix Undelete tests after subunit upgrade > work > >Change-Id: I4712a2a2163a57fde037511afcc1cb7bee05f12e >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 4944e73d537199208e9895e818ff3233223da5d7) >--- > source4/dsdb/tests/python/deletetest.py | 62 ++++++++++++++++----------------- > 1 file changed, 31 insertions(+), 31 deletions(-) > >diff --git a/source4/dsdb/tests/python/deletetest.py b/source4/dsdb/tests/python/deletetest.py >index 67b37a7..60bb689 100755 >--- a/source4/dsdb/tests/python/deletetest.py >+++ b/source4/dsdb/tests/python/deletetest.py >@@ -394,70 +394,70 @@ class BasicUndeleteTests(BaseDeleteTests): > > def enable_recycle_bin(self): > msg = Message() >- msg.dn = Dn(ldb, "") >+ msg.dn = Dn(self.ldb, "") > msg["enableOptionalFeature"] = MessageElement( > "CN=Partitions," + self.configuration_dn + ":766ddcd8-acd0-445e-f3b9-a7f9b6744f2a", > FLAG_MOD_ADD, "enableOptionalFeature") > try: >- ldb.modify(msg) >+ self.ldb.modify(msg) > except LdbError, (num, _): > self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) > > def undelete_deleted(self, olddn, newdn, samldb): > msg = Message() >- msg.dn = Dn(ldb, olddn) >+ msg.dn = Dn(samldb, olddn) > msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") > msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName") >- res = samldb.modify(msg, ["show_deleted:1"]) >+ samldb.modify(msg, ["show_deleted:1"]) > > def undelete_deleted_with_mod(self, olddn, newdn): > msg = Message() >- msg.dn = Dn(ldb, olddn) >+ msg.dn = Dn(self.ldb, olddn) > msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") > msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName") > msg["url"] = MessageElement(["www.samba.org"], FLAG_MOD_REPLACE, "url") >- res = ldb.modify(msg, ["show_deleted:1"]) >+ self.ldb.modify(msg, ["show_deleted:1"]) > > > def test_undelete(self): > print "Testing standard undelete operation" > usr1="cn=testuser,cn=users," + self.base_dn > delete_force(self.ldb, usr1) >- ldb.add({ >+ self.ldb.add({ > "dn": usr1, > "objectclass": "user", > "description": "test user description", > "samaccountname": "testuser"}) > objLive1 = self.search_dn(usr1) > guid1=objLive1["objectGUID"][0] >- ldb.delete(usr1) >+ self.ldb.delete(usr1) > objDeleted1 = self.search_guid(guid1) >- self.undelete_deleted(str(objDeleted1.dn), usr1, ldb) >+ self.undelete_deleted(str(objDeleted1.dn), usr1, self.ldb) > objLive2 = self.search_dn(usr1) > self.assertEqual(str(objLive2.dn).lower(),str(objLive1.dn).lower()) > delete_force(self.ldb, usr1) > >- def test_rename(self): >+ def __test_rename(self): > print "Testing attempt to rename deleted object" > usr1="cn=testuser,cn=users," + self.base_dn >- ldb.add({ >+ self.ldb.add({ > "dn": usr1, > "objectclass": "user", > "description": "test user description", > "samaccountname": "testuser"}) > objLive1 = self.search_dn(usr1) > guid1=objLive1["objectGUID"][0] >- ldb.delete(usr1) >+ self.ldb.delete(usr1) > objDeleted1 = self.search_guid(guid1) > #just to make sure we get the correct error if the show deleted is missing > try: >- ldb.rename(str(objDeleted1.dn), usr1) >+ self.ldb.rename(str(objDeleted1.dn), usr1) > self.fail() > except LdbError, (num, _): > self.assertEquals(num,ERR_NO_SUCH_OBJECT) > > try: >- ldb.rename(str(objDeleted1.dn), usr1, ["show_deleted:1"]) >+ self.ldb.rename(str(objDeleted1.dn), usr1, ["show_deleted:1"]) > self.fail() > except LdbError, (num, _): > self.assertEquals(num,ERR_UNWILLING_TO_PERFORM) >@@ -465,14 +465,14 @@ class BasicUndeleteTests(BaseDeleteTests): > def test_undelete_with_mod(self): > print "Testing standard undelete operation with modification of additional attributes" > usr1="cn=testuser,cn=users," + self.base_dn >- ldb.add({ >+ self.ldb.add({ > "dn": usr1, > "objectclass": "user", > "description": "test user description", > "samaccountname": "testuser"}) > objLive1 = self.search_dn(usr1) > guid1=objLive1["objectGUID"][0] >- ldb.delete(usr1) >+ self.ldb.delete(usr1) > objDeleted1 = self.search_guid(guid1) > self.undelete_deleted_with_mod(str(objDeleted1.dn), usr1) > objLive2 = self.search_dn(usr1) >@@ -484,16 +484,16 @@ class BasicUndeleteTests(BaseDeleteTests): > usr1="cn=testuser,cn=users," + self.base_dn > usr2="cn=testuser2,cn=users," + self.base_dn > delete_force(self.ldb, usr1) >- ldb.add({ >+ self.ldb.add({ > "dn": usr1, > "objectclass": "user", > "description": "test user description", > "samaccountname": "testuser"}) > objLive1 = self.search_dn(usr1) > guid1=objLive1["objectGUID"][0] >- ldb.delete(usr1) >+ self.ldb.delete(usr1) > objDeleted1 = self.search_guid(guid1) >- self.undelete_deleted(str(objDeleted1.dn), usr2, ldb) >+ self.undelete_deleted(str(objDeleted1.dn), usr2, self.ldb) > objLive2 = self.search_dn(usr2) > delete_force(self.ldb, usr1) > delete_force(self.ldb, usr2) >@@ -501,22 +501,22 @@ class BasicUndeleteTests(BaseDeleteTests): > def test_undelete_existing(self): > print "Testing undelete user after a user with the same dn has been created" > usr1="cn=testuser,cn=users," + self.base_dn >- ldb.add({ >+ self.ldb.add({ > "dn": usr1, > "objectclass": "user", > "description": "test user description", > "samaccountname": "testuser"}) > objLive1 = self.search_dn(usr1) > guid1=objLive1["objectGUID"][0] >- ldb.delete(usr1) >- ldb.add({ >+ self.ldb.delete(usr1) >+ self.ldb.add({ > "dn": usr1, > "objectclass": "user", > "description": "test user description", > "samaccountname": "testuser"}) > objDeleted1 = self.search_guid(guid1) > try: >- self.undelete_deleted(str(objDeleted1.dn), usr1, ldb) >+ self.undelete_deleted(str(objDeleted1.dn), usr1, self.ldb) > self.fail() > except LdbError, (num, _): > self.assertEquals(num, ERR_ENTRY_ALREADY_EXISTS) >@@ -527,35 +527,35 @@ class BasicUndeleteTests(BaseDeleteTests): > c2 = "cn=ldaptestcontainer2," + self.configuration_dn > c3 = "cn=ldaptestcontainer," + self.configuration_dn > c4 = "cn=ldaptestcontainer2," + self.base_dn >- ldb.add({ >+ self.ldb.add({ > "dn": c1, > "objectclass": "container"}) >- ldb.add({ >+ self.ldb.add({ > "dn": c2, > "objectclass": "container"}) > objLive1 = self.search_dn(c1) > objLive2 = self.search_dn(c2) > guid1=objLive1["objectGUID"][0] > guid2=objLive2["objectGUID"][0] >- ldb.delete(c1) >- ldb.delete(c2) >+ self.ldb.delete(c1) >+ self.ldb.delete(c2) > objDeleted1 = self.search_guid(guid1) > objDeleted2 = self.search_guid(guid2) > #try to undelete from base dn to config > try: >- self.undelete_deleted(str(objDeleted1.dn), c3, ldb) >+ self.undelete_deleted(str(objDeleted1.dn), c3, self.ldb) > self.fail() > except LdbError, (num, _): > self.assertEquals(num, ERR_OPERATIONS_ERROR) > #try to undelete from config to base dn > try: >- self.undelete_deleted(str(objDeleted2.dn), c4, ldb) >+ self.undelete_deleted(str(objDeleted2.dn), c4, self.ldb) > self.fail() > except LdbError, (num, _): > self.assertEquals(num, ERR_OPERATIONS_ERROR) > #assert undeletion will work in same nc >- self.undelete_deleted(str(objDeleted1.dn), c4, ldb) >- self.undelete_deleted(str(objDeleted2.dn), c3, ldb) >+ self.undelete_deleted(str(objDeleted1.dn), c4, self.ldb) >+ self.undelete_deleted(str(objDeleted2.dn), c3, self.ldb) > delete_force(self.ldb, c3) > delete_force(self.ldb, c4) > >-- >2.1.4 > > >From 377a244eea684f2c23b368b7699ea48b075ac258 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Fri, 7 Nov 2014 07:02:51 +0100 >Subject: [PATCH 26/51] s4-dsdb-util: Mark attributes with ADD flag in > samdb_find_or_add_attribute() > >At the moment no flags are set and it works fine, since this function >is solely used in samldb during ADD requests handling. >Pre-setting a flag make it usefull for other modules and request >handlers too > >Change-Id: I7e43dcbe2a8f34e3b0ec16ae2db80ef436df8bfe >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 8e10c10bd6e601df47a2815c638482e486646f59) >--- > source4/dsdb/common/util.c | 8 +++++++- > 1 file changed, 7 insertions(+), 1 deletion(-) > >diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c >index 7e5e5b8..5e8fa06 100644 >--- a/source4/dsdb/common/util.c >+++ b/source4/dsdb/common/util.c >@@ -774,6 +774,7 @@ struct ldb_message_element *samdb_find_attribute(struct ldb_context *ldb, > > int samdb_find_or_add_attribute(struct ldb_context *ldb, struct ldb_message *msg, const char *name, const char *set_value) > { >+ int ret; > struct ldb_message_element *el; > > el = ldb_msg_find_element(msg, name); >@@ -781,7 +782,12 @@ int samdb_find_or_add_attribute(struct ldb_context *ldb, struct ldb_message *msg > return LDB_SUCCESS; > } > >- return ldb_msg_add_string(msg, name, set_value); >+ ret = ldb_msg_add_string(msg, name, set_value); >+ if (ret != LDB_SUCCESS) { >+ return ret; >+ } >+ msg->elements[msg->num_elements - 1].flags = LDB_FLAG_MOD_ADD; >+ return LDB_SUCCESS; > } > > /* >-- >2.1.4 > > >From 52108facd22588f4484d9bf1e4d8b24c31aa4255 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Fri, 7 Nov 2014 07:04:30 +0100 >Subject: [PATCH 27/51] s4-dsdb/reanimate: Implement attribute_restore function > >At the moment it works for objects with objectClass user + a common >case of removing isRecycled attribute > >Change-Id: I70b0ef0ef65c13d3def82ca53ace52a85a078a37 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 4acd22508d0b066eee67b778153d82ba4f90be6e) >--- > .../dsdb/samdb/ldb_modules/tombstone_reanimate.c | 90 ++++++++++++++++++++++ > 1 file changed, 90 insertions(+) > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index 6ff9800..51660bb 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -50,6 +50,7 @@ > #include "param/param.h" > #include "../libds/common/flags.h" > #include "dsdb/samdb/ldb_modules/util.h" >+#include "libds/common/flag_mapping.h" > > struct tr_context { > >@@ -211,6 +212,95 @@ static int _tr_do_modify(struct ldb_module *module, struct ldb_request *parent_r > return ret; > } > >+static int _tr_restore_attributes(struct ldb_context *ldb, struct ldb_message *cur_msg, struct ldb_message *new_msg) >+{ >+ int ret; >+ struct ldb_message_element *el; >+ uint32_t account_type, user_account_control; >+ >+ >+ /* remove isRecycled */ >+ ret = ldb_msg_add_empty(new_msg, "isRecycled", LDB_FLAG_MOD_DELETE, NULL); >+ if (ret != LDB_SUCCESS) { >+ ldb_asprintf_errstring(ldb, "Failed to reset isRecycled attribute: %s", ldb_strerror(ret)); >+ return LDB_ERR_OPERATIONS_ERROR; >+ } >+ >+ /* objectClass is USER */ >+ if (samdb_find_attribute(ldb, cur_msg, "objectclass", "user") != NULL) { >+ /* restoring 'user' instance attribute is heavily borrowed from samldb.c */ >+ >+ /* Default values */ >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "accountExpires", "9223372036854775807"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "badPasswordTime", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "badPwdCount", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "codePage", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "countryCode", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "lastLogoff", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "lastLogon", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "logonCount", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "pwdLastSet", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "adminCount", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "operatorCount", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ >+ /* restore "sAMAccountType" */ >+ user_account_control = ldb_msg_find_attr_as_uint(cur_msg, "userAccountControl", (uint32_t)-1); >+ if (user_account_control == (uint32_t)-1) { >+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, >+ "reanimate: No 'userAccountControl' attribute found!"); >+ } >+ account_type = ds_uf2atype(user_account_control); >+ if (account_type == 0) { >+ ldb_set_errstring(ldb, "reanimate: Unrecognized account type!"); >+ return LDB_ERR_UNWILLING_TO_PERFORM; >+ } >+ ret = samdb_msg_add_uint(ldb, new_msg, new_msg, "sAMAccountType", account_type); >+ if (ret != LDB_SUCCESS) { >+ return ret; >+ } >+ el = ldb_msg_find_element(new_msg, "sAMAccountType"); >+ el->flags = LDB_FLAG_MOD_REPLACE; >+ >+ /* "userAccountControl" -> "primaryGroupID" mapping */ >+ if (!ldb_msg_find_element(new_msg, "primaryGroupID")) { >+ uint32_t rid = ds_uf2prim_group_rid(user_account_control); >+ >+ ret = samdb_msg_add_uint(ldb, new_msg, new_msg, >+ "primaryGroupID", rid); >+ if (ret != LDB_SUCCESS) { >+ return ret; >+ } >+ el = ldb_msg_find_element(new_msg, "primaryGroupID"); >+ el->flags = LDB_FLAG_MOD_REPLACE; >+ } >+ >+ } >+ >+ return LDB_SUCCESS; >+} >+ > /** > * Handle special LDAP modify request to restore deleted objects > */ >-- >2.1.4 > > >From 4bbbc867023fcd84f385c0aa46744a29843ec1e8 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Fri, 7 Nov 2014 07:05:56 +0100 >Subject: [PATCH 28/51] s4-dsdb/samldb: Fix type "omputer" -> "computer" > >Change-Id: Ic56c6945528b7f60becc4f0b318429f4c22c3d2e >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit afd4b23dc938cf5c9f1f0b7e1c642852fbe68ef6) >--- > source4/dsdb/samdb/ldb_modules/samldb.c | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c >index 54e2e5e..1cddcd8 100644 >--- a/source4/dsdb/samdb/ldb_modules/samldb.c >+++ b/source4/dsdb/samdb/ldb_modules/samldb.c >@@ -1706,7 +1706,7 @@ static int samldb_user_account_control_change(struct samldb_ctx *ac) > "lockoutTime", 0); > old_is_critical = ldb_msg_find_attr_as_bool(res->msgs[0], > "isCriticalSystemObject", 0); >- /* When we do not have objectclass "omputer" we cannot switch to a (read-only) DC */ >+ /* When we do not have objectclass "computer" we cannot switch to a (read-only) DC */ > el = ldb_msg_find_element(res->msgs[0], "objectClass"); > if (el == NULL) { > return ldb_operr(ldb); >-- >2.1.4 > > >From 23e0fbcfca49d6d063d89fcbeeee42e65a4f84ac Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Fri, 7 Nov 2014 07:07:07 +0100 >Subject: [PATCH 29/51] s4-dsdb/samldb: Skip 'sAMAccountType' and > 'primaryGroupID' during Tombstone reanimate > >tombstone_reanimate.c module is going to restore those attributes >and it needs a way to propagate them to DB > >Change-Id: I36f30b33fa204fd28329eab01044a125f7a3f08e >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 4c5c7d3c1c09835729404c13961572a9cb4be16c) >--- > source4/dsdb/samdb/ldb_modules/samldb.c | 34 ++++++++++++++++++++++----------- > 1 file changed, 23 insertions(+), 11 deletions(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c >index 1cddcd8..a72b909 100644 >--- a/source4/dsdb/samdb/ldb_modules/samldb.c >+++ b/source4/dsdb/samdb/ldb_modules/samldb.c >@@ -2632,6 +2632,7 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req) > struct ldb_context *ldb; > struct samldb_ctx *ac; > struct ldb_message_element *el, *el2; >+ struct ldb_control *is_undelete; > bool modified = false; > int ret; > >@@ -2642,6 +2643,13 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req) > > ldb = ldb_module_get_ctx(module); > >+ /* >+ * we are going to need some special handling if in Undelete call. >+ * Since tombstone_reanimate module will restore certain attributes, >+ * we need to relax checks for: sAMAccountType, primaryGroupID >+ */ >+ is_undelete = ldb_request_get_control(req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID); >+ > /* make sure that "objectSid" is not specified */ > el = ldb_msg_find_element(req->op.mod.message, "objectSid"); > if (el != NULL) { >@@ -2651,12 +2659,14 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req) > return LDB_ERR_UNWILLING_TO_PERFORM; > } > } >- /* make sure that "sAMAccountType" is not specified */ >- el = ldb_msg_find_element(req->op.mod.message, "sAMAccountType"); >- if (el != NULL) { >- ldb_set_errstring(ldb, >- "samldb: sAMAccountType must not be specified!"); >- return LDB_ERR_UNWILLING_TO_PERFORM; >+ if (is_undelete == NULL) { >+ /* make sure that "sAMAccountType" is not specified */ >+ el = ldb_msg_find_element(req->op.mod.message, "sAMAccountType"); >+ if (el != NULL) { >+ ldb_set_errstring(ldb, >+ "samldb: sAMAccountType must not be specified!"); >+ return LDB_ERR_UNWILLING_TO_PERFORM; >+ } > } > /* make sure that "isCriticalSystemObject" is not specified */ > el = ldb_msg_find_element(req->op.mod.message, "isCriticalSystemObject"); >@@ -2700,11 +2710,13 @@ static int samldb_modify(struct ldb_module *module, struct ldb_request *req) > return ldb_operr(ldb); > } > >- el = ldb_msg_find_element(ac->msg, "primaryGroupID"); >- if (el != NULL) { >- ret = samldb_prim_group_trigger(ac); >- if (ret != LDB_SUCCESS) { >- return ret; >+ if (is_undelete == NULL) { >+ el = ldb_msg_find_element(ac->msg, "primaryGroupID"); >+ if (el != NULL) { >+ ret = samldb_prim_group_trigger(ac); >+ if (ret != LDB_SUCCESS) { >+ return ret; >+ } > } > } > >-- >2.1.4 > > >From f4a2aa99a3c372e3ead9d9f94ec4ccc224d59bde Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Fri, 7 Nov 2014 07:08:29 +0100 >Subject: [PATCH 30/51] s4-dsdb/reanimate: Use 'show deleted' control in modify > operations too > >Before committing changes, object is still deleted - isDeleted = true > >Change-Id: Ie1ab53dc594d1bfaf5b9e06316e7a1fc0dd4b8cb >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 72c55980e3adf1f47cf973c8c1a3f87e98121276) >--- > source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c | 7 +++++++ > 1 file changed, 7 insertions(+) > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index 51660bb..070d952 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -195,6 +195,13 @@ static int _tr_do_modify(struct ldb_module *module, struct ldb_request *parent_r > return ret; > } > >+ /* We need this since object is 'delete' atm */ >+ ret = ldb_request_add_control(mod_req, LDB_CONTROL_SHOW_DELETED_OID, false, NULL); >+ if (ret != LDB_SUCCESS) { >+ talloc_free(tmp_ctx); >+ return ret; >+ } >+ > /* mark request as part of Tombstone reanimation */ > ret = ldb_request_add_control(mod_req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID, false, NULL); > if (ret != LDB_SUCCESS) { >-- >2.1.4 > > >From 2d291515290ae27412ae7d8dc6f9d73dcf974988 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Fri, 7 Nov 2014 07:11:59 +0100 >Subject: [PATCH 31/51] s4-dsdb/reanimate: Swap rename->modify operations to > modify->rename sequence > >This way it is more visible that we work on 'deleted object' during modify >and also will help us to handle 'stop rename for deletec objects' >propertly in future > >[MS-ADTS]: 3.1.1.5.3.7.3 Undelete Processing Specifics > >Change-Id: I9bb644e099a4a2afcb261ad22515c9c4ce4875bb >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit d5fc8b080fe47bf6f93de136788d56d51c526cb4) >--- > .../dsdb/samdb/ldb_modules/tombstone_reanimate.c | 38 +++++++++++++--------- > 1 file changed, 22 insertions(+), 16 deletions(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index 070d952..825bbf1 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -352,30 +352,21 @@ static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_requ > } > > /* Simple implementation */ >- /* Rename request to modify distinguishedName */ >- dn_new = ldb_dn_from_ldb_val(req, ldb, &el_dn->values[0]); >- if (dn_new == NULL) { >- return ldb_oom(ldb); >- } >- ret = _tr_do_rename(module, req, req->op.mod.message->dn, dn_new); >- if (ret != LDB_SUCCESS) { >- ldb_debug(ldb, LDB_DEBUG_ERROR, "Renaming object to %s has failed with %s\n", el_dn->values[0].data, ldb_strerror(ret)); >- if (ret != LDB_ERR_ENTRY_ALREADY_EXISTS && ret != LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS ) { >- /* Windows returns Operations Error in case we can't rename the object */ >- return LDB_ERR_OPERATIONS_ERROR; >- } >- return ret; >- } > > /* Modify request to: */ > msg = ldb_msg_copy_shallow(ac, req->op.mod.message); > if (msg == NULL) { > return ldb_module_oom(ac->module); > } >- msg->dn = dn_new; >- /* - delete isDeleted */ >+ /* - remove distinguishedName - we don't need it */ > ldb_msg_remove_attr(msg, "distinguishedName"); > >+ /* restore attributed depending on objectClass */ >+ ret = _tr_restore_attributes(ldb, res_obj->msgs[0], msg); >+ if (ret != LDB_SUCCESS) { >+ return ret; >+ } >+ > /* - restore objectCategory if not present */ > objectcategory = ldb_msg_find_attr_as_dn(ldb, ac, msg, > "objectCategory"); >@@ -398,6 +389,21 @@ static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_requ > return ret; > } > >+ /* Rename request to modify distinguishedName */ >+ dn_new = ldb_dn_from_ldb_val(req, ldb, &el_dn->values[0]); >+ if (dn_new == NULL) { >+ return ldb_oom(ldb); >+ } >+ ret = _tr_do_rename(module, req, req->op.mod.message->dn, dn_new); >+ if (ret != LDB_SUCCESS) { >+ ldb_debug(ldb, LDB_DEBUG_ERROR, "Renaming object to %s has failed with %s\n", el_dn->values[0].data, ldb_strerror(ret)); >+ if (ret != LDB_ERR_ENTRY_ALREADY_EXISTS && ret != LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS ) { >+ /* Windows returns Operations Error in case we can't rename the object */ >+ return LDB_ERR_OPERATIONS_ERROR; >+ } >+ return ret; >+ } >+ > return ldb_module_done(ac->req, NULL, NULL, LDB_SUCCESS); > } > >-- >2.1.4 > > >From 1c0d1778bec230d55adb8dad3e7cbf9a5d2f7348 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Thu, 13 Nov 2014 04:11:08 +0100 >Subject: [PATCH 32/51] s4-dsdb/reanimate: Group objects reanimation > implementation > >Change-Id: Iea92924ff6b33fa3723b104d5dfff1ce5a7a09b0 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 70c03fa7a86be3653e936e259c7850bcd522d22a) >--- > .../dsdb/samdb/ldb_modules/tombstone_reanimate.c | 36 ++++++++++++++++++++++ > 1 file changed, 36 insertions(+) > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index 825bbf1..8b31579 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -302,7 +302,43 @@ static int _tr_restore_attributes(struct ldb_context *ldb, struct ldb_message *c > el = ldb_msg_find_element(new_msg, "primaryGroupID"); > el->flags = LDB_FLAG_MOD_REPLACE; > } >+ } >+ >+ /* objectClass is GROUP */ >+ if (samdb_find_attribute(ldb, cur_msg, "objectclass", "group") != NULL) { >+ /* "groupType" -> "sAMAccountType" */ >+ uint32_t group_type; >+ >+ el = ldb_msg_find_element(cur_msg, "groupType"); >+ if (el == NULL) { >+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, >+ "reanimate: Unexpected: missing groupType attribute."); >+ } >+ >+ group_type = ldb_msg_find_attr_as_uint(cur_msg, >+ "groupType", 0); >+ >+ account_type = ds_gtype2atype(group_type); >+ if (account_type == 0) { >+ return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM, >+ "reanimate: Unrecognized account type!"); >+ } >+ ret = samdb_msg_add_uint(ldb, new_msg, new_msg, >+ "sAMAccountType", account_type); >+ if (ret != LDB_SUCCESS) { >+ return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, >+ "reanimate: Failed to add sAMAccountType to restored object."); >+ } >+ el = ldb_msg_find_element(new_msg, "sAMAccountType"); >+ el->flags = LDB_FLAG_MOD_REPLACE; > >+ /* Default values set by Windows */ >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "adminCount", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, new_msg, >+ "operatorCount", "0"); >+ if (ret != LDB_SUCCESS) return ret; > } > > return LDB_SUCCESS; >-- >2.1.4 > > >From 3fd98307215bcb88a3c5bf88b654a022f3bd0e43 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sun, 16 Nov 2014 03:34:22 +0100 >Subject: [PATCH 33/51] s4-dsdb-test: remove trailing ';' in ldap.py > >Change-Id: I5edc6e017b576791c1575f71a625c49ccc88fe8f >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit e80bba721fcff03ec8f2740c82ab5d88b473aae1) >--- > source4/dsdb/tests/python/ldap.py | 64 +++++++++++++++++++-------------------- > 1 file changed, 32 insertions(+), 32 deletions(-) > >diff --git a/source4/dsdb/tests/python/ldap.py b/source4/dsdb/tests/python/ldap.py >index dc12fea..63eb9a5 100755 >--- a/source4/dsdb/tests/python/ldap.py >+++ b/source4/dsdb/tests/python/ldap.py >@@ -1153,7 +1153,7 @@ class BasicTests(samba.tests.TestCase): > # what the rDN length constraints are > def DISABLED_test_largeRDN(self): > """Testing large rDN (limit 64 characters)""" >- rdn = "CN=a012345678901234567890123456789012345678901234567890123456789012"; >+ rdn = "CN=a012345678901234567890123456789012345678901234567890123456789012" > delete_force(self.ldb, "%s,%s" % (rdn, self.base_dn)) > ldif = """ > dn: %s,%s""" % (rdn,self.base_dn) + """ >@@ -1162,7 +1162,7 @@ objectClass: container > self.ldb.add_ldif(ldif) > delete_force(self.ldb, "%s,%s" % (rdn, self.base_dn)) > >- rdn = "CN=a0123456789012345678901234567890123456789012345678901234567890120"; >+ rdn = "CN=a0123456789012345678901234567890123456789012345678901234567890120" > delete_force(self.ldb, "%s,%s" % (rdn, self.base_dn)) > try: > ldif = """ >@@ -1338,20 +1338,20 @@ objectGUID: bd3480c9-58af-4cd8-92df-bc4a18b6e44d > self.ldb.add({ > "dn": "cn=parentguidtest,cn=users," + self.base_dn, > "objectclass":"user", >- "samaccountname":"parentguidtest"}); >+ "samaccountname":"parentguidtest"}) > res1 = ldb.search(base="cn=parentguidtest,cn=users," + self.base_dn, scope=SCOPE_BASE, >- attrs=["parentGUID", "samaccountname"]); >+ attrs=["parentGUID", "samaccountname"]) > res2 = ldb.search(base="cn=users," + self.base_dn,scope=SCOPE_BASE, >- attrs=["objectGUID"]); >+ attrs=["objectGUID"]) > res3 = ldb.search(base=self.base_dn, scope=SCOPE_BASE, >- attrs=["parentGUID"]); >+ attrs=["parentGUID"]) > res4 = ldb.search(base=self.configuration_dn, scope=SCOPE_BASE, >- attrs=["parentGUID"]); >+ attrs=["parentGUID"]) > res5 = ldb.search(base=self.schema_dn, scope=SCOPE_BASE, >- attrs=["parentGUID"]); >+ attrs=["parentGUID"]) > > """Check if the parentGUID is valid """ >- self.assertEquals(res1[0]["parentGUID"], res2[0]["objectGUID"]); >+ self.assertEquals(res1[0]["parentGUID"], res2[0]["objectGUID"]) > > """Check if it returns nothing when there is no parent object - default NC""" > has_parentGUID = False >@@ -1359,7 +1359,7 @@ objectGUID: bd3480c9-58af-4cd8-92df-bc4a18b6e44d > if key == "parentGUID": > has_parentGUID = True > break >- self.assertFalse(has_parentGUID); >+ self.assertFalse(has_parentGUID) > > """Check if it returns nothing when there is no parent object - configuration NC""" > has_parentGUID = False >@@ -1367,7 +1367,7 @@ objectGUID: bd3480c9-58af-4cd8-92df-bc4a18b6e44d > if key == "parentGUID": > has_parentGUID = True > break >- self.assertFalse(has_parentGUID); >+ self.assertFalse(has_parentGUID) > > """Check if it returns nothing when there is no parent object - schema NC""" > has_parentGUID = False >@@ -1375,7 +1375,7 @@ objectGUID: bd3480c9-58af-4cd8-92df-bc4a18b6e44d > if key == "parentGUID": > has_parentGUID = True > break >- self.assertFalse(has_parentGUID); >+ self.assertFalse(has_parentGUID) > > """Ensures that if you look for another object attribute after the constructed > parentGUID, it will return correctly""" >@@ -1386,21 +1386,21 @@ objectGUID: bd3480c9-58af-4cd8-92df-bc4a18b6e44d > break > self.assertTrue(has_another_attribute) > self.assertTrue(len(res1[0]["samaccountname"]) == 1) >- self.assertEquals(res1[0]["samaccountname"][0], "parentguidtest"); >+ self.assertEquals(res1[0]["samaccountname"][0], "parentguidtest") > > # Testing parentGUID behaviour on rename\ > > self.ldb.add({ > "dn": "cn=testotherusers," + self.base_dn, >- "objectclass":"container"}); >+ "objectclass":"container"}) > res1 = ldb.search(base="cn=testotherusers," + self.base_dn,scope=SCOPE_BASE, >- attrs=["objectGUID"]); >+ attrs=["objectGUID"]) > ldb.rename("cn=parentguidtest,cn=users," + self.base_dn, >- "cn=parentguidtest,cn=testotherusers," + self.base_dn); >+ "cn=parentguidtest,cn=testotherusers," + self.base_dn) > res2 = ldb.search(base="cn=parentguidtest,cn=testotherusers," + self.base_dn, > scope=SCOPE_BASE, >- attrs=["parentGUID"]); >- self.assertEquals(res1[0]["objectGUID"], res2[0]["parentGUID"]); >+ attrs=["parentGUID"]) >+ self.assertEquals(res1[0]["objectGUID"], res2[0]["parentGUID"]) > > delete_force(self.ldb, "cn=parentguidtest,cn=testotherusers," + self.base_dn) > delete_force(self.ldb, "cn=testotherusers," + self.base_dn) >@@ -1563,10 +1563,10 @@ delete: description > """Test groupType (int32) behaviour (should appear to be casted to a 32 bit signed integer before comparsion)""" > > res1 = ldb.search(base=self.base_dn, scope=SCOPE_SUBTREE, >- attrs=["groupType"], expression="groupType=2147483653"); >+ attrs=["groupType"], expression="groupType=2147483653") > > res2 = ldb.search(base=self.base_dn, scope=SCOPE_SUBTREE, >- attrs=["groupType"], expression="groupType=-2147483643"); >+ attrs=["groupType"], expression="groupType=-2147483643") > > self.assertEquals(len(res1), len(res2)) > >@@ -1742,23 +1742,23 @@ delete: description > }) > > # Testing ldb.search for (&(cn=ldaptestcomputer3)(objectClass=user)) >- res = ldb.search(self.base_dn, expression="(&(cn=ldaptestcomputer3)(objectClass=user))"); >+ res = ldb.search(self.base_dn, expression="(&(cn=ldaptestcomputer3)(objectClass=user))") > self.assertEquals(len(res), 1, "Found only %d for (&(cn=ldaptestcomputer3)(objectClass=user))" % len(res)) > >- self.assertEquals(str(res[0].dn), ("CN=ldaptestcomputer3,CN=Computers," + self.base_dn)); >- self.assertEquals(res[0]["cn"][0], "ldaptestcomputer3"); >- self.assertEquals(res[0]["name"][0], "ldaptestcomputer3"); >- self.assertEquals(res[0]["objectClass"][0], "top"); >- self.assertEquals(res[0]["objectClass"][1], "person"); >- self.assertEquals(res[0]["objectClass"][2], "organizationalPerson"); >- self.assertEquals(res[0]["objectClass"][3], "user"); >- self.assertEquals(res[0]["objectClass"][4], "computer"); >+ self.assertEquals(str(res[0].dn), ("CN=ldaptestcomputer3,CN=Computers," + self.base_dn)) >+ self.assertEquals(res[0]["cn"][0], "ldaptestcomputer3") >+ self.assertEquals(res[0]["name"][0], "ldaptestcomputer3") >+ self.assertEquals(res[0]["objectClass"][0], "top") >+ self.assertEquals(res[0]["objectClass"][1], "person") >+ self.assertEquals(res[0]["objectClass"][2], "organizationalPerson") >+ self.assertEquals(res[0]["objectClass"][3], "user") >+ self.assertEquals(res[0]["objectClass"][4], "computer") > self.assertTrue("objectGUID" in res[0]) > self.assertTrue("whenCreated" in res[0]) > self.assertEquals(res[0]["objectCategory"][0], ("CN=Computer,%s" % ldb.get_schema_basedn())) >- self.assertEquals(int(res[0]["primaryGroupID"][0]), 513); >- self.assertEquals(int(res[0]["sAMAccountType"][0]), ATYPE_NORMAL_ACCOUNT); >- self.assertEquals(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE); >+ self.assertEquals(int(res[0]["primaryGroupID"][0]), 513) >+ self.assertEquals(int(res[0]["sAMAccountType"][0]), ATYPE_NORMAL_ACCOUNT) >+ self.assertEquals(int(res[0]["userAccountControl"][0]), UF_NORMAL_ACCOUNT | UF_PASSWD_NOTREQD | UF_ACCOUNTDISABLE) > > delete_force(self.ldb, "cn=ldaptestcomputer3,cn=computers," + self.base_dn) > >-- >2.1.4 > > >From 1edcfab95dfd999cf1dca10b0dc6e46600329366 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sun, 16 Nov 2014 03:35:01 +0100 >Subject: [PATCH 34/51] s4-dsdb/objectclass: remove duplicated declaration for > objectclass_do_add > >Change-Id: Ib88a45cea64fb661a41ca3b4a3df9dabf509fc6c >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit add32d85750700aa6e4766a3a3067d7f3a6a02a2) >--- > source4/dsdb/samdb/ldb_modules/objectclass.c | 2 -- > 1 file changed, 2 deletions(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/objectclass.c b/source4/dsdb/samdb/ldb_modules/objectclass.c >index 8c361e9..bceeda9 100644 >--- a/source4/dsdb/samdb/ldb_modules/objectclass.c >+++ b/source4/dsdb/samdb/ldb_modules/objectclass.c >@@ -246,8 +246,6 @@ static int fix_dn(struct ldb_context *ldb, > } > > >-static int objectclass_do_add(struct oc_context *ac); >- > static int objectclass_add(struct ldb_module *module, struct ldb_request *req) > { > struct ldb_context *ldb; >-- >2.1.4 > > >From dbc683f1b9a4dce99de9f70a02c508b8d8778c71 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Fri, 21 Nov 2014 19:31:25 +0100 >Subject: [PATCH 35/51] s4-dsdb-test: Fix duplicated key in a dictionary in > sam.py > >Change-Id: Ie33d92bd308262d9bfda553d6d5e2cfd98f6d7b3 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 98750442a396368df262218d343c439afdda01e2) >--- > source4/dsdb/tests/python/sam.py | 4 ++-- > 1 file changed, 2 insertions(+), 2 deletions(-) > >diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py >index 31b5a39..44295d3 100755 >--- a/source4/dsdb/tests/python/sam.py >+++ b/source4/dsdb/tests/python/sam.py >@@ -2608,9 +2608,9 @@ class SamTests(samba.tests.TestCase): > > self.ldb.add({ > "dn": "cn=ldaptestgroup,cn=users," + self.base_dn, >- "description": "desc2", > "objectclass": "group", >- "description": "desc1"}) >+ "description": "desc1" >+ }) > > res = ldb.search("cn=ldaptestgroup,cn=users," + self.base_dn, > scope=SCOPE_BASE, attrs=["description"]) >-- >2.1.4 > > >From abfa00283d606690ae43027d181320c4a01572a9 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Wed, 26 Nov 2014 06:23:51 +0100 >Subject: [PATCH 36/51] s4-dsdb-tests: Move base tests for Tombstone > reanimation in tombstone_reanimation module > >So we have them all in one place. > >While moving, I have: >* inherited from the base class for Tombstone reanimations >* replace self.ldb with self.samdb > >Change-Id: Id3e4f02cc2e0877d736da812c14c91e2311203d2 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit a72e6287e5bc7cc48f8d8ea13333271fe8e28494) >--- > source4/dsdb/tests/python/deletetest.py | 173 -------------------- > source4/dsdb/tests/python/tombstone_reanimation.py | 180 ++++++++++++++++++++- > 2 files changed, 179 insertions(+), 174 deletions(-) > >diff --git a/source4/dsdb/tests/python/deletetest.py b/source4/dsdb/tests/python/deletetest.py >index 60bb689..6044c9f 100755 >--- a/source4/dsdb/tests/python/deletetest.py >+++ b/source4/dsdb/tests/python/deletetest.py >@@ -387,179 +387,6 @@ class BasicDeleteTests(BaseDeleteTests): > self.assertFalse("CN=Deleted Objects" in str(objDeleted6.dn)) > self.assertFalse("CN=Deleted Objects" in str(objDeleted7.dn)) > >-class BasicUndeleteTests(BaseDeleteTests): >- >- def setUp(self): >- super(BasicUndeleteTests, self).setUp() >- >- def enable_recycle_bin(self): >- msg = Message() >- msg.dn = Dn(self.ldb, "") >- msg["enableOptionalFeature"] = MessageElement( >- "CN=Partitions," + self.configuration_dn + ":766ddcd8-acd0-445e-f3b9-a7f9b6744f2a", >- FLAG_MOD_ADD, "enableOptionalFeature") >- try: >- self.ldb.modify(msg) >- except LdbError, (num, _): >- self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) >- >- def undelete_deleted(self, olddn, newdn, samldb): >- msg = Message() >- msg.dn = Dn(samldb, olddn) >- msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") >- msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName") >- samldb.modify(msg, ["show_deleted:1"]) >- >- def undelete_deleted_with_mod(self, olddn, newdn): >- msg = Message() >- msg.dn = Dn(self.ldb, olddn) >- msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") >- msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName") >- msg["url"] = MessageElement(["www.samba.org"], FLAG_MOD_REPLACE, "url") >- self.ldb.modify(msg, ["show_deleted:1"]) >- >- >- def test_undelete(self): >- print "Testing standard undelete operation" >- usr1="cn=testuser,cn=users," + self.base_dn >- delete_force(self.ldb, usr1) >- self.ldb.add({ >- "dn": usr1, >- "objectclass": "user", >- "description": "test user description", >- "samaccountname": "testuser"}) >- objLive1 = self.search_dn(usr1) >- guid1=objLive1["objectGUID"][0] >- self.ldb.delete(usr1) >- objDeleted1 = self.search_guid(guid1) >- self.undelete_deleted(str(objDeleted1.dn), usr1, self.ldb) >- objLive2 = self.search_dn(usr1) >- self.assertEqual(str(objLive2.dn).lower(),str(objLive1.dn).lower()) >- delete_force(self.ldb, usr1) >- >- def __test_rename(self): >- print "Testing attempt to rename deleted object" >- usr1="cn=testuser,cn=users," + self.base_dn >- self.ldb.add({ >- "dn": usr1, >- "objectclass": "user", >- "description": "test user description", >- "samaccountname": "testuser"}) >- objLive1 = self.search_dn(usr1) >- guid1=objLive1["objectGUID"][0] >- self.ldb.delete(usr1) >- objDeleted1 = self.search_guid(guid1) >- #just to make sure we get the correct error if the show deleted is missing >- try: >- self.ldb.rename(str(objDeleted1.dn), usr1) >- self.fail() >- except LdbError, (num, _): >- self.assertEquals(num,ERR_NO_SUCH_OBJECT) >- >- try: >- self.ldb.rename(str(objDeleted1.dn), usr1, ["show_deleted:1"]) >- self.fail() >- except LdbError, (num, _): >- self.assertEquals(num,ERR_UNWILLING_TO_PERFORM) >- >- def test_undelete_with_mod(self): >- print "Testing standard undelete operation with modification of additional attributes" >- usr1="cn=testuser,cn=users," + self.base_dn >- self.ldb.add({ >- "dn": usr1, >- "objectclass": "user", >- "description": "test user description", >- "samaccountname": "testuser"}) >- objLive1 = self.search_dn(usr1) >- guid1=objLive1["objectGUID"][0] >- self.ldb.delete(usr1) >- objDeleted1 = self.search_guid(guid1) >- self.undelete_deleted_with_mod(str(objDeleted1.dn), usr1) >- objLive2 = self.search_dn(usr1) >- self.assertEqual(objLive2["url"][0],"www.samba.org") >- delete_force(self.ldb, usr1) >- >- def test_undelete_newuser(self): >- print "Testing undelete user with a different dn" >- usr1="cn=testuser,cn=users," + self.base_dn >- usr2="cn=testuser2,cn=users," + self.base_dn >- delete_force(self.ldb, usr1) >- self.ldb.add({ >- "dn": usr1, >- "objectclass": "user", >- "description": "test user description", >- "samaccountname": "testuser"}) >- objLive1 = self.search_dn(usr1) >- guid1=objLive1["objectGUID"][0] >- self.ldb.delete(usr1) >- objDeleted1 = self.search_guid(guid1) >- self.undelete_deleted(str(objDeleted1.dn), usr2, self.ldb) >- objLive2 = self.search_dn(usr2) >- delete_force(self.ldb, usr1) >- delete_force(self.ldb, usr2) >- >- def test_undelete_existing(self): >- print "Testing undelete user after a user with the same dn has been created" >- usr1="cn=testuser,cn=users," + self.base_dn >- self.ldb.add({ >- "dn": usr1, >- "objectclass": "user", >- "description": "test user description", >- "samaccountname": "testuser"}) >- objLive1 = self.search_dn(usr1) >- guid1=objLive1["objectGUID"][0] >- self.ldb.delete(usr1) >- self.ldb.add({ >- "dn": usr1, >- "objectclass": "user", >- "description": "test user description", >- "samaccountname": "testuser"}) >- objDeleted1 = self.search_guid(guid1) >- try: >- self.undelete_deleted(str(objDeleted1.dn), usr1, self.ldb) >- self.fail() >- except LdbError, (num, _): >- self.assertEquals(num, ERR_ENTRY_ALREADY_EXISTS) >- >- def test_undelete_cross_nc(self): >- print "Cross NC undelete" >- c1 = "cn=ldaptestcontainer," + self.base_dn >- c2 = "cn=ldaptestcontainer2," + self.configuration_dn >- c3 = "cn=ldaptestcontainer," + self.configuration_dn >- c4 = "cn=ldaptestcontainer2," + self.base_dn >- self.ldb.add({ >- "dn": c1, >- "objectclass": "container"}) >- self.ldb.add({ >- "dn": c2, >- "objectclass": "container"}) >- objLive1 = self.search_dn(c1) >- objLive2 = self.search_dn(c2) >- guid1=objLive1["objectGUID"][0] >- guid2=objLive2["objectGUID"][0] >- self.ldb.delete(c1) >- self.ldb.delete(c2) >- objDeleted1 = self.search_guid(guid1) >- objDeleted2 = self.search_guid(guid2) >- #try to undelete from base dn to config >- try: >- self.undelete_deleted(str(objDeleted1.dn), c3, self.ldb) >- self.fail() >- except LdbError, (num, _): >- self.assertEquals(num, ERR_OPERATIONS_ERROR) >- #try to undelete from config to base dn >- try: >- self.undelete_deleted(str(objDeleted2.dn), c4, self.ldb) >- self.fail() >- except LdbError, (num, _): >- self.assertEquals(num, ERR_OPERATIONS_ERROR) >- #assert undeletion will work in same nc >- self.undelete_deleted(str(objDeleted1.dn), c4, self.ldb) >- self.undelete_deleted(str(objDeleted2.dn), c3, self.ldb) >- delete_force(self.ldb, c3) >- delete_force(self.ldb, c4) >- >- > > if not "://" in host: > if os.path.isfile(host): >diff --git a/source4/dsdb/tests/python/tombstone_reanimation.py b/source4/dsdb/tests/python/tombstone_reanimation.py >index 21df42d..4bc1525 100644 >--- a/source4/dsdb/tests/python/tombstone_reanimation.py >+++ b/source4/dsdb/tests/python/tombstone_reanimation.py >@@ -3,6 +3,7 @@ > # Tombstone reanimation tests > # > # Copyright (C) Kamen Mazdrashki <kamenim@samba.org> 2014 >+# Copyright (C) Nadezhda Ivanova <nivanova@symas.com> 2014 > # > # This program is free software; you can redistribute it and/or modify > # it under the terms of the GNU General Public License as published by >@@ -26,7 +27,10 @@ import samba > > import samba.tests > import samba.getopt as options >-from ldb import (SCOPE_BASE, FLAG_MOD_DELETE, FLAG_MOD_REPLACE, Dn, Message, MessageElement) >+from ldb import (SCOPE_BASE, FLAG_MOD_ADD, FLAG_MOD_DELETE, FLAG_MOD_REPLACE, Dn, Message, >+ MessageElement, LdbError, >+ ERR_ATTRIBUTE_OR_VALUE_EXISTS, ERR_NO_SUCH_OBJECT, ERR_ENTRY_ALREADY_EXISTS, >+ ERR_OPERATIONS_ERROR, ERR_UNWILLING_TO_PERFORM) > > > class RestoredObjectAttributesBaseTestCase(samba.tests.TestCase): >@@ -41,6 +45,7 @@ class RestoredObjectAttributesBaseTestCase(samba.tests.TestCase): > self.samdb = samba.tests.connect_samdb_env("TEST_SERVER", "TEST_USERNAME", "TEST_PASSWORD", lp=lp) > self.base_dn = self.samdb.domain_dn() > self.schema_dn = self.samdb.get_schema_basedn().get_linearized() >+ self.configuration_dn = self.samdb.get_config_basedn().get_linearized() > # Get the old "dSHeuristics" if it was set > self.dsheuristics = self.samdb.get_dsheuristics() > # Set the "dSHeuristics" to activate the correct "userPassword" behaviour >@@ -120,6 +125,179 @@ class RestoredObjectAttributesBaseTestCase(samba.tests.TestCase): > samdb.modify(msg, ["show_deleted:1"]) > > >+class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): >+ >+ def setUp(self): >+ super(BaseRestoreObjectTestCase, self).setUp() >+ >+ def enable_recycle_bin(self): >+ msg = Message() >+ msg.dn = Dn(self.samdb, "") >+ msg["enableOptionalFeature"] = MessageElement( >+ "CN=Partitions," + self.configuration_dn + ":766ddcd8-acd0-445e-f3b9-a7f9b6744f2a", >+ FLAG_MOD_ADD, "enableOptionalFeature") >+ try: >+ self.samdb.modify(msg) >+ except LdbError, (num, _): >+ self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) >+ >+ def undelete_deleted(self, olddn, newdn, samldb): >+ msg = Message() >+ msg.dn = Dn(samldb, olddn) >+ msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") >+ msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName") >+ samldb.modify(msg, ["show_deleted:1"]) >+ >+ def undelete_deleted_with_mod(self, olddn, newdn): >+ msg = Message() >+ msg.dn = Dn(self.samdb, olddn) >+ msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") >+ msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName") >+ msg["url"] = MessageElement(["www.samba.org"], FLAG_MOD_REPLACE, "url") >+ self.samdb.modify(msg, ["show_deleted:1"]) >+ >+ >+ def test_undelete(self): >+ print "Testing standard undelete operation" >+ usr1="cn=testuser,cn=users," + self.base_dn >+ samba.tests.delete_force(self.samdb, usr1) >+ self.samdb.add({ >+ "dn": usr1, >+ "objectclass": "user", >+ "description": "test user description", >+ "samaccountname": "testuser"}) >+ objLive1 = self.search_dn(usr1) >+ guid1=objLive1["objectGUID"][0] >+ self.samdb.delete(usr1) >+ objDeleted1 = self.search_guid(guid1) >+ self.undelete_deleted(str(objDeleted1.dn), usr1, self.samdb) >+ objLive2 = self.search_dn(usr1) >+ self.assertEqual(str(objLive2.dn).lower(),str(objLive1.dn).lower()) >+ samba.tests.delete_force(self.samdb, usr1) >+ >+ def test_rename(self): >+ print "Testing attempt to rename deleted object" >+ usr1="cn=testuser,cn=users," + self.base_dn >+ self.samdb.add({ >+ "dn": usr1, >+ "objectclass": "user", >+ "description": "test user description", >+ "samaccountname": "testuser"}) >+ objLive1 = self.search_dn(usr1) >+ guid1=objLive1["objectGUID"][0] >+ self.samdb.delete(usr1) >+ objDeleted1 = self.search_guid(guid1) >+ #just to make sure we get the correct error if the show deleted is missing >+ try: >+ self.samdb.rename(str(objDeleted1.dn), usr1) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num,ERR_NO_SUCH_OBJECT) >+ >+ try: >+ self.samdb.rename(str(objDeleted1.dn), usr1, ["show_deleted:1"]) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num, ERR_UNWILLING_TO_PERFORM) >+ >+ def test_undelete_with_mod(self): >+ print "Testing standard undelete operation with modification of additional attributes" >+ usr1="cn=testuser,cn=users," + self.base_dn >+ self.samdb.add({ >+ "dn": usr1, >+ "objectclass": "user", >+ "description": "test user description", >+ "samaccountname": "testuser"}) >+ objLive1 = self.search_dn(usr1) >+ guid1=objLive1["objectGUID"][0] >+ self.samdb.delete(usr1) >+ objDeleted1 = self.search_guid(guid1) >+ self.undelete_deleted_with_mod(str(objDeleted1.dn), usr1) >+ objLive2 = self.search_dn(usr1) >+ self.assertEqual(objLive2["url"][0],"www.samba.org") >+ samba.tests.delete_force(self.samdb, usr1) >+ >+ def test_undelete_newuser(self): >+ print "Testing undelete user with a different dn" >+ usr1="cn=testuser,cn=users," + self.base_dn >+ usr2="cn=testuser2,cn=users," + self.base_dn >+ samba.tests.delete_force(self.samdb, usr1) >+ self.samdb.add({ >+ "dn": usr1, >+ "objectclass": "user", >+ "description": "test user description", >+ "samaccountname": "testuser"}) >+ objLive1 = self.search_dn(usr1) >+ guid1=objLive1["objectGUID"][0] >+ self.samdb.delete(usr1) >+ objDeleted1 = self.search_guid(guid1) >+ self.undelete_deleted(str(objDeleted1.dn), usr2, self.samdb) >+ objLive2 = self.search_dn(usr2) >+ samba.tests.delete_force(self.samdb, usr1) >+ samba.tests.delete_force(self.samdb, usr2) >+ >+ def test_undelete_existing(self): >+ print "Testing undelete user after a user with the same dn has been created" >+ usr1="cn=testuser,cn=users," + self.base_dn >+ self.samdb.add({ >+ "dn": usr1, >+ "objectclass": "user", >+ "description": "test user description", >+ "samaccountname": "testuser"}) >+ objLive1 = self.search_dn(usr1) >+ guid1=objLive1["objectGUID"][0] >+ self.samdb.delete(usr1) >+ self.samdb.add({ >+ "dn": usr1, >+ "objectclass": "user", >+ "description": "test user description", >+ "samaccountname": "testuser"}) >+ objDeleted1 = self.search_guid(guid1) >+ try: >+ self.undelete_deleted(str(objDeleted1.dn), usr1, self.samdb) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num, ERR_ENTRY_ALREADY_EXISTS) >+ >+ def test_undelete_cross_nc(self): >+ print "Cross NC undelete" >+ c1 = "cn=ldaptestcontainer," + self.base_dn >+ c2 = "cn=ldaptestcontainer2," + self.configuration_dn >+ c3 = "cn=ldaptestcontainer," + self.configuration_dn >+ c4 = "cn=ldaptestcontainer2," + self.base_dn >+ self.samdb.add({ >+ "dn": c1, >+ "objectclass": "container"}) >+ self.samdb.add({ >+ "dn": c2, >+ "objectclass": "container"}) >+ objLive1 = self.search_dn(c1) >+ objLive2 = self.search_dn(c2) >+ guid1=objLive1["objectGUID"][0] >+ guid2=objLive2["objectGUID"][0] >+ self.samdb.delete(c1) >+ self.samdb.delete(c2) >+ objDeleted1 = self.search_guid(guid1) >+ objDeleted2 = self.search_guid(guid2) >+ #try to undelete from base dn to config >+ try: >+ self.undelete_deleted(str(objDeleted1.dn), c3, self.samdb) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num, ERR_OPERATIONS_ERROR) >+ #try to undelete from config to base dn >+ try: >+ self.undelete_deleted(str(objDeleted2.dn), c4, self.samdb) >+ self.fail() >+ except LdbError, (num, _): >+ self.assertEquals(num, ERR_OPERATIONS_ERROR) >+ #assert undeletion will work in same nc >+ self.undelete_deleted(str(objDeleted1.dn), c4, self.samdb) >+ self.undelete_deleted(str(objDeleted2.dn), c3, self.samdb) >+ samba.tests.delete_force(self.samdb, c3) >+ samba.tests.delete_force(self.samdb, c4) >+ >+ > class RestoreUserObjectTestCase(RestoredObjectAttributesBaseTestCase): > """Test cases for delete/reanimate user objects""" > >-- >2.1.4 > > >From ee0a0697150eb4ac990846074a70a1c4570f4108 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Wed, 26 Nov 2014 06:59:09 +0100 >Subject: [PATCH 37/51] s4-dsdb-test/reanimate: Fix whitespaces according to > PEP8 > >Change-Id: I7b46992c80178d40a0531b5afd71a7783068a9dd >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 84b897aec40af3c33b0d1dac16060ddc4a8dbee0) >--- > source4/dsdb/tests/python/tombstone_reanimation.py | 39 +++++++++++----------- > 1 file changed, 19 insertions(+), 20 deletions(-) > >diff --git a/source4/dsdb/tests/python/tombstone_reanimation.py b/source4/dsdb/tests/python/tombstone_reanimation.py >index 4bc1525..faa78bb 100644 >--- a/source4/dsdb/tests/python/tombstone_reanimation.py >+++ b/source4/dsdb/tests/python/tombstone_reanimation.py >@@ -126,7 +126,6 @@ class RestoredObjectAttributesBaseTestCase(samba.tests.TestCase): > > > class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): >- > def setUp(self): > super(BaseRestoreObjectTestCase, self).setUp() > >@@ -134,7 +133,7 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > msg = Message() > msg.dn = Dn(self.samdb, "") > msg["enableOptionalFeature"] = MessageElement( >- "CN=Partitions," + self.configuration_dn + ":766ddcd8-acd0-445e-f3b9-a7f9b6744f2a", >+ "CN=Partitions," + self.configuration_dn + ":766ddcd8-acd0-445e-f3b9-a7f9b6744f2a", > FLAG_MOD_ADD, "enableOptionalFeature") > try: > self.samdb.modify(msg) >@@ -159,7 +158,7 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > > def test_undelete(self): > print "Testing standard undelete operation" >- usr1="cn=testuser,cn=users," + self.base_dn >+ usr1 = "cn=testuser,cn=users," + self.base_dn > samba.tests.delete_force(self.samdb, usr1) > self.samdb.add({ > "dn": usr1, >@@ -167,32 +166,32 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > "description": "test user description", > "samaccountname": "testuser"}) > objLive1 = self.search_dn(usr1) >- guid1=objLive1["objectGUID"][0] >+ guid1 = objLive1["objectGUID"][0] > self.samdb.delete(usr1) > objDeleted1 = self.search_guid(guid1) > self.undelete_deleted(str(objDeleted1.dn), usr1, self.samdb) > objLive2 = self.search_dn(usr1) >- self.assertEqual(str(objLive2.dn).lower(),str(objLive1.dn).lower()) >+ self.assertEqual(str(objLive2.dn).lower(), str(objLive1.dn).lower()) > samba.tests.delete_force(self.samdb, usr1) > > def test_rename(self): > print "Testing attempt to rename deleted object" >- usr1="cn=testuser,cn=users," + self.base_dn >+ usr1 = "cn=testuser,cn=users," + self.base_dn > self.samdb.add({ > "dn": usr1, > "objectclass": "user", > "description": "test user description", > "samaccountname": "testuser"}) > objLive1 = self.search_dn(usr1) >- guid1=objLive1["objectGUID"][0] >+ guid1 = objLive1["objectGUID"][0] > self.samdb.delete(usr1) > objDeleted1 = self.search_guid(guid1) >- #just to make sure we get the correct error if the show deleted is missing >+ # just to make sure we get the correct error if the show deleted is missing > try: > self.samdb.rename(str(objDeleted1.dn), usr1) > self.fail() > except LdbError, (num, _): >- self.assertEquals(num,ERR_NO_SUCH_OBJECT) >+ self.assertEquals(num, ERR_NO_SUCH_OBJECT) > > try: > self.samdb.rename(str(objDeleted1.dn), usr1, ["show_deleted:1"]) >@@ -202,25 +201,25 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > > def test_undelete_with_mod(self): > print "Testing standard undelete operation with modification of additional attributes" >- usr1="cn=testuser,cn=users," + self.base_dn >+ usr1 = "cn=testuser,cn=users," + self.base_dn > self.samdb.add({ > "dn": usr1, > "objectclass": "user", > "description": "test user description", > "samaccountname": "testuser"}) > objLive1 = self.search_dn(usr1) >- guid1=objLive1["objectGUID"][0] >+ guid1 = objLive1["objectGUID"][0] > self.samdb.delete(usr1) > objDeleted1 = self.search_guid(guid1) > self.undelete_deleted_with_mod(str(objDeleted1.dn), usr1) > objLive2 = self.search_dn(usr1) >- self.assertEqual(objLive2["url"][0],"www.samba.org") >+ self.assertEqual(objLive2["url"][0], "www.samba.org") > samba.tests.delete_force(self.samdb, usr1) > > def test_undelete_newuser(self): > print "Testing undelete user with a different dn" >- usr1="cn=testuser,cn=users," + self.base_dn >- usr2="cn=testuser2,cn=users," + self.base_dn >+ usr1 = "cn=testuser,cn=users," + self.base_dn >+ usr2 = "cn=testuser2,cn=users," + self.base_dn > samba.tests.delete_force(self.samdb, usr1) > self.samdb.add({ > "dn": usr1, >@@ -228,7 +227,7 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > "description": "test user description", > "samaccountname": "testuser"}) > objLive1 = self.search_dn(usr1) >- guid1=objLive1["objectGUID"][0] >+ guid1 = objLive1["objectGUID"][0] > self.samdb.delete(usr1) > objDeleted1 = self.search_guid(guid1) > self.undelete_deleted(str(objDeleted1.dn), usr2, self.samdb) >@@ -238,14 +237,14 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > > def test_undelete_existing(self): > print "Testing undelete user after a user with the same dn has been created" >- usr1="cn=testuser,cn=users," + self.base_dn >+ usr1 = "cn=testuser,cn=users," + self.base_dn > self.samdb.add({ > "dn": usr1, > "objectclass": "user", > "description": "test user description", > "samaccountname": "testuser"}) > objLive1 = self.search_dn(usr1) >- guid1=objLive1["objectGUID"][0] >+ guid1 = objLive1["objectGUID"][0] > self.samdb.delete(usr1) > self.samdb.add({ > "dn": usr1, >@@ -273,13 +272,13 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > "objectclass": "container"}) > objLive1 = self.search_dn(c1) > objLive2 = self.search_dn(c2) >- guid1=objLive1["objectGUID"][0] >- guid2=objLive2["objectGUID"][0] >+ guid1 = objLive1["objectGUID"][0] >+ guid2 = objLive2["objectGUID"][0] > self.samdb.delete(c1) > self.samdb.delete(c2) > objDeleted1 = self.search_guid(guid1) > objDeleted2 = self.search_guid(guid2) >- #try to undelete from base dn to config >+ # try to undelete from base dn to config > try: > self.undelete_deleted(str(objDeleted1.dn), c3, self.samdb) > self.fail() >-- >2.1.4 > > >From 9d7f14f24b26e84e2c092150e5cb582e48e5ac82 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Wed, 26 Nov 2014 21:53:53 +0100 >Subject: [PATCH 38/51] s4-dsdb/samdb: Don't relax contraint checking during > rename for Deleted objects > >Now we have a module to handle to handle Tombstone reanimation >and it is better we do all the check here as usual > >Change-Id: Ia5d28d64e99f7a961cfe8b9aa7cc96e4ca56192e >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit e30be9a948241c7c42a7d0f8f4610489910987da) >--- > source4/dsdb/samdb/ldb_modules/samldb.c | 3 --- > 1 file changed, 3 deletions(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c >index a72b909..5dda641 100644 >--- a/source4/dsdb/samdb/ldb_modules/samldb.c >+++ b/source4/dsdb/samdb/ldb_modules/samldb.c >@@ -2924,9 +2924,6 @@ static int check_rename_constraints(struct ldb_message *msg, > if (ldb_request_get_control(ac->req, LDB_CONTROL_RELAX_OID) != NULL) { > return LDB_SUCCESS; > } >- if (ldb_msg_find_attr_as_bool(msg, "isDeleted", false)) { >- return LDB_SUCCESS; >- } > > /* Objects under CN=System */ > >-- >2.1.4 > > >From 6491de7565ae09070a42fea4b9910764b95730a7 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Thu, 27 Nov 2014 05:15:58 +0100 >Subject: [PATCH 39/51] s4-dsdb/samldb: Relax a bit restrictions in Config > partition while restoring deleted object > >Change-Id: Iead460d24058b160b46cf3ddedaf4d84b844da4d >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit ac2931628cb79543b8ed96b4522bff8958541bd5) >--- > source4/dsdb/samdb/ldb_modules/samldb.c | 3 ++- > 1 file changed, 2 insertions(+), 1 deletion(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c >index 5dda641..dfaa21c 100644 >--- a/source4/dsdb/samdb/ldb_modules/samldb.c >+++ b/source4/dsdb/samdb/ldb_modules/samldb.c >@@ -3020,7 +3020,8 @@ static int check_rename_constraints(struct ldb_message *msg, > talloc_free(dn2); > } > >- if (!limited_move) { >+ if (!limited_move >+ && ldb_request_get_control(ac->req, DSDB_CONTROL_RESTORE_TOMBSTONE_OID) == NULL) { > ldb_asprintf_errstring(ldb, > "subtree_rename: Cannot move %s to %s in config partition", > ldb_dn_get_linearized(olddn), ldb_dn_get_linearized(newdn)); >-- >2.1.4 > > >From 4eb92e1f4992f2c5ffda4d3eb11e0ff4e3163766 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Thu, 27 Nov 2014 05:20:22 +0100 >Subject: [PATCH 40/51] s4-dsdb/test: Delete any leftover objects in the > beginning of Cross-NC test > >This way we ensure that samdb is clean before we make the test > >Change-Id: I3c6fc94763807394e52b6df41548e9aba8b452c1 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit b4ccfbc214a52b2d8d3747614e445bccfac9a63b) >--- > source4/dsdb/tests/python/tombstone_reanimation.py | 6 ++++-- > 1 file changed, 4 insertions(+), 2 deletions(-) > >diff --git a/source4/dsdb/tests/python/tombstone_reanimation.py b/source4/dsdb/tests/python/tombstone_reanimation.py >index faa78bb..c2ec2b8 100644 >--- a/source4/dsdb/tests/python/tombstone_reanimation.py >+++ b/source4/dsdb/tests/python/tombstone_reanimation.py >@@ -264,6 +264,10 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > c2 = "cn=ldaptestcontainer2," + self.configuration_dn > c3 = "cn=ldaptestcontainer," + self.configuration_dn > c4 = "cn=ldaptestcontainer2," + self.base_dn >+ samba.tests.delete_force(self.samdb, c1) >+ samba.tests.delete_force(self.samdb, c2) >+ samba.tests.delete_force(self.samdb, c3) >+ samba.tests.delete_force(self.samdb, c4) > self.samdb.add({ > "dn": c1, > "objectclass": "container"}) >@@ -293,8 +297,6 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > #assert undeletion will work in same nc > self.undelete_deleted(str(objDeleted1.dn), c4, self.samdb) > self.undelete_deleted(str(objDeleted2.dn), c3, self.samdb) >- samba.tests.delete_force(self.samdb, c3) >- samba.tests.delete_force(self.samdb, c4) > > > class RestoreUserObjectTestCase(RestoredObjectAttributesBaseTestCase): >-- >2.1.4 > > >From 1c31b64aa15db9bf2c67506ee0e567f6f7a01336 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Thu, 27 Nov 2014 06:20:33 +0100 >Subject: [PATCH 41/51] s4-dsdb/samldb: Don't allow rename requests on Deleted > object > >Windows behavior in case of renaming Deleted object is: >* return ERR_NO_SUCH_OBJECT in case client is not providing > SHOW_DELETED control >* ERR_UNWILLING_TO_PERFORM otherwise > >Renaming of Deleted objects is allowed only through special >Tombstone reanimation modify request > >Change-Id: I1eb33fc294a5de44917f6037988ea6362e6e21fc >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit db993c0de4eeb391b68288b5d4909080dac23b26) >--- > source4/dsdb/samdb/ldb_modules/samldb.c | 11 +++++++++++ > 1 file changed, 11 insertions(+) > >diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c >index dfaa21c..3e8922c 100644 >--- a/source4/dsdb/samdb/ldb_modules/samldb.c >+++ b/source4/dsdb/samdb/ldb_modules/samldb.c >@@ -2925,6 +2925,17 @@ static int check_rename_constraints(struct ldb_message *msg, > return LDB_SUCCESS; > } > >+ if (ldb_msg_find_attr_as_bool(msg, "isDeleted", false)) { >+ /* >+ * check originating request if we are supposed >+ * to "see" this record in first place. >+ */ >+ if (ldb_request_get_control(ac->req, LDB_CONTROL_SHOW_DELETED_OID) == NULL) { >+ return LDB_ERR_NO_SUCH_OBJECT; >+ } >+ return LDB_ERR_UNWILLING_TO_PERFORM; >+ } >+ > /* Objects under CN=System */ > > dn1 = ldb_dn_copy(ac, ldb_get_default_basedn(ldb)); >-- >2.1.4 > > >From a4ccc21716da20a7f329656120eec1e5e9e99a48 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Thu, 27 Nov 2014 17:49:15 +0100 >Subject: [PATCH 42/51] s4-dsdb-test: Use common base method for restoring > Deleted objects > >Change-Id: I266b58ced814cf7ea3616862506df5b55f4f1d8c >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 2ad50f8842c33fb90570e469dfb54df2bff1195c) >--- > source4/dsdb/tests/python/tombstone_reanimation.py | 39 ++++++++-------------- > 1 file changed, 14 insertions(+), 25 deletions(-) > >diff --git a/source4/dsdb/tests/python/tombstone_reanimation.py b/source4/dsdb/tests/python/tombstone_reanimation.py >index c2ec2b8..807cfee 100644 >--- a/source4/dsdb/tests/python/tombstone_reanimation.py >+++ b/source4/dsdb/tests/python/tombstone_reanimation.py >@@ -112,16 +112,21 @@ class RestoredObjectAttributesBaseTestCase(samba.tests.TestCase): > self.assertEqual(orig_ldif.lower(), rest_ldif.lower()) > > @staticmethod >- def restore_deleted_object(samdb, del_dn, new_dn): >+ def restore_deleted_object(samdb, del_dn, new_dn, new_attrs=None): > """Restores a deleted object > :param samdb: SamDB connection to SAM > :param del_dn: str Deleted object DN > :param new_dn: str Where to restore the object >+ :param new_attrs: dict Additional attributes to set > """ > msg = Message() > msg.dn = Dn(samdb, str(del_dn)) > msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") > msg["distinguishedName"] = MessageElement([str(new_dn)], FLAG_MOD_REPLACE, "distinguishedName") >+ if new_attrs is not None: >+ assert isinstance(new_attrs, dict) >+ for attr in new_attrs: >+ msg[attr] = MessageElement(new_attrs[attr], FLAG_MOD_REPLACE, attr) > samdb.modify(msg, ["show_deleted:1"]) > > >@@ -140,22 +145,6 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > except LdbError, (num, _): > self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS) > >- def undelete_deleted(self, olddn, newdn, samldb): >- msg = Message() >- msg.dn = Dn(samldb, olddn) >- msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") >- msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName") >- samldb.modify(msg, ["show_deleted:1"]) >- >- def undelete_deleted_with_mod(self, olddn, newdn): >- msg = Message() >- msg.dn = Dn(self.samdb, olddn) >- msg["isDeleted"] = MessageElement([], FLAG_MOD_DELETE, "isDeleted") >- msg["distinguishedName"] = MessageElement([newdn], FLAG_MOD_REPLACE, "distinguishedName") >- msg["url"] = MessageElement(["www.samba.org"], FLAG_MOD_REPLACE, "url") >- self.samdb.modify(msg, ["show_deleted:1"]) >- >- > def test_undelete(self): > print "Testing standard undelete operation" > usr1 = "cn=testuser,cn=users," + self.base_dn >@@ -169,7 +158,7 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > guid1 = objLive1["objectGUID"][0] > self.samdb.delete(usr1) > objDeleted1 = self.search_guid(guid1) >- self.undelete_deleted(str(objDeleted1.dn), usr1, self.samdb) >+ self.restore_deleted_object(self.samdb, objDeleted1.dn, usr1) > objLive2 = self.search_dn(usr1) > self.assertEqual(str(objLive2.dn).lower(), str(objLive1.dn).lower()) > samba.tests.delete_force(self.samdb, usr1) >@@ -211,7 +200,7 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > guid1 = objLive1["objectGUID"][0] > self.samdb.delete(usr1) > objDeleted1 = self.search_guid(guid1) >- self.undelete_deleted_with_mod(str(objDeleted1.dn), usr1) >+ self.restore_deleted_object(self.samdb, objDeleted1.dn, usr1, {"url": "www.samba.org"}) > objLive2 = self.search_dn(usr1) > self.assertEqual(objLive2["url"][0], "www.samba.org") > samba.tests.delete_force(self.samdb, usr1) >@@ -230,7 +219,7 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > guid1 = objLive1["objectGUID"][0] > self.samdb.delete(usr1) > objDeleted1 = self.search_guid(guid1) >- self.undelete_deleted(str(objDeleted1.dn), usr2, self.samdb) >+ self.restore_deleted_object(self.samdb, objDeleted1.dn, usr2) > objLive2 = self.search_dn(usr2) > samba.tests.delete_force(self.samdb, usr1) > samba.tests.delete_force(self.samdb, usr2) >@@ -253,7 +242,7 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > "samaccountname": "testuser"}) > objDeleted1 = self.search_guid(guid1) > try: >- self.undelete_deleted(str(objDeleted1.dn), usr1, self.samdb) >+ self.restore_deleted_object(self.samdb, objDeleted1.dn, usr1) > self.fail() > except LdbError, (num, _): > self.assertEquals(num, ERR_ENTRY_ALREADY_EXISTS) >@@ -284,19 +273,19 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > objDeleted2 = self.search_guid(guid2) > # try to undelete from base dn to config > try: >- self.undelete_deleted(str(objDeleted1.dn), c3, self.samdb) >+ self.restore_deleted_object(self.samdb, objDeleted1.dn, c3) > self.fail() > except LdbError, (num, _): > self.assertEquals(num, ERR_OPERATIONS_ERROR) > #try to undelete from config to base dn > try: >- self.undelete_deleted(str(objDeleted2.dn), c4, self.samdb) >+ self.restore_deleted_object(self.samdb, objDeleted2.dn, c4) > self.fail() > except LdbError, (num, _): > self.assertEquals(num, ERR_OPERATIONS_ERROR) > #assert undeletion will work in same nc >- self.undelete_deleted(str(objDeleted1.dn), c4, self.samdb) >- self.undelete_deleted(str(objDeleted2.dn), c3, self.samdb) >+ self.restore_deleted_object(self.samdb, objDeleted1.dn, c4) >+ self.restore_deleted_object(self.samdb, objDeleted2.dn, c3) > > > class RestoreUserObjectTestCase(RestoredObjectAttributesBaseTestCase): >-- >2.1.4 > > >From 6e7d443ede52d064dc102f15cc56615282cb2133 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sun, 28 Dec 2014 04:23:33 +0200 >Subject: [PATCH 43/51] s4-dsdb/tests: Do not pre-create LoadParm - > connect_samdb_env() will handle it > >Change-Id: I3483c5aa50de2f7aca19e4d7cc4fa49bbe5f889d >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 459a7c7de6eeb536684d801b79e3022fc20bdd4a) >--- > source4/dsdb/tests/python/tombstone_reanimation.py | 6 +----- > 1 file changed, 1 insertion(+), 5 deletions(-) > >diff --git a/source4/dsdb/tests/python/tombstone_reanimation.py b/source4/dsdb/tests/python/tombstone_reanimation.py >index 807cfee..c049661 100644 >--- a/source4/dsdb/tests/python/tombstone_reanimation.py >+++ b/source4/dsdb/tests/python/tombstone_reanimation.py >@@ -19,14 +19,12 @@ > # along with this program. If not, see <http://www.gnu.org/licenses/>. > > import sys >-import optparse > import unittest > > sys.path.insert(0, "bin/python") > import samba > > import samba.tests >-import samba.getopt as options > from ldb import (SCOPE_BASE, FLAG_MOD_ADD, FLAG_MOD_DELETE, FLAG_MOD_REPLACE, Dn, Message, > MessageElement, LdbError, > ERR_ATTRIBUTE_OR_VALUE_EXISTS, ERR_NO_SUCH_OBJECT, ERR_ENTRY_ALREADY_EXISTS, >@@ -40,9 +38,7 @@ class RestoredObjectAttributesBaseTestCase(samba.tests.TestCase): > > def setUp(self): > super(RestoredObjectAttributesBaseTestCase, self).setUp() >- # load LoadParm >- lp = options.SambaOptions(optparse.OptionParser()).get_loadparm() >- self.samdb = samba.tests.connect_samdb_env("TEST_SERVER", "TEST_USERNAME", "TEST_PASSWORD", lp=lp) >+ self.samdb = samba.tests.connect_samdb_env("TEST_SERVER", "TEST_USERNAME", "TEST_PASSWORD") > self.base_dn = self.samdb.domain_dn() > self.schema_dn = self.samdb.get_schema_basedn().get_linearized() > self.configuration_dn = self.samdb.get_config_basedn().get_linearized() >-- >2.1.4 > > >From 8719f51addabcda86e293efa340ef75aef80be8d Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Wed, 21 Jan 2015 01:03:13 +0200 >Subject: [PATCH 44/51] s4-tests: Add tombstone_reanimation test case to s4 > test suite > >DC, USERNAME and PASSWORD are passed as environment variables >prefixed with TEST_ > >Change-Id: I84ff628496bfa3e0538011400328585d080f21b8 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit de42cdd305c68a7389525d245a01205469d3cf9b) >--- > source4/selftest/tests.py | 5 +++++ > 1 file changed, 5 insertions(+) > >diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py >index 159714d..dabd581 100755 >--- a/source4/selftest/tests.py >+++ b/source4/selftest/tests.py >@@ -483,6 +483,11 @@ for env in ["dc", "fl2000dc", "fl2003dc", "fl2008r2dc"]: > # therefore skip it in that configuration > plantestsuite_loadlist("samba4.ldap.passwords.python(%s)" % env, env, [python, os.path.join(samba4srcdir, "dsdb/tests/python/passwords.py"), "$SERVER", '-U"$USERNAME%$PASSWORD"', "-W$DOMAIN", '$LOADLIST', '$LISTOPT']) > plantestsuite_loadlist("samba4.ldap.password_lockout.python(%s)" % env, env, [python, os.path.join(samba4srcdir, "dsdb/tests/python/password_lockout.py"), "$SERVER", '-U"$USERNAME%$PASSWORD"', "-W$DOMAIN", "--realm=$REALM", '$LOADLIST', '$LISTOPT']) >+ planoldpythontestsuite(env, "tombstone_reanimation", >+ name="samba4.tombstone_reanimation.python", >+ environ={'TEST_SERVER': '$SERVER', 'TEST_USERNAME': '$USERNAME', 'TEST_PASSWORD': '$PASSWORD'}, >+ extra_path=[os.path.join(samba4srcdir, 'dsdb/tests/python')] >+ ) > > planpythontestsuite("dc:local", "samba.tests.upgradeprovisionneeddc") > planpythontestsuite("plugin_s4_dc:local", "samba.tests.posixacl") >-- >2.1.4 > > >From fc501ee96aef7535e0bcb041546535ec408b99c8 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Mon, 12 Jan 2015 03:30:17 +0200 >Subject: [PATCH 45/51] s4-dsdb: Move User object default attribute values in > separate helper > >Change-Id: I1e291bcf0a5c9b2fca11323dc7f8be29f5145d42 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit c9b0945199080b72ad454d49b310be0b66410124) >--- > source4/dsdb/common/util.c | 42 ++++++++++++++++++++++ > source4/dsdb/samdb/ldb_modules/samldb.c | 27 +------------- > .../dsdb/samdb/ldb_modules/tombstone_reanimate.c | 29 ++------------- > 3 files changed, 46 insertions(+), 52 deletions(-) > >diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c >index 5e8fa06..b0357f0 100644 >--- a/source4/dsdb/common/util.c >+++ b/source4/dsdb/common/util.c >@@ -4844,3 +4844,45 @@ NTSTATUS dsdb_update_bad_pwd_count(TALLOC_CTX *mem_ctx, > *_mod_msg = mod_msg; > return NT_STATUS_OK; > } >+ >+/** >+ * Sets defaults for a User object >+ * List of default attributes set: >+ * accountExpires, badPasswordTime, badPwdCount, >+ * codePage, countryCode, lastLogoff, lastLogon >+ * logonCount, pwdLastSet >+ */ >+int dsdb_user_obj_set_defaults(struct ldb_context *ldb, struct ldb_message *usr_obj) >+{ >+ int ret; >+ >+ ret = samdb_find_or_add_attribute(ldb, usr_obj, >+ "accountExpires", "9223372036854775807"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, usr_obj, >+ "badPasswordTime", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, usr_obj, >+ "badPwdCount", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, usr_obj, >+ "codePage", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, usr_obj, >+ "countryCode", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, usr_obj, >+ "lastLogoff", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, usr_obj, >+ "lastLogon", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, usr_obj, >+ "logonCount", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ ret = samdb_find_or_add_attribute(ldb, usr_obj, >+ "pwdLastSet", "0"); >+ if (ret != LDB_SUCCESS) return ret; >+ >+ return LDB_SUCCESS; >+} >diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c >index 3e8922c..58f7bac 100644 >--- a/source4/dsdb/samdb/ldb_modules/samldb.c >+++ b/source4/dsdb/samdb/ldb_modules/samldb.c >@@ -999,32 +999,7 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) > bool uac_generated = false, uac_add_flags = false; > > /* Step 1.2: Default values */ >- ret = samdb_find_or_add_attribute(ldb, ac->msg, >- "accountExpires", "9223372036854775807"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, ac->msg, >- "badPasswordTime", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, ac->msg, >- "badPwdCount", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, ac->msg, >- "codePage", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, ac->msg, >- "countryCode", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, ac->msg, >- "lastLogoff", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, ac->msg, >- "lastLogon", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, ac->msg, >- "logonCount", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, ac->msg, >- "pwdLastSet", "0"); >+ ret = dsdb_user_obj_set_defaults(ldb, ac->msg); > if (ret != LDB_SUCCESS) return ret; > > /* On add operations we might need to generate a >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index 8b31579..298567e 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -238,33 +238,10 @@ static int _tr_restore_attributes(struct ldb_context *ldb, struct ldb_message *c > /* restoring 'user' instance attribute is heavily borrowed from samldb.c */ > > /* Default values */ >- ret = samdb_find_or_add_attribute(ldb, new_msg, >- "accountExpires", "9223372036854775807"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, new_msg, >- "badPasswordTime", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, new_msg, >- "badPwdCount", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, new_msg, >- "codePage", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, new_msg, >- "countryCode", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, new_msg, >- "lastLogoff", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, new_msg, >- "lastLogon", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, new_msg, >- "logonCount", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, new_msg, >- "pwdLastSet", "0"); >+ ret = dsdb_user_obj_set_defaults(ldb, new_msg); > if (ret != LDB_SUCCESS) return ret; >+ >+ /* Following are set only while reanimating objects */ > ret = samdb_find_or_add_attribute(ldb, new_msg, > "adminCount", "0"); > if (ret != LDB_SUCCESS) return ret; >-- >2.1.4 > > >From b0427ebd262e535aecc6f9ccf8d31a45bf64cd39 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Mon, 12 Jan 2015 04:46:38 +0200 >Subject: [PATCH 46/51] s4-dsdb: Common helper for setting "sAMAccountType" on > User objects > >Change-Id: I4480e7d1ed0c754e960028e0be9a90ee56935e94 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit b37f7e619048593e267271f1b30af3f915fc422b) >--- > source4/dsdb/common/util.c | 36 ++++++++++++++++++++++ > source4/dsdb/samdb/ldb_modules/samldb.c | 14 ++------- > .../dsdb/samdb/ldb_modules/tombstone_reanimate.c | 13 +++----- > 3 files changed, 43 insertions(+), 20 deletions(-) > >diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c >index b0357f0..71cfd42 100644 >--- a/source4/dsdb/common/util.c >+++ b/source4/dsdb/common/util.c >@@ -4886,3 +4886,39 @@ int dsdb_user_obj_set_defaults(struct ldb_context *ldb, struct ldb_message *usr_ > > return LDB_SUCCESS; > } >+ >+/** >+ * Sets 'sAMAccountType on user object based on userAccountControl >+ * @param ldb Current ldb_context >+ * @param usr_obj ldb_message representing User object >+ * @param user_account_control Value for userAccountControl flags >+ * @param account_type_p Optional pointer to account_type to return >+ * @return LDB_SUCCESS or LDB_ERR* code on failure >+ */ >+int dsdb_user_obj_set_account_type(struct ldb_context *ldb, struct ldb_message *usr_obj, >+ uint32_t user_account_control, uint32_t *account_type_p) >+{ >+ int ret; >+ uint32_t account_type; >+ struct ldb_message_element *el; >+ >+ account_type = ds_uf2atype(user_account_control); >+ if (account_type == 0) { >+ ldb_set_errstring(ldb, "dsdb: Unrecognized account type!"); >+ return LDB_ERR_UNWILLING_TO_PERFORM; >+ } >+ ret = samdb_msg_add_uint(ldb, usr_obj, usr_obj, >+ "sAMAccountType", >+ account_type); >+ if (ret != LDB_SUCCESS) { >+ return ret; >+ } >+ el = ldb_msg_find_element(usr_obj, "sAMAccountType"); >+ el->flags = LDB_FLAG_MOD_REPLACE; >+ >+ if (account_type_p) { >+ *account_type_p = account_type; >+ } >+ >+ return LDB_SUCCESS; >+} >diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c >index 58f7bac..09278c2 100644 >--- a/source4/dsdb/samdb/ldb_modules/samldb.c >+++ b/source4/dsdb/samdb/ldb_modules/samldb.c >@@ -1018,7 +1018,7 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) > > el = ldb_msg_find_element(ac->msg, "userAccountControl"); > if (el != NULL) { >- uint32_t user_account_control, account_type; >+ uint32_t user_account_control; > /* Step 1.3: "userAccountControl" -> "sAMAccountType" mapping */ > user_account_control = ldb_msg_find_attr_as_uint(ac->msg, > "userAccountControl", >@@ -1056,19 +1056,11 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) > return LDB_ERR_OBJECT_CLASS_VIOLATION; > } > >- account_type = ds_uf2atype(user_account_control); >- if (account_type == 0) { >- ldb_set_errstring(ldb, "samldb: Unrecognized account type!"); >- return LDB_ERR_UNWILLING_TO_PERFORM; >- } >- ret = samdb_msg_add_uint(ldb, ac->msg, ac->msg, >- "sAMAccountType", >- account_type); >+ /* add "sAMAccountType" attribute */ >+ ret = dsdb_user_obj_set_account_type(ldb, ac->msg, user_account_control, NULL); > if (ret != LDB_SUCCESS) { > return ret; > } >- el2 = ldb_msg_find_element(ac->msg, "sAMAccountType"); >- el2->flags = LDB_FLAG_MOD_REPLACE; > > /* "isCriticalSystemObject" might be set */ > if (user_account_control & >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index 298567e..bbcad63 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -249,23 +249,18 @@ static int _tr_restore_attributes(struct ldb_context *ldb, struct ldb_message *c > "operatorCount", "0"); > if (ret != LDB_SUCCESS) return ret; > >- /* restore "sAMAccountType" */ >+ /* "userAccountControl" must exists on deleted object */ > user_account_control = ldb_msg_find_attr_as_uint(cur_msg, "userAccountControl", (uint32_t)-1); > if (user_account_control == (uint32_t)-1) { > return ldb_error(ldb, LDB_ERR_OPERATIONS_ERROR, > "reanimate: No 'userAccountControl' attribute found!"); > } >- account_type = ds_uf2atype(user_account_control); >- if (account_type == 0) { >- ldb_set_errstring(ldb, "reanimate: Unrecognized account type!"); >- return LDB_ERR_UNWILLING_TO_PERFORM; >- } >- ret = samdb_msg_add_uint(ldb, new_msg, new_msg, "sAMAccountType", account_type); >+ >+ /* restore "sAMAccountType" */ >+ ret = dsdb_user_obj_set_account_type(ldb, new_msg, user_account_control, NULL); > if (ret != LDB_SUCCESS) { > return ret; > } >- el = ldb_msg_find_element(new_msg, "sAMAccountType"); >- el->flags = LDB_FLAG_MOD_REPLACE; > > /* "userAccountControl" -> "primaryGroupID" mapping */ > if (!ldb_msg_find_element(new_msg, "primaryGroupID")) { >-- >2.1.4 > > >From f0787b6be116499766d2abf05de9ea642d596bd4 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sun, 18 Jan 2015 23:58:13 +0200 >Subject: [PATCH 47/51] s4-dsdb: common helper to determine "primaryGroupID" > attribute value > >At the moment current implementation does not check if group RID >is existing group RID - this responsibility is left to the caller. > >Change-Id: I8c58dd23a7185d63fa2117be0617884eb78d13c1 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 3fdda87120abfd296af5efbb79e22095609f62fe) >--- > source4/dsdb/common/util.c | 32 ++++++++++++++++++++++ > source4/dsdb/samdb/ldb_modules/samldb.c | 15 ++++------ > .../dsdb/samdb/ldb_modules/tombstone_reanimate.c | 22 ++++++++------- > 3 files changed, 49 insertions(+), 20 deletions(-) > >diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c >index 71cfd42..8b6899f 100644 >--- a/source4/dsdb/common/util.c >+++ b/source4/dsdb/common/util.c >@@ -4922,3 +4922,35 @@ int dsdb_user_obj_set_account_type(struct ldb_context *ldb, struct ldb_message * > > return LDB_SUCCESS; > } >+ >+/** >+ * Determine and set primaryGroupID based on userAccountControl value >+ * @param ldb Current ldb_context >+ * @param usr_obj ldb_message representing User object >+ * @param user_account_control Value for userAccountControl flags >+ * @param group_rid_p Optional pointer to group RID to return >+ * @return LDB_SUCCESS or LDB_ERR* code on failure >+ */ >+int dsdb_user_obj_set_primary_group_id(struct ldb_context *ldb, struct ldb_message *usr_obj, >+ uint32_t user_account_control, uint32_t *group_rid_p) >+{ >+ int ret; >+ uint32_t rid; >+ struct ldb_message_element *el; >+ >+ rid = ds_uf2prim_group_rid(user_account_control); >+ >+ ret = samdb_msg_add_uint(ldb, usr_obj, usr_obj, >+ "primaryGroupID", rid); >+ if (ret != LDB_SUCCESS) { >+ return ret; >+ } >+ el = ldb_msg_find_element(usr_obj, "primaryGroupID"); >+ el->flags = LDB_FLAG_MOD_REPLACE; >+ >+ if (group_rid_p) { >+ *group_rid_p = rid; >+ } >+ >+ return LDB_SUCCESS; >+} >diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c >index 09278c2..bb702ef 100644 >--- a/source4/dsdb/samdb/ldb_modules/samldb.c >+++ b/source4/dsdb/samdb/ldb_modules/samldb.c >@@ -1086,8 +1086,12 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) > > /* Step 1.4: "userAccountControl" -> "primaryGroupID" mapping */ > if (!ldb_msg_find_element(ac->msg, "primaryGroupID")) { >- uint32_t rid = ds_uf2prim_group_rid(user_account_control); >+ uint32_t rid; > >+ ret = dsdb_user_obj_set_primary_group_id(ldb, ac->msg, user_account_control, &rid); >+ if (ret != LDB_SUCCESS) { >+ return ret; >+ } > /* > * Older AD deployments don't know about the > * RODC group >@@ -1098,15 +1102,6 @@ static int samldb_objectclass_trigger(struct samldb_ctx *ac) > return ret; > } > } >- >- ret = samdb_msg_add_uint(ldb, ac->msg, ac->msg, >- "primaryGroupID", rid); >- if (ret != LDB_SUCCESS) { >- return ret; >- } >- el2 = ldb_msg_find_element(ac->msg, >- "primaryGroupID"); >- el2->flags = LDB_FLAG_MOD_REPLACE; > } > > /* Step 1.5: Add additional flags when needed */ >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index bbcad63..fa24ca4 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -235,6 +235,7 @@ static int _tr_restore_attributes(struct ldb_context *ldb, struct ldb_message *c > > /* objectClass is USER */ > if (samdb_find_attribute(ldb, cur_msg, "objectclass", "user") != NULL) { >+ uint32_t primary_group_rid; > /* restoring 'user' instance attribute is heavily borrowed from samldb.c */ > > /* Default values */ >@@ -263,17 +264,18 @@ static int _tr_restore_attributes(struct ldb_context *ldb, struct ldb_message *c > } > > /* "userAccountControl" -> "primaryGroupID" mapping */ >- if (!ldb_msg_find_element(new_msg, "primaryGroupID")) { >- uint32_t rid = ds_uf2prim_group_rid(user_account_control); >- >- ret = samdb_msg_add_uint(ldb, new_msg, new_msg, >- "primaryGroupID", rid); >- if (ret != LDB_SUCCESS) { >- return ret; >- } >- el = ldb_msg_find_element(new_msg, "primaryGroupID"); >- el->flags = LDB_FLAG_MOD_REPLACE; >+ ret = dsdb_user_obj_set_primary_group_id(ldb, new_msg, user_account_control, &primary_group_rid); >+ if (ret != LDB_SUCCESS) { >+ return ret; > } >+ /* >+ * Older AD deployments don't know about the >+ * RODC group >+ */ >+ if (primary_group_rid == DOMAIN_RID_READONLY_DCS) { >+ /* TODO: check group exists */ >+ } >+ > } > > /* objectClass is GROUP */ >-- >2.1.4 > > >From f603ccf1905831f80b4da7c364069a4122e9ef2c Mon Sep 17 00:00:00 2001 >From: Andrew Bartlett <abartlet@samba.org> >Date: Thu, 22 Jan 2015 17:22:52 +1300 >Subject: [PATCH 48/51] dsdb: Do not use _ prefix in tombstone_reanimate module > >This should only be used by the C library. > >Andrew Bartlett > >Change-Id: I00da64de1443a7c6b21aafae79e126180eb1a3d4 >Signed-off-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Kamen Mazdrashki <kamenim@samba.org> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit ed60811893e1362c0067001113a5bf267ae2c52e) >--- > source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c | 12 ++++++------ > 1 file changed, 6 insertions(+), 6 deletions(-) > >diff --git a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >index fa24ca4..add6c57 100644 >--- a/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >+++ b/source4/dsdb/samdb/ldb_modules/tombstone_reanimate.c >@@ -111,7 +111,7 @@ static bool is_tombstone_reanimate_request(struct ldb_request *req, struct ldb_m > * Local rename implementation based on dsdb_module_rename() > * so we could fine tune it and add more controls > */ >-static int _tr_do_rename(struct ldb_module *module, struct ldb_request *parent_req, >+static int tr_do_rename(struct ldb_module *module, struct ldb_request *parent_req, > struct ldb_dn *dn_from, struct ldb_dn *dn_to) > { > int ret; >@@ -169,7 +169,7 @@ static int _tr_do_rename(struct ldb_module *module, struct ldb_request *parent_r > * Local rename implementation based on dsdb_module_modify() > * so we could fine tune it and add more controls > */ >-static int _tr_do_modify(struct ldb_module *module, struct ldb_request *parent_req, struct ldb_message *msg) >+static int tr_do_modify(struct ldb_module *module, struct ldb_request *parent_req, struct ldb_message *msg) > { > int ret; > struct ldb_request *mod_req; >@@ -219,7 +219,7 @@ static int _tr_do_modify(struct ldb_module *module, struct ldb_request *parent_r > return ret; > } > >-static int _tr_restore_attributes(struct ldb_context *ldb, struct ldb_message *cur_msg, struct ldb_message *new_msg) >+static int tr_restore_attributes(struct ldb_context *ldb, struct ldb_message *cur_msg, struct ldb_message *new_msg) > { > int ret; > struct ldb_message_element *el; >@@ -372,7 +372,7 @@ static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_requ > ldb_msg_remove_attr(msg, "distinguishedName"); > > /* restore attributed depending on objectClass */ >- ret = _tr_restore_attributes(ldb, res_obj->msgs[0], msg); >+ ret = tr_restore_attributes(ldb, res_obj->msgs[0], msg); > if (ret != LDB_SUCCESS) { > return ret; > } >@@ -394,7 +394,7 @@ static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_requ > } > msg->elements[msg->num_elements-1].flags = LDB_FLAG_MOD_ADD; > } >- ret = _tr_do_modify(module, req, msg); >+ ret = tr_do_modify(module, req, msg); > if (ret != LDB_SUCCESS) { > return ret; > } >@@ -404,7 +404,7 @@ static int tombstone_reanimate_modify(struct ldb_module *module, struct ldb_requ > if (dn_new == NULL) { > return ldb_oom(ldb); > } >- ret = _tr_do_rename(module, req, req->op.mod.message->dn, dn_new); >+ ret = tr_do_rename(module, req, req->op.mod.message->dn, dn_new); > if (ret != LDB_SUCCESS) { > ldb_debug(ldb, LDB_DEBUG_ERROR, "Renaming object to %s has failed with %s\n", el_dn->values[0].data, ldb_strerror(ret)); > if (ret != LDB_ERR_ENTRY_ALREADY_EXISTS && ret != LDB_ERR_INSUFFICIENT_ACCESS_RIGHTS ) { >-- >2.1.4 > > >From 070a321d4c13bd0d7be2ef0a3d5f8c7de14d3748 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Wed, 28 Jan 2015 01:43:10 +0200 >Subject: [PATCH 49/51] s4-dsdb: Refactor user objects defaults setter to use > attribute/value map > >Change-Id: Iaa32af4225219a4c5c42c663022e8be429b8a1d2 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >Reviewed-by: Garming Sam <garming@catalyst.net.nz> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 3c066661e826bed16869a6c0d52c4e083ea6bae0) >--- > source4/dsdb/common/util.c | 77 +++++++++++++++++++++++++++++----------------- > 1 file changed, 49 insertions(+), 28 deletions(-) > >diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c >index 8b6899f..85edd46 100644 >--- a/source4/dsdb/common/util.c >+++ b/source4/dsdb/common/util.c >@@ -4854,35 +4854,56 @@ NTSTATUS dsdb_update_bad_pwd_count(TALLOC_CTX *mem_ctx, > */ > int dsdb_user_obj_set_defaults(struct ldb_context *ldb, struct ldb_message *usr_obj) > { >- int ret; >+ int i, ret; >+ const struct attribute_values { >+ const char *name; >+ const char *value; >+ } map[] = { >+ { >+ .name = "accountExpires", >+ .value = "9223372036854775807" >+ }, >+ { >+ .name = "badPasswordTime", >+ .value = "0" >+ }, >+ { >+ .name = "badPwdCount", >+ .value = "0" >+ }, >+ { >+ .name = "codePage", >+ .value = "0" >+ }, >+ { >+ .name = "countryCode", >+ .value = "0" >+ }, >+ { >+ .name = "lastLogoff", >+ .value = "0" >+ }, >+ { >+ .name = "lastLogon", >+ .value = "0" >+ }, >+ { >+ .name = "logonCount", >+ .value = "0" >+ }, >+ { >+ .name = "pwdLastSet", >+ .value = "0" >+ } >+ }; > >- ret = samdb_find_or_add_attribute(ldb, usr_obj, >- "accountExpires", "9223372036854775807"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, usr_obj, >- "badPasswordTime", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, usr_obj, >- "badPwdCount", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, usr_obj, >- "codePage", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, usr_obj, >- "countryCode", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, usr_obj, >- "lastLogoff", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, usr_obj, >- "lastLogon", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, usr_obj, >- "logonCount", "0"); >- if (ret != LDB_SUCCESS) return ret; >- ret = samdb_find_or_add_attribute(ldb, usr_obj, >- "pwdLastSet", "0"); >- if (ret != LDB_SUCCESS) return ret; >+ for (i = 0; i < ARRAY_SIZE(map); i++) { >+ ret = samdb_find_or_add_attribute(ldb, usr_obj, >+ map[i].name, map[i].value); >+ if (ret != LDB_SUCCESS) { >+ return ret; >+ } >+ } > > return LDB_SUCCESS; > } >-- >2.1.4 > > >From d126b81edde5ee46b20a0646a8ad28f0f8fa81d4 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sun, 25 Jan 2015 18:16:58 +0200 >Subject: [PATCH 50/51] s4-dsdb/tests: Assert on expected set of attributes for > restored objects > >Change-Id: I788406d9c3839d108cea508cf2a59488d495f141 >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >Reviewed-by: Andrew Bartlett <abartlet@samba.org> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 >(cherry picked from commit 72998acc451a8722f19b901a9948774de089921a) >--- > source4/dsdb/tests/python/tombstone_reanimation.py | 109 ++++++++++++++++++++- > 1 file changed, 104 insertions(+), 5 deletions(-) > >diff --git a/source4/dsdb/tests/python/tombstone_reanimation.py b/source4/dsdb/tests/python/tombstone_reanimation.py >index c049661..6407279 100644 >--- a/source4/dsdb/tests/python/tombstone_reanimation.py >+++ b/source4/dsdb/tests/python/tombstone_reanimation.py >@@ -83,7 +83,7 @@ class RestoredObjectAttributesBaseTestCase(samba.tests.TestCase): > return self.search_dn(msg['dn']) > > def assertAttributesEqual(self, obj_orig, attrs_orig, obj_restored, attrs_rest): >- self.assertSetEqual(attrs_orig, attrs_rest) >+ self.assertEqual(attrs_orig, attrs_rest, "Actual object does not has expected attributes") > # remove volatile attributes, they can't be equal > attrs_orig -= set(["uSNChanged", "dSCorePropagationData", "whenChanged"]) > for attr in attrs_orig: >@@ -98,7 +98,7 @@ class RestoredObjectAttributesBaseTestCase(samba.tests.TestCase): > orig_ldif = self.samdb.write_ldif(m, 0) > # convert restored attr value to ldif > rest_val = obj_restored.get(attr) >- self.assertIsNotNone(rest_val) >+ self.assertFalse(rest_val is None) > m = Message() > if not isinstance(rest_val, MessageElement): > rest_val = MessageElement(str(rest_val), 0, attr) >@@ -107,6 +107,25 @@ class RestoredObjectAttributesBaseTestCase(samba.tests.TestCase): > # compare generated ldif's > self.assertEqual(orig_ldif.lower(), rest_ldif.lower()) > >+ def assertAttributesExists(self, attr_expected, obj_msg): >+ """Check object contains at least expected attrbigutes >+ :param attr_expected: dict of expected attributes with values. ** is any value >+ :param obj_msg: Ldb.Message for the object under test >+ """ >+ actual_names = set(obj_msg.keys()) >+ # Samba does not use 'dSCorePropagationData', so skip it >+ actual_names -= set(['dSCorePropagationData']) >+ self.assertEqual(set(attr_expected.keys()), actual_names, "Actual object does not has expected attributes") >+ for name in attr_expected.keys(): >+ expected_val = attr_expected[name] >+ actual_val = obj_msg.get(name) >+ self.assertFalse(actual_val is None, "No value for attribute '%s'" % name) >+ if expected_val == "**": >+ # "**" values means "any" >+ continue >+ self.assertEqual(expected_val.lower(), str(actual_val).lower(), >+ "Unexpected value for '%s'" % name) >+ > @staticmethod > def restore_deleted_object(samdb, del_dn, new_dn, new_attrs=None): > """Restores a deleted object >@@ -287,6 +306,38 @@ class BaseRestoreObjectTestCase(RestoredObjectAttributesBaseTestCase): > class RestoreUserObjectTestCase(RestoredObjectAttributesBaseTestCase): > """Test cases for delete/reanimate user objects""" > >+ def _expected_user_attributes(self, username, user_dn, category): >+ return {'dn': user_dn, >+ 'objectClass': '**', >+ 'cn': username, >+ 'distinguishedName': user_dn, >+ 'instanceType': '4', >+ 'whenCreated': '**', >+ 'whenChanged': '**', >+ 'uSNCreated': '**', >+ 'uSNChanged': '**', >+ 'name': username, >+ 'objectGUID': '**', >+ 'userAccountControl': '546', >+ 'badPwdCount': '0', >+ 'badPasswordTime': '0', >+ 'codePage': '0', >+ 'countryCode': '0', >+ 'lastLogon': '0', >+ 'lastLogoff': '0', >+ 'pwdLastSet': '0', >+ 'primaryGroupID': '513', >+ 'operatorCount': '0', >+ 'objectSid': '**', >+ 'adminCount': '0', >+ 'accountExpires': '9223372036854775807', >+ 'logonCount': '0', >+ 'sAMAccountName': username, >+ 'sAMAccountType': '805306368', >+ 'lastKnownParent': 'CN=Users,%s' % self.base_dn, >+ 'objectCategory': 'CN=%s,%s' % (category, self.schema_dn) >+ } >+ > def test_restore_user(self): > print "Test restored user attributes" > username = "restore_user" >@@ -308,7 +359,8 @@ class RestoreUserObjectTestCase(RestoredObjectAttributesBaseTestCase): > # windows restore more attributes that originally we have > orig_attrs.update(['adminCount', 'operatorCount', 'lastKnownParent']) > rest_attrs = set(obj_restore.keys()) >- self.assertSetEqual(orig_attrs, rest_attrs) >+ self.assertEqual(orig_attrs, rest_attrs, "Actual object does not has expected attributes") >+ self.assertAttributesExists(self._expected_user_attributes(username, usr_dn, "Person"), obj_restore) > > > class RestoreGroupObjectTestCase(RestoredObjectAttributesBaseTestCase): >@@ -347,6 +399,27 @@ class RestoreGroupObjectTestCase(RestoredObjectAttributesBaseTestCase): > self.samdb.add(ldif) > return self.search_dn(group_dn) > >+ def _expected_group_attributes(self, groupname, group_dn, category): >+ return {'dn': group_dn, >+ 'groupType': '-2147483646', >+ 'distinguishedName': group_dn, >+ 'sAMAccountName': groupname, >+ 'name': groupname, >+ 'objectCategory': 'CN=%s,%s' % (category, self.schema_dn), >+ 'objectClass': '**', >+ 'objectGUID': '**', >+ 'lastKnownParent': 'CN=Users,%s' % self.base_dn, >+ 'whenChanged': '**', >+ 'sAMAccountType': '268435456', >+ 'objectSid': '**', >+ 'whenCreated': '**', >+ 'uSNCreated': '**', >+ 'operatorCount': '0', >+ 'uSNChanged': '**', >+ 'instanceType': '4', >+ 'adminCount': '0', >+ 'cn': groupname } >+ > def test_plain_group(self): > print "Test restored Group attributes" > # create test group >@@ -364,6 +437,7 @@ class RestoreGroupObjectTestCase(RestoredObjectAttributesBaseTestCase): > attr_orig.update(['adminCount', 'operatorCount', 'lastKnownParent']) > attr_rest = set(obj_restore.keys()) > self.assertAttributesEqual(obj, attr_orig, obj_restore, attr_rest) >+ self.assertAttributesExists(self._expected_group_attributes("r_group", str(obj.dn), "Group"), obj_restore) > > def test_group_with_members(self): > print "Test restored Group with members attributes" >@@ -386,11 +460,31 @@ class RestoreGroupObjectTestCase(RestoredObjectAttributesBaseTestCase): > attr_orig.remove("member") > attr_rest = set(obj_restore.keys()) > self.assertAttributesEqual(obj, attr_orig, obj_restore, attr_rest) >+ self.assertAttributesExists(self._expected_group_attributes("r_group", str(obj.dn), "Group"), obj_restore) > > > class RestoreContainerObjectTestCase(RestoredObjectAttributesBaseTestCase): > """Test different scenarios for delete/reanimate OU/container objects""" > >+ def _expected_container_attributes(self, rdn, name, dn, category): >+ if rdn == 'ou': >+ lastKnownParent = '%s' % self.base_dn >+ else: >+ lastKnownParent = 'CN=Users,%s' % self.base_dn >+ return {'dn': dn, >+ 'distinguishedName': dn, >+ 'name': name, >+ 'objectCategory': 'CN=%s,%s' % (category, self.schema_dn), >+ 'objectClass': '**', >+ 'objectGUID': '**', >+ 'lastKnownParent': lastKnownParent, >+ 'whenChanged': '**', >+ 'whenCreated': '**', >+ 'uSNCreated': '**', >+ 'uSNChanged': '**', >+ 'instanceType': '4', >+ rdn: name } >+ > def _create_test_ou(self, rdn, name=None, description=None): > ou_dn = "OU=%s,%s" % (rdn, self.base_dn) > # delete an object if leftover from previous test >@@ -418,8 +512,10 @@ class RestoreContainerObjectTestCase(RestoredObjectAttributesBaseTestCase): > # windows restore more attributes that originally we have > attr_orig.update(["lastKnownParent"]) > # and does not restore following attributes >- attr_orig -= {"description"} >+ attr_orig -= set(["description"]) > self.assertAttributesEqual(obj, attr_orig, obj_restore, attr_rest) >+ expected_attrs = self._expected_container_attributes("ou", "r_ou", str(obj.dn), "Organizational-Unit") >+ self.assertAttributesExists(expected_attrs, obj_restore) > > def test_container(self): > print "Test Container reanimation" >@@ -441,8 +537,11 @@ class RestoreContainerObjectTestCase(RestoredObjectAttributesBaseTestCase): > # windows restore more attributes that originally we have > attr_orig.update(["lastKnownParent"]) > # and does not restore following attributes >- attr_orig -= {"showInAdvancedViewOnly"} >+ attr_orig -= set(["showInAdvancedViewOnly"]) > self.assertAttributesEqual(obj, attr_orig, obj_restore, attr_rest) >+ expected_attrs = self._expected_container_attributes("cn", "r_container", >+ str(obj.dn), "container") >+ self.assertAttributesExists(expected_attrs, obj_restore) > > > if __name__ == '__main__': >-- >2.1.4 > > >From 3d19318a7d26cf7ea8739c0087c382ef8c481f69 Mon Sep 17 00:00:00 2001 >From: Kamen Mazdrashki <kamenim@samba.org> >Date: Sun, 25 Jan 2015 21:39:17 +0200 >Subject: [PATCH 51/51] s4-samdb/tests: Assert on expected set of attributes > for new User object > >Change-Id: I225b64ff7492b41852fecb914f464a6c8d504a2c >Signed-off-by: Kamen Mazdrashki <kamenim@samba.org> >BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 > >Reviewed-by: Andrew Bartlett <abartlet@samba.org> > >Autobuild-User(master): Andrew Bartlett <abartlet@samba.org> >Autobuild-Date(master): Tue Feb 3 07:30:17 CET 2015 on sn-devel-104 > >(cherry picked from commit 7fd2401b7d08a0c74f34fb117c81c5b23ddae571) >--- > source4/dsdb/tests/python/sam.py | 60 ++++++++++++++++++++++++++++++++++++++++ > 1 file changed, 60 insertions(+) > >diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py >index 44295d3..62a3a92 100755 >--- a/source4/dsdb/tests/python/sam.py >+++ b/source4/dsdb/tests/python/sam.py >@@ -2877,6 +2877,66 @@ class SamTests(samba.tests.TestCase): > self.ldb.rename(pr_object[0] + "2," + pr_object[1] + self.base_dn, > pr_object[0] + "," + pr_object[1] + self.base_dn) > >+ def test_new_user_default_attributes(self): >+ """Test default attributes for new user objects""" >+ print "Test default attributes for new User objects\n" >+ >+ user_name = "ldaptestuser" >+ user_dn = "CN=%s,CN=Users,%s" % (user_name, self.base_dn) >+ ldb.add({ >+ "dn": user_dn, >+ "objectclass": "user", >+ "sAMAccountName": user_name}) >+ >+ res = ldb.search(user_dn, scope=SCOPE_BASE) >+ self.assertTrue(len(res) == 1) >+ user_obj = res[0] >+ >+ expected_attrs = {"primaryGroupID": MessageElement(["513"]), >+ "logonCount": MessageElement(["0"]), >+ "cn": MessageElement([user_name]), >+ "countryCode": MessageElement(["0"]), >+ "objectClass": MessageElement(["top","person","organizationalPerson","user"]), >+ "instanceType": MessageElement(["4"]), >+ "distinguishedName": MessageElement([user_dn]), >+ "sAMAccountType": MessageElement(["805306368"]), >+ "objectSid": "**SKIP**", >+ "whenCreated": "**SKIP**", >+ "uSNCreated": "**SKIP**", >+ "badPasswordTime": MessageElement(["0"]), >+ "dn": Dn(ldb, user_dn), >+ "pwdLastSet": MessageElement(["0"]), >+ "sAMAccountName": MessageElement([user_name]), >+ "objectCategory": MessageElement(["CN=Person,%s" % ldb.get_schema_basedn().get_linearized()]), >+ "objectGUID": "**SKIP**", >+ "whenChanged": "**SKIP**", >+ "badPwdCount": MessageElement(["0"]), >+ "accountExpires": MessageElement(["9223372036854775807"]), >+ "name": MessageElement([user_name]), >+ "codePage": MessageElement(["0"]), >+ "userAccountControl": MessageElement(["546"]), >+ "lastLogon": MessageElement(["0"]), >+ "uSNChanged": "**SKIP**", >+ "lastLogoff": MessageElement(["0"])} >+ # assert we have expected attribute names >+ actual_names = set(user_obj.keys()) >+ # Samba does not use 'dSCorePropagationData', so skip it >+ actual_names -= set(['dSCorePropagationData']) >+ self.assertEqual(set(expected_attrs.keys()), actual_names, "Actual object does not has expected attributes") >+ # check attribute values >+ for name in expected_attrs.keys(): >+ actual_val = user_obj.get(name) >+ self.assertFalse(actual_val is None, "No value for attribute '%s'" % name) >+ expected_val = expected_attrs[name] >+ if expected_val == "**SKIP**": >+ # "**ANY**" values means "any" >+ continue >+ self.assertEqual(expected_val, actual_val, >+ "Unexpected value for '%s'" % name) >+ # clean up >+ delete_force(self.ldb, "cn=ldaptestuser,cn=users," + self.base_dn) >+ >+ > if not "://" in host: > if os.path.isfile(host): > host = "tdb://%s" % host >-- >2.1.4 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Raw
Flags:
abartlet
:
review?
(
kamenim
)
metze
:
review-
Actions:
View
Attachments on
bug 10371
:
9575
|
9576
|
10825
|
10868
|
10869
| 10878 |
10887
|
12389
|
12390