From 550279fa6e2c14343eb1cd6914c7b80fa5cabf7b Mon Sep 17 00:00:00 2001 From: Nadezhda Ivanova Date: Tue, 21 Oct 2014 16:35:30 +0300 Subject: [PATCH 01/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 1a1a0b87db7de489aed4565da704a72509ccb19b Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sat, 27 Dec 2014 21:14:25 +0200 Subject: [PATCH 02/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 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 . +*/ + +/* + * 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 ce717c1d6d4c3480209bfe3f7a5f20b24ec052e5 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Thu, 23 Oct 2014 08:15:23 +0200 Subject: [PATCH 03/49] s4-dsdb: Insert tombstone_reanimate module in ldb modules chain after objectclass Change-Id: Id9748f36f0aefe40b1894ecd2e5071e3b9c8a6d6 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 4c0616b1e4ded4b8c8f4c1084fcc515e19009689 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sun, 26 Oct 2014 03:42:45 +0100 Subject: [PATCH 04/49] s4-dsdb-tests: Remove trailing ';' in deletetest.py Change-Id: Ic1ad6bbda55be56cbf7ae78a8ad988b8e479a40c Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 4f1c2aeb56da760870594a440df58db181343cdf Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sun, 26 Oct 2014 03:43:29 +0100 Subject: [PATCH 05/49] s4-dsdb-tests: Remove unused method get_ldap_connection() Change-Id: Ie50f77dbba724dbd3c2822de5c2cfff41016fac6 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 f59f8f36e049feb4074861dbf39cd0ee0be667ca Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sun, 26 Oct 2014 04:29:16 +0100 Subject: [PATCH 06/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 d16ad7c8feb327c35a63655ccb417104e24c4c5f Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sun, 26 Oct 2014 04:29:49 +0100 Subject: [PATCH 07/49] s4-dsdb-tests: Fix whitespace in deletetest.py Change-Id: Ic2924b0aa9cffd29fe0c857317ccb65ba53a1c21 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 5dac82bc0323d684ac38699a2d350605227830be Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sun, 26 Oct 2014 04:31:41 +0100 Subject: [PATCH 08/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 d5c43e170e9448938401637b6d20d21551d26000 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Mon, 27 Oct 2014 05:31:54 +0100 Subject: [PATCH 09/49] s4-dsdb: Initialize module context only we are to handle Tombstone request Change-Id: I73bd2043e96907e3d1a669bdbd943ddee1df8c0a Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 e7ca1c2ba1afc8e9abb83f9cf879fee45d77bd55 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Tue, 28 Oct 2014 06:10:56 +0100 Subject: [PATCH 10/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 8325b86847bc28bead6a90e70a86fa02fc901155 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Tue, 28 Oct 2014 06:11:31 +0100 Subject: [PATCH 11/49] s4-dsdb: Make use dsdb_make_object_category() for objectCategory Change-Id: If65c54a653ad7078ca7a535b5c247db2746b5be7 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 235c92f3563cfffc6c7a5eec66e15dc8bec1086a Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Tue, 28 Oct 2014 15:03:59 +0100 Subject: [PATCH 12/49] 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 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 46b98e2d0ba9ee16164b462f81a96c573953374f Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sun, 2 Nov 2014 17:11:20 +0100 Subject: [PATCH 13/49] s4-tests: Print out what the error is in delete_force() Change-Id: Iaa631179dc79fa756416be8eaf8c55e3b0c1a29f Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 8d3b4dd..2889863 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 c4bc28bafc8c2b9ae684da04141d7176e3981cac Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Mon, 3 Nov 2014 04:58:20 +0100 Subject: [PATCH 14/49] s4-dsdb: Add documentation link for Tombstone Reanimation Change-Id: Ib779c8b0839889371f25ad5751c9cda1a510eb54 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 3f527a719395a0b7b4d23581d049160797a0b5c2 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Tue, 4 Nov 2014 04:10:16 +0100 Subject: [PATCH 15/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 361e68581937124c8b4751f053977b8a644e27e2 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Tue, 4 Nov 2014 04:17:35 +0100 Subject: [PATCH 16/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 d11f1ff8d588e36215f1e23a54ad45186c086dbe Mon Sep 17 00:00:00 2001 From: Nadezhda Ivanova Date: Tue, 4 Nov 2014 20:08:58 +0200 Subject: [PATCH 17/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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="" % 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 5514c86ff8a5c9cc159823779250c69a3554e7e3 Mon Sep 17 00:00:00 2001 From: Nadezhda Ivanova Date: Tue, 4 Nov 2014 20:21:57 +0200 Subject: [PATCH 18/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 a0e4dc289d00b1b7195b9ff67ac93932f953b680 Mon Sep 17 00:00:00 2001 From: Nadezhda Ivanova Date: Tue, 4 Nov 2014 20:24:11 +0200 Subject: [PATCH 19/49] s4-dsdb: Some minor fixes in tombstone_reanimate, to make it work with acl Change-Id: Idad221c7ecf778fd24f6017bb4c6eacac541086a Signed-off-by: Nadezhda Ivanova Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 e3117b817f70254c2a40023b740e893b55e26dc7 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Wed, 5 Nov 2014 06:26:25 +0100 Subject: [PATCH 20/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 2889863..8555c74 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 a0b9666a0ab6fa5e0b444b759fb70e7591b9ec11 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Wed, 21 Jan 2015 00:58:56 +0200 Subject: [PATCH 21/49] s4-dsdb-test: Initial implementation for Tombstone restore test suite Change-Id: Ib35ff930b6e7cee14317328b6fe25b59eec5262c Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 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 . + +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="" % 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 9f6bff09297366e4e4772c8cfb4cd7f63b11af09 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Thu, 6 Nov 2014 03:01:54 +0100 Subject: [PATCH 22/49] s4-dsdb-test: Use case insensitive comparison for DNs in undelete test Change-Id: I4a009bb7ed58ab857ac74a235bb5f580911f0d92 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 b81fafc5bd07f039ff84ed49be6cad2f9202b151 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Thu, 6 Nov 2014 04:10:42 +0100 Subject: [PATCH 23/49] s4-dsdb-test: Fix Undelete tests after subunit upgrade work Change-Id: I4712a2a2163a57fde037511afcc1cb7bee05f12e Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 0f189ef0c2ef8e2a638f744546943ceb0ed63c67 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Fri, 7 Nov 2014 07:02:51 +0100 Subject: [PATCH 24/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 812d805232f18512bf1c2482f31438bfe03cb5cb Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Fri, 7 Nov 2014 07:04:30 +0100 Subject: [PATCH 25/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 1b1eda6f33270d141a1a3bf71b742d19e76d383b Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Fri, 7 Nov 2014 07:05:56 +0100 Subject: [PATCH 26/49] s4-dsdb/samldb: Fix type "omputer" -> "computer" Change-Id: Ic56c6945528b7f60becc4f0b318429f4c22c3d2e Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 5b7dd41b4c4a279a78f7e96929dab39d0e95feea Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Fri, 7 Nov 2014 07:07:07 +0100 Subject: [PATCH 27/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 57b9c6e63b0d5ccba8dd183e811cec378b3fbeb3 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Fri, 7 Nov 2014 07:08:29 +0100 Subject: [PATCH 28/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 82e03582ec990054359080771586860a3dd1d58a Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Fri, 7 Nov 2014 07:11:59 +0100 Subject: [PATCH 29/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 e08fc5b342c02a96b6493bce6f0dc94312d56dc1 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Thu, 13 Nov 2014 04:11:08 +0100 Subject: [PATCH 30/49] s4-dsdb/reanimate: Group objects reanimation implementation Change-Id: Iea92924ff6b33fa3723b104d5dfff1ce5a7a09b0 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 ac426918069f23bc5b0e3b076d4b45c68f6790b8 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sun, 16 Nov 2014 03:34:22 +0100 Subject: [PATCH 31/49] s4-dsdb-test: remove trailing ';' in ldap.py Change-Id: I5edc6e017b576791c1575f71a625c49ccc88fe8f Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 1ca4a2e52d2e5f16740e6709e0301a93f8f9ae0e Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sun, 16 Nov 2014 03:35:01 +0100 Subject: [PATCH 32/49] s4-dsdb/objectclass: remove duplicated declaration for objectclass_do_add Change-Id: Ib88a45cea64fb661a41ca3b4a3df9dabf509fc6c Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 b821bfc0c1d275449cd956560387d7d1c2056d11 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Fri, 21 Nov 2014 19:31:25 +0100 Subject: [PATCH 33/49] s4-dsdb-test: Fix duplicated key in a dictionary in sam.py Change-Id: Ie33d92bd308262d9bfda553d6d5e2cfd98f6d7b3 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 a8fc1f4b8d7f9e29d7539f9cc57095d7a4471652 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Wed, 26 Nov 2014 06:23:51 +0100 Subject: [PATCH 34/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 2014 +# Copyright (C) Nadezhda Ivanova 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 ff32569cb1238aca4ffad2cf3c3eeaf84b0ad851 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Wed, 26 Nov 2014 06:59:09 +0100 Subject: [PATCH 35/49] s4-dsdb-test/reanimate: Fix whitespaces according to PEP8 Change-Id: I7b46992c80178d40a0531b5afd71a7783068a9dd Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 2a997c589a307e2e622bfc4c381a32a6d16de7c8 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Wed, 26 Nov 2014 21:53:53 +0100 Subject: [PATCH 36/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 0e80a5f79c30be4d69f93371cc83d4c038dd3b38 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Thu, 27 Nov 2014 05:15:58 +0100 Subject: [PATCH 37/49] s4-dsdb/samldb: Relax a bit restrictions in Config partition while restoring deleted object Change-Id: Iead460d24058b160b46cf3ddedaf4d84b844da4d Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 68c7c3b7eedf13e4d64bbe3df7fc7fea8e97842f Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Thu, 27 Nov 2014 05:20:22 +0100 Subject: [PATCH 38/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 ceea0db03d0f61613f7be9807afa933d4215cda2 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Thu, 27 Nov 2014 06:20:33 +0100 Subject: [PATCH 39/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 e2663a68285480bd82f5ffbffc0094471ba5c8db Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Thu, 27 Nov 2014 17:49:15 +0100 Subject: [PATCH 40/49] s4-dsdb-test: Use common base method for restoring Deleted objects Change-Id: I266b58ced814cf7ea3616862506df5b55f4f1d8c Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 2b5bc1e0193023a8e7213c11d6584e24eebba5fe Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sun, 28 Dec 2014 04:23:33 +0200 Subject: [PATCH 41/49] s4-dsdb/tests: Do not pre-create LoadParm - connect_samdb_env() will handle it Change-Id: I3483c5aa50de2f7aca19e4d7cc4fa49bbe5f889d Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 . 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 cc149b4bfe62eda2fdebc9aeac5c4451a4e3044f Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Wed, 21 Jan 2015 01:03:13 +0200 Subject: [PATCH 42/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 ad2bcec..0013df3 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -484,6 +484,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 a1155974531deb3e7d73e199982d19fcf7cba3cb Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Mon, 12 Jan 2015 03:30:17 +0200 Subject: [PATCH 43/49] s4-dsdb: Move User object default attribute values in separate helper Change-Id: I1e291bcf0a5c9b2fca11323dc7f8be29f5145d42 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 cac4b1a450832d1ad6a31ae209e75194ca47f4a3 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Mon, 12 Jan 2015 04:46:38 +0200 Subject: [PATCH 44/49] s4-dsdb: Common helper for setting "sAMAccountType" on User objects Change-Id: I4480e7d1ed0c754e960028e0be9a90ee56935e94 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 e511956815d49b17bdb9616ae7c78ebfefc3a1ef Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sun, 18 Jan 2015 23:58:13 +0200 Subject: [PATCH 45/49] 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 Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 de132765fd2d080c9b9c7b728a95e4ab1b97eb62 Mon Sep 17 00:00:00 2001 From: Andrew Bartlett Date: Thu, 22 Jan 2015 17:22:52 +1300 Subject: [PATCH 46/49] 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 Reviewed-by: Kamen Mazdrashki 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 b48bf37dcf024b5f05c45290e2a6a10e553a3156 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Wed, 28 Jan 2015 01:43:10 +0200 Subject: [PATCH 47/49] s4-dsdb: Refactor user objects defaults setter to use attribute/value map Change-Id: Iaa32af4225219a4c5c42c663022e8be429b8a1d2 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett Reviewed-by: Garming Sam 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 f1a1208025425164157bbfa8bf900a90db6f2657 Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sun, 25 Jan 2015 18:16:58 +0200 Subject: [PATCH 48/49] s4-dsdb/tests: Assert on expected set of attributes for restored objects Change-Id: I788406d9c3839d108cea508cf2a59488d495f141 Signed-off-by: Kamen Mazdrashki Reviewed-by: Andrew Bartlett 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 b9126c68546a6922431518a3adc2796fe2675d6d Mon Sep 17 00:00:00 2001 From: Kamen Mazdrashki Date: Sun, 25 Jan 2015 21:39:17 +0200 Subject: [PATCH 49/49] s4-samdb/tests: Assert on expected set of attributes for new User object Change-Id: I225b64ff7492b41852fecb914f464a6c8d504a2c Signed-off-by: Kamen Mazdrashki BUG: https://bugzilla.samba.org/show_bug.cgi?id=10371 Reviewed-by: Andrew Bartlett Autobuild-User(master): Andrew Bartlett 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