>From b34a8bc0d07a5831a85c285af8cff6128bd22e59 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 6 Jul 2016 14:09:19 +1200 Subject: [PATCH 01/10] tests/dsdb/sam.py: remove duplicated primaryGroupID change Signed-off-by: Douglas Bagnall --- source4/dsdb/tests/python/sam.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/source4/dsdb/tests/python/sam.py b/source4/dsdb/tests/python/sam.py index 8296167..f0ec8c9 100755 --- a/source4/dsdb/tests/python/sam.py +++ b/source4/dsdb/tests/python/sam.py @@ -433,15 +433,6 @@ class SamTests(samba.tests.TestCase): FLAG_MOD_REPLACE, "primaryGroupID") ldb.modify(m) - # Swap the groups (does not really make sense but does the same) - m = Message() - m.dn = Dn(ldb, "cn=ldaptestuser,cn=users," + self.base_dn) - m["primaryGroupID"] = MessageElement(str(group_rid_1), - FLAG_MOD_REPLACE, "primaryGroupID") - m["primaryGroupID"] = MessageElement(str(group_rid_2), - FLAG_MOD_REPLACE, "primaryGroupID") - ldb.modify(m) - # Old primary group should contain a "member" attribute for the user, # the new shouldn't contain anymore one res1 = ldb.search("cn=ldaptestgroup, cn=users," + self.base_dn, -- 2.7.4 >From c4422f82f14ab63565d90be335ea6b10ca47d4e8 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 2 Jun 2016 09:25:00 +1200 Subject: [PATCH 02/10] s4/dsdb/repl_meta_data: use local bool version of flag Signed-off-by: Douglas Bagnall --- source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index b551e34..f4e1e6c 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -6123,7 +6123,7 @@ linked_attributes[0]: la->meta_data.originating_usn, seq_num, la->meta_data.originating_change_time, la->meta_data.version, - (la->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE)?false:true); + !active); if (ret != LDB_SUCCESS) { talloc_free(tmp_ctx); return ret; -- 2.7.4 >From fc10a5b6253cf6e74ba0d03e08a38d44eddc253e Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 30 Jun 2016 15:43:33 +1200 Subject: [PATCH 03/10] replmd_modify_delete: check talloc_new() Signed-off-by: Douglas Bagnall --- source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index f4e1e6c..fa16484 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -2125,7 +2125,7 @@ static int replmd_modify_la_delete(struct ldb_module *module, { unsigned int i; struct parsed_dn *dns, *old_dns; - TALLOC_CTX *tmp_ctx = talloc_new(msg); + TALLOC_CTX *tmp_ctx = NULL; int ret; const struct GUID *invocation_id; struct ldb_context *ldb = ldb_module_get_ctx(module); @@ -2143,6 +2143,11 @@ static int replmd_modify_la_delete(struct ldb_module *module, return LDB_ERR_NO_SUCH_ATTRIBUTE; } + tmp_ctx = talloc_new(msg); + if (tmp_ctx == NULL) { + return LDB_ERR_OPERATIONS_ERROR; + } + ret = get_parsed_dns(module, tmp_ctx, el, &dns, schema_attr->syntax->ldap_oid, parent); if (ret != LDB_SUCCESS) { talloc_free(tmp_ctx); -- 2.7.4 >From f3666210727c6312c593a05f858f9d3114162d11 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 6 Jul 2016 11:53:19 +1200 Subject: [PATCH 04/10] repl_meta_data: free context on error in replmd_modify_la_delete() Signed-off-by: Douglas Bagnall --- source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index fa16484..9b4a1b6 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -2162,6 +2162,7 @@ static int replmd_modify_la_delete(struct ldb_module *module, invocation_id = samdb_ntds_invocation_id(ldb); if (!invocation_id) { + talloc_free(tmp_ctx); return LDB_ERR_OPERATIONS_ERROR; } @@ -2186,8 +2187,10 @@ static int replmd_modify_la_delete(struct ldb_module *module, ldb_asprintf_errstring(ldb, "Attribute %s doesn't exist for target GUID %s", el->name, GUID_buf_string(&p->guid, &buf)); if (ldb_attr_cmp(el->name, "member") == 0) { + talloc_free(tmp_ctx); return LDB_ERR_UNWILLING_TO_PERFORM; } else { + talloc_free(tmp_ctx); return LDB_ERR_NO_SUCH_ATTRIBUTE; } } @@ -2197,8 +2200,10 @@ static int replmd_modify_la_delete(struct ldb_module *module, ldb_asprintf_errstring(ldb, "Attribute %s already deleted for target GUID %s", el->name, GUID_buf_string(&p->guid, &buf)); if (ldb_attr_cmp(el->name, "member") == 0) { + talloc_free(tmp_ctx); return LDB_ERR_UNWILLING_TO_PERFORM; } else { + talloc_free(tmp_ctx); return LDB_ERR_NO_SUCH_ATTRIBUTE; } } -- 2.7.4 >From 35ce7c5844eec2965a98f56acd528cb033172e23 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Wed, 6 Jul 2016 11:54:25 +1200 Subject: [PATCH 05/10] dsdb: add vanish links control Normally linked attributes are deleted by marking them as with RMD flags, but sometimes we want them to vanish without trace. At those times we set the DSDB_CONTROL_REPLMD_VANISH_LINKS control. Signed-off-by: Douglas Bagnall Signed-off-by: Garming Sam Signed-off-by: Bob Campbell Pair-programmed-with: Andrew Bartlett fixes for link deletion --- source4/dsdb/common/util.c | 7 ++ source4/dsdb/common/util.h | 33 +++--- source4/dsdb/pydsdb.c | 1 + source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 129 +++++++++++++++++++----- source4/dsdb/samdb/samdb.h | 3 + source4/setup/schema_samba4.ldif | 1 + 6 files changed, 133 insertions(+), 41 deletions(-) diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c index 1c546d7..174779f 100644 --- a/source4/dsdb/common/util.c +++ b/source4/dsdb/common/util.c @@ -4275,6 +4275,13 @@ int dsdb_request_add_controls(struct ldb_request *req, uint32_t dsdb_flags) } } + if (dsdb_flags & DSDB_REPLMD_VANISH_LINKS) { + ret = ldb_request_add_control(req, DSDB_CONTROL_REPLMD_VANISH_LINKS, true, NULL); + if (ret != LDB_SUCCESS) { + return ret; + } + } + if (dsdb_flags & DSDB_MODIFY_PARTIAL_REPLICA) { ret = ldb_request_add_control(req, DSDB_CONTROL_PARTIAL_REPLICA, false, NULL); if (ret != LDB_SUCCESS) { diff --git a/source4/dsdb/common/util.h b/source4/dsdb/common/util.h index 1085073..f2867a2 100644 --- a/source4/dsdb/common/util.h +++ b/source4/dsdb/common/util.h @@ -26,22 +26,23 @@ flags for dsdb_request_add_controls(). For the module functions, the upper 16 bits are in dsdb/samdb/ldb_modules/util.h */ -#define DSDB_SEARCH_SEARCH_ALL_PARTITIONS 0x0001 -#define DSDB_SEARCH_SHOW_DELETED 0x0002 -#define DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT 0x0004 -#define DSDB_SEARCH_REVEAL_INTERNALS 0x0008 -#define DSDB_SEARCH_SHOW_EXTENDED_DN 0x0010 -#define DSDB_MODIFY_RELAX 0x0020 -#define DSDB_MODIFY_PERMISSIVE 0x0040 -#define DSDB_FLAG_AS_SYSTEM 0x0080 -#define DSDB_TREE_DELETE 0x0100 -#define DSDB_SEARCH_ONE_ONLY 0x0200 /* give an error unless 1 record */ -#define DSDB_SEARCH_SHOW_RECYCLED 0x0400 -#define DSDB_PROVISION 0x0800 -#define DSDB_BYPASS_PASSWORD_HASH 0x1000 -#define DSDB_SEARCH_NO_GLOBAL_CATALOG 0x2000 -#define DSDB_MODIFY_PARTIAL_REPLICA 0x4000 -#define DSDB_PASSWORD_BYPASS_LAST_SET 0x8000 +#define DSDB_SEARCH_SEARCH_ALL_PARTITIONS 0x00001 +#define DSDB_SEARCH_SHOW_DELETED 0x00002 +#define DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT 0x00004 +#define DSDB_SEARCH_REVEAL_INTERNALS 0x00008 +#define DSDB_SEARCH_SHOW_EXTENDED_DN 0x00010 +#define DSDB_MODIFY_RELAX 0x00020 +#define DSDB_MODIFY_PERMISSIVE 0x00040 +#define DSDB_FLAG_AS_SYSTEM 0x00080 +#define DSDB_TREE_DELETE 0x00100 +#define DSDB_SEARCH_ONE_ONLY 0x00200 /* give an error unless 1 record */ +#define DSDB_SEARCH_SHOW_RECYCLED 0x00400 +#define DSDB_PROVISION 0x00800 +#define DSDB_BYPASS_PASSWORD_HASH 0x01000 +#define DSDB_SEARCH_NO_GLOBAL_CATALOG 0x02000 +#define DSDB_MODIFY_PARTIAL_REPLICA 0x04000 +#define DSDB_PASSWORD_BYPASS_LAST_SET 0x08000 +#define DSDB_REPLMD_VANISH_LINKS 0x10000 bool is_attr_in_list(const char * const * attrs, const char *attr); diff --git a/source4/dsdb/pydsdb.c b/source4/dsdb/pydsdb.c index f663b43..f79c382 100644 --- a/source4/dsdb/pydsdb.c +++ b/source4/dsdb/pydsdb.c @@ -1323,6 +1323,7 @@ void initdsdb(void) ADD_DSDB_STRING(DSDB_SYNTAX_OR_NAME); ADD_DSDB_STRING(DSDB_CONTROL_DBCHECK); ADD_DSDB_STRING(DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA); + ADD_DSDB_STRING(DSDB_CONTROL_REPLMD_VANISH_LINKS); ADD_DSDB_STRING(DSDB_CONTROL_PERMIT_INTERDOMAIN_TRUST_UAC_OID); ADD_DSDB_STRING(DS_GUID_COMPUTERS_CONTAINER); diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c index 9b4a1b6..b51e664 100644 --- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c +++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c @@ -2129,6 +2129,9 @@ static int replmd_modify_la_delete(struct ldb_module *module, int ret; const struct GUID *invocation_id; struct ldb_context *ldb = ldb_module_get_ctx(module); + struct ldb_control *vanish_links_ctrl = NULL; + bool vanish_links = false; + unsigned int num_to_delete = el->num_values; NTTIME now; unix_to_nt_time(&now, t); @@ -2172,11 +2175,20 @@ static int replmd_modify_la_delete(struct ldb_module *module, return ret; } + if (parent) { + vanish_links_ctrl = ldb_request_get_control(parent, DSDB_CONTROL_REPLMD_VANISH_LINKS); + if (vanish_links_ctrl) { + vanish_links = true; + vanish_links_ctrl->critical = false; + } + } + + el->num_values = 0; el->values = NULL; /* see if we are being asked to delete any links that don't exist or are already deleted */ - for (i=0; inum_values; i++) { + for (i=0; i < num_to_delete; i++) { struct parsed_dn *p = &dns[i]; struct parsed_dn *p2; uint32_t rmd_flags; @@ -2197,8 +2209,15 @@ static int replmd_modify_la_delete(struct ldb_module *module, rmd_flags = dsdb_dn_rmd_flags(p2->dsdb_dn->dn); if (rmd_flags & DSDB_RMD_FLAG_DELETED) { struct GUID_txt_buf buf; + const char *guid_str = GUID_buf_string(&p->guid, &buf); + if (vanish_links) { + DEBUG(0, ("Deleting deleted linked attribute %s to %s, " + "because vanish_links control is set\n", + el->name, guid_str)); + continue; + } ldb_asprintf_errstring(ldb, "Attribute %s already deleted for target GUID %s", - el->name, GUID_buf_string(&p->guid, &buf)); + el->name, guid_str); if (ldb_attr_cmp(el->name, "member") == 0) { talloc_free(tmp_ctx); return LDB_ERR_UNWILLING_TO_PERFORM; @@ -2209,34 +2228,75 @@ static int replmd_modify_la_delete(struct ldb_module *module, } } - /* for each new value, see if it exists already with the same GUID - if it is not already deleted and matches the delete list then delete it - */ - for (i=0; inum_values; i++) { - struct parsed_dn *p = &old_dns[i]; - uint32_t rmd_flags; + if (vanish_links) { + if (num_to_delete == old_el->num_values || num_to_delete == 0) { + el->flags = LDB_FLAG_MOD_REPLACE; - if (el->num_values && parsed_dn_find(dns, el->num_values, &p->guid, NULL) == NULL) { - continue; + for (i = 0; i < old_el->num_values; i++) { + ret = replmd_add_backlink(module, schema, msg_guid, &old_dns[i].guid, false, schema_attr, true); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + } + talloc_free(tmp_ctx); + return LDB_SUCCESS; + } else { + unsigned int num_values = 0; + unsigned int j = 0; + for (i = 0; i < old_el->num_values; i++) { + if (parsed_dn_find(dns, num_to_delete, &old_dns[i].guid, NULL) != NULL) { + /* The element is in the delete list. mark it dead. */ + ret = replmd_add_backlink(module, schema, msg_guid, &old_dns[i].guid, false, schema_attr, true); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + old_dns[i].v->length = 0; + } else { + num_values++; + } + } + for (i = 0; i < old_el->num_values; i++) { + if (old_el->values[i].length != 0) { + old_el->values[j] = old_el->values[i]; + j++; + if (j == num_values) { + break; + } + } + } + old_el->num_values = num_values; } + } else { - rmd_flags = dsdb_dn_rmd_flags(p->dsdb_dn->dn); - if (rmd_flags & DSDB_RMD_FLAG_DELETED) continue; + /* for each new value, see if it exists already with the same GUID + if it is not already deleted and matches the delete list then delete it + */ + for (i=0; inum_values; i++) { + struct parsed_dn *p = &old_dns[i]; + uint32_t rmd_flags; - ret = replmd_update_la_val(old_el->values, p->v, p->dsdb_dn, p->dsdb_dn, - invocation_id, seq_num, seq_num, now, 0, true); - if (ret != LDB_SUCCESS) { - talloc_free(tmp_ctx); - return ret; - } + if (num_to_delete && parsed_dn_find(dns, num_to_delete, &p->guid, NULL) == NULL) { + continue; + } - ret = replmd_add_backlink(module, schema, msg_guid, &old_dns[i].guid, false, schema_attr, true); - if (ret != LDB_SUCCESS) { - talloc_free(tmp_ctx); - return ret; + rmd_flags = dsdb_dn_rmd_flags(p->dsdb_dn->dn); + if (rmd_flags & DSDB_RMD_FLAG_DELETED) continue; + + ret = replmd_update_la_val(old_el->values, p->v, p->dsdb_dn, p->dsdb_dn, + invocation_id, seq_num, seq_num, now, 0, true); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } + ret = replmd_add_backlink(module, schema, msg_guid, &old_dns[i].guid, false, schema_attr, true); + if (ret != LDB_SUCCESS) { + talloc_free(tmp_ctx); + return ret; + } } } - el->values = talloc_steal(msg->elements, old_el->values); el->num_values = old_el->num_values; @@ -2644,6 +2704,11 @@ static int replmd_modify(struct ldb_module *module, struct ldb_request *req) /* current partition control is needed by "replmd_op_callback" */ if (ldb_request_get_control(req, DSDB_CONTROL_CURRENT_PARTITION_OID) == NULL) { + struct ldb_control *ctrl = ldb_request_get_control(down_req, + DSDB_CONTROL_REPLMD_VANISH_LINKS); + if (ctrl) { + ctrl->critical = false; + } ret = ldb_request_add_control(down_req, DSDB_CONTROL_CURRENT_PARTITION_OID, false, NULL); @@ -2976,6 +3041,7 @@ static int replmd_delete_remove_link(struct ldb_module *module, const struct dsdb_attribute *target_attr; struct ldb_message_element *el2; struct ldb_val dn_val; + uint32_t dsdb_flags = 0; if (dsdb_dn_is_deleted_val(&el->values[i])) { continue; @@ -3019,7 +3085,13 @@ static int replmd_delete_remove_link(struct ldb_module *module, el2->values = &dn_val; el2->num_values = 1; - ret = dsdb_module_modify(module, msg, DSDB_FLAG_OWN_MODULE, parent); + /* + * Ensure that we tell the modification to vanish any linked + * attributes (not simply mark them as isDeleted = TRUE) + */ + dsdb_flags |= DSDB_REPLMD_VANISH_LINKS; + + ret = dsdb_module_modify(module, msg, dsdb_flags|DSDB_FLAG_OWN_MODULE, parent); if (ret != LDB_SUCCESS) { talloc_free(tmp_ctx); return ret; @@ -3067,6 +3139,7 @@ static int replmd_delete_internals(struct ldb_module *module, struct ldb_request "trustType", "trustAttributes", "userAccountControl", "uSNChanged", "uSNCreated", "whenCreated", "whenChanged", NULL}; unsigned int i, el_count = 0; + uint32_t dsdb_flags = 0; enum deletion_state deletion_state, next_deletion_state; if (ldb_dn_is_special(req->op.del.dn)) { @@ -3426,6 +3499,12 @@ static int replmd_delete_internals(struct ldb_module *module, struct ldb_request if (sa->searchFlags & SEARCH_FLAG_PRESERVEONDELETE) { continue; } + } else { + /* + * Ensure that we tell the modification to vanish any linked + * attributes (not simply mark them as isDeleted = TRUE) + */ + dsdb_flags |= DSDB_REPLMD_VANISH_LINKS; } ret = ldb_msg_add_empty(msg, el->name, LDB_FLAG_MOD_DELETE, &el); if (ret != LDB_SUCCESS) { @@ -3523,7 +3602,7 @@ static int replmd_delete_internals(struct ldb_module *module, struct ldb_request msg->dn = new_dn; } - ret = dsdb_module_modify(module, msg, DSDB_FLAG_OWN_MODULE, req); + ret = dsdb_module_modify(module, msg, dsdb_flags|DSDB_FLAG_OWN_MODULE, req); if (ret != LDB_SUCCESS) { ldb_asprintf_errstring(ldb, "replmd_delete: Failed to modify object %s in delete - %s", ldb_dn_get_linearized(old_dn), ldb_errstring(ldb)); diff --git a/source4/dsdb/samdb/samdb.h b/source4/dsdb/samdb/samdb.h index 4fff80e..4e8c5d9 100644 --- a/source4/dsdb/samdb/samdb.h +++ b/source4/dsdb/samdb/samdb.h @@ -175,6 +175,9 @@ struct dsdb_control_password_user_account_control { uint32_t new_flags; /* the new flags stored */ }; +/* passed when we want to thoroughly delete linked attributes */ +#define DSDB_CONTROL_REPLMD_VANISH_LINKS "1.3.6.1.4.1.7165.4.3.28" + #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 4c0bbee..c0a4fa0 100644 --- a/source4/setup/schema_samba4.ldif +++ b/source4/setup/schema_samba4.ldif @@ -213,6 +213,7 @@ #Allocated: DSDB_CONTROL_CHANGEREPLMETADATA_RESORT_OID 1.3.6.1.4.1.7165.4.3.25 #Allocated: DSDB_CONTROL_PASSWORD_DEFAULT_LAST_SET_OID 1.3.6.1.4.1.7165.4.3.26 #Allocated: DSDB_CONTROL_PASSWORD_USER_ACCOUNT_CONTROL_OID 1.3.6.1.4.1.7165.4.3.27 +#Allocated: DSDB_CONTROL_REPLMD_VANISH_LINKS 1.3.6.1.4.1.7165.4.3.28 # 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.7.4 >From 617c691f29437d9a33ce15ea1a61fca14bca3827 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 30 Jun 2016 16:15:35 +1200 Subject: [PATCH 06/10] dbcheck: cache linkIDs and reverse attribute names This avoids fetching the same same schema things again and again. Signed-off-by: Douglas Bagnall --- python/samba/dbchecker.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/python/samba/dbchecker.py b/python/samba/dbchecker.py index e652f86..c91e367 100644 --- a/python/samba/dbchecker.py +++ b/python/samba/dbchecker.py @@ -88,7 +88,7 @@ class dbcheck(object): self.fix_missing_deleted_objects = False self.dn_set = set() - + self.link_id_cache = {} self.name_map = {} try: res = samdb.search(base="CN=DnsAdmins,CN=Users,%s" % samdb.domain_dn(), scope=ldb.SCOPE_BASE, @@ -333,6 +333,17 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) return False return True + def get_attr_linkID_and_reverse_name(self, attrname): + if attrname in self.link_id_cache: + return self.link_id_cache[attrname] + linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname) + if linkID: + revname = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname) + else: + revname = None + self.link_id_cache[attrname] = (linkID, revname) + return linkID, revname + def err_empty_attribute(self, dn, attrname): '''fix empty attributes''' self.report("ERROR: Empty attribute %s in %s" % (attrname, dn)) @@ -446,7 +457,7 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) def err_missing_dn_GUID(self, dn, attrname, val, dsdb_dn): """handle a missing target DN (both GUID and DN string form are missing)""" # check if its a backlink - linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname) + linkID, _ = self.get_attr_linkID_and_reverse_name(attrname) if (linkID & 1 == 0) and str(dsdb_dn).find('\\0ADEL') == -1: self.report("Not removing dangling forward link") return @@ -743,8 +754,7 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) else: fixing_msDS_HasInstantiatedNCs = False - linkID = self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname) - reverse_link_name = self.samdb_schema.get_backlink_from_lDAPDisplayName(attrname) + linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname) if reverse_link_name is not None: attrs.append(reverse_link_name) @@ -1573,10 +1583,12 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) error_count += 1 continue + linkID, reverse_link_name = self.get_attr_linkID_and_reverse_name(attrname) + flag = self.samdb_schema.get_systemFlags_from_lDAPDisplayName(attrname) if (not flag & dsdb.DS_FLAG_ATTR_NOT_REPLICATED and not flag & dsdb.DS_FLAG_ATTR_IS_CONSTRUCTED - and not self.samdb_schema.get_linkId_from_lDAPDisplayName(attrname)): + and not linkID): set_attrs_seen.add(str(attrname).lower()) if syntax_oid in [ dsdb.DSDB_SYNTAX_BINARY_DN, dsdb.DSDB_SYNTAX_OR_NAME, -- 2.7.4 >From ad63bdf7e006e39998e5f17fdd76c4051c659266 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 30 Jun 2016 16:17:37 +1200 Subject: [PATCH 07/10] dbcheck: check for linked atributes that should not exist In order to do this we need to use the reveal internals control, which breaks the comparison against extended DNs. So we compare the components instead. Signed-off-by: Douglas Bagnall --- python/samba/dbchecker.py | 134 +++++++++++++++++++++++++++++++--------------- 1 file changed, 90 insertions(+), 44 deletions(-) diff --git a/python/samba/dbchecker.py b/python/samba/dbchecker.py index c91e367..2e049be 100644 --- a/python/samba/dbchecker.py +++ b/python/samba/dbchecker.py @@ -53,9 +53,12 @@ class dbcheck(object): self.fix_all_DN_GUIDs = False self.fix_all_binary_dn = False self.remove_all_deleted_DN_links = False - self.fix_all_target_mismatch = False + self.fix_all_string_dn_component_mismatch = False + self.fix_all_GUID_dn_component_mismatch = False + self.fix_all_SID_dn_component_mismatch = False self.fix_all_metadata = False self.fix_time_metadata = False + self.fix_undead_linked_attributes = False self.fix_all_missing_backlinks = False self.fix_all_orphaned_backlinks = False self.fix_rmd_flags = False @@ -450,7 +453,8 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) m = ldb.Message() m.dn = dn m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname) - if self.do_modify(m, ["show_recycled:1", "local_oid:%s:0" % dsdb.DSDB_CONTROL_DBCHECK], + if self.do_modify(m, ["show_recycled:1", + "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS], "Failed to remove deleted DN attribute %s" % attrname): self.report("Removed deleted DN on attribute %s" % attrname) @@ -509,21 +513,22 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) "Failed to fix %s on attribute %s" % (errstr, attrname)): self.report("Fixed %s on attribute %s" % (errstr, attrname)) - def err_dn_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, errstr): + def err_dn_component_target_mismatch(self, dn, attrname, val, dsdb_dn, correct_dn, mismatch_type): """handle a DN string being incorrect""" - self.report("ERROR: incorrect DN string component for %s in object %s - %s" % (attrname, dn, val)) + self.report("ERROR: incorrect DN %s component for %s in object %s - %s" % (mismatch_type, attrname, dn, val)) dsdb_dn.dn = correct_dn - if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), 'fix_all_target_mismatch'): - self.report("Not fixing %s" % errstr) + if not self.confirm_all('Change DN to %s?' % str(dsdb_dn), + 'fix_all_%s_dn_component_mismatch' % mismatch_type): + self.report("Not fixing %s component mistmatch" % mismatch_type) return m = ldb.Message() m.dn = dn m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname) m['new_value'] = ldb.MessageElement(str(dsdb_dn), ldb.FLAG_MOD_ADD, attrname) if self.do_modify(m, ["show_recycled:1"], - "Failed to fix incorrect DN string on attribute %s" % attrname): - self.report("Fixed incorrect DN string on attribute %s" % (attrname)) + "Failed to fix incorrect DN %s on attribute %s" % (mismatch_type, attrname)): + self.report("Fixed incorrect DN %s on attribute %s" % (mismatch_type, attrname)) def err_unknown_attribute(self, obj, attrname): '''handle an unknown attribute error''' @@ -538,6 +543,22 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) "Failed to remove unknown attribute %s" % attrname): self.report("Removed unknown attribute %s" % (attrname)) + def err_undead_linked_attribute(self, obj, attrname, val): + '''handle a link that should not be there on a deleted object''' + self.report("ERROR: linked attribute '%s' to '%s' is present on " + "deleted object %s" % (attrname, val, obj.dn)) + if not self.confirm_all('Remove linked attribute %s' % attrname, 'fix_undead_linked_attributes'): + self.report("Not removing linked attribute %s" % attrname) + return + m = ldb.Message() + m.dn = obj.dn + m['old_value'] = ldb.MessageElement(val, ldb.FLAG_MOD_DELETE, attrname) + + if self.do_modify(m, ["show_recycled:1", "show_deleted:1", "reveal_internals:0", + "local_oid:%s:0" % dsdb.DSDB_CONTROL_REPLMD_VANISH_LINKS], + "Failed to delete forward link %s" % attrname): + self.report("Fixed undead forward link %s" % (attrname)) + def err_missing_backlink(self, obj, attrname, val, backlink_name, target_dn): '''handle a missing backlink value''' self.report("ERROR: missing backlink attribute '%s' in %s for link %s in %s" % (backlink_name, target_dn, attrname, obj.dn)) @@ -733,6 +754,8 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) def check_dn(self, obj, attrname, syntax_oid): '''check a DN attribute for correctness''' error_count = 0 + obj_guid = obj['objectGUID'][0] + for val in obj[attrname]: dsdb_dn = dsdb_Dn(self.samdb, val, syntax_oid) @@ -745,7 +768,6 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) continue guidstr = str(misc.GUID(guid)) - attrs = ['isDeleted'] if (str(attrname).lower() == 'msds-hasinstantiatedncs') and (obj.dn == self.ntds_dsa): @@ -761,7 +783,9 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) # check its the right GUID try: res = self.samdb.search(base="" % guidstr, scope=ldb.SCOPE_BASE, - attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1"]) + attrs=attrs, controls=["extended_dn:1:1", "show_recycled:1", + "reveal_internals:0" + ]) except ldb.LdbError, (enum, estr): error_count += 1 self.err_incorrect_dn_GUID(obj.dn, attrname, val, dsdb_dn, "incorrect GUID") @@ -780,43 +804,62 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) is_deleted = 'isDeleted' in obj and obj['isDeleted'][0].upper() == 'TRUE' target_is_deleted = 'isDeleted' in res[0] and res[0]['isDeleted'][0].upper() == 'TRUE' - # the target DN is not allowed to be deleted, unless the target DN is the - # special Deleted Objects container - if target_is_deleted and not is_deleted and not self.is_deleted_objects_dn(dsdb_dn): + + if is_deleted and not obj.dn in self.deleted_objects_containers and linkID: + # A fully deleted object should not have any linked + # attributes. (MS-ADTS 3.1.1.5.5.1.1 Tombstone + # Requirements and 3.1.1.5.5.1.3 Recycled-Object + # Requirements) + self.err_undead_linked_attribute(obj, attrname, val) + error_count += 1 + continue + elif target_is_deleted and not self.is_deleted_objects_dn(dsdb_dn): + # the target DN is not allowed to be deleted, unless the target DN is the + # special Deleted Objects container error_count += 1 self.err_deleted_dn(obj.dn, attrname, val, dsdb_dn, res[0].dn) continue # check the DN matches in string form - if res[0].dn.extended_str() != dsdb_dn.dn.extended_str(): + if str(res[0].dn) != str(dsdb_dn.dn): error_count += 1 - self.err_dn_target_mismatch(obj.dn, attrname, val, dsdb_dn, - res[0].dn, "incorrect string version of DN") + self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn, + res[0].dn, "string") + continue + + if res[0].dn.get_extended_component("GUID") != dsdb_dn.dn.get_extended_component("GUID"): + error_count += 1 + self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn, + res[0].dn, "GUID") + continue + + if res[0].dn.get_extended_component("SID") != dsdb_dn.dn.get_extended_component("SID"): + error_count += 1 + self.err_dn_component_target_mismatch(obj.dn, attrname, val, dsdb_dn, + res[0].dn, "SID") continue - if is_deleted and not target_is_deleted and reverse_link_name is not None: - revealed_dn = self.find_revealed_link(obj.dn, attrname, guid) - rmd_flags = revealed_dn.dn.get_extended_component("RMD_FLAGS") - if rmd_flags is not None and (int(rmd_flags) & 1) == 0: - # the RMD_FLAGS for this link should be 1, as the target is deleted - self.err_incorrect_rmd_flags(obj, attrname, revealed_dn) - continue # check the reverse_link is correct if there should be one if reverse_link_name is not None: match_count = 0 if reverse_link_name in res[0]: for v in res[0][reverse_link_name]: - if v == obj.dn.extended_str(): + v_guid = dsdb_Dn(self.samdb, v).dn.get_extended_component("GUID") + if v_guid == obj_guid: match_count += 1 if match_count != 1: - error_count += 1 - if linkID & 1: - self.err_orphaned_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn) - else: - self.err_missing_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn) + if target_is_deleted: + error_count += 1 + if linkID & 1: + self.err_missing_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn) + else: + self.err_orphaned_backlink(obj, attrname, val, reverse_link_name, dsdb_dn.dn) continue + + + return error_count @@ -1373,6 +1416,8 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) attrs.append("systemFlags") if '*' in attrs: attrs.append("replPropertyMetaData") + else: + attrs.append("objectGUID") try: sd_flags = 0 @@ -1387,6 +1432,7 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) "show_recycled:1", "show_deleted:1", "sd_flags:1:%d" % sd_flags, + "reveal_internals:0", ], attrs=attrs) except ldb.LdbError, (enum, estr): @@ -1423,7 +1469,7 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) systemFlags = 0 for attrname in obj: - if attrname == 'dn': + if attrname == 'dn' or attrname == "distinguishedName": continue if str(attrname).lower() == 'objectclass': @@ -1476,8 +1522,7 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) else: # Here we check that the first attid is 0 - # (objectClass) and that the last on is the RDN - # from the DN. + # (objectClass). if list_attid_from_md[0] != 0: error_count += 1 self.report("ERROR: Not fixing incorrect inital attributeID in '%s' on '%s', it should be objectClass" % @@ -1595,23 +1640,24 @@ newSuperior: %s""" % (str(from_dn), str(to_rdn), str(to_base))) dsdb.DSDB_SYNTAX_STRING_DN, ldb.SYNTAX_DN ]: # it's some form of DN, do specialised checking on those error_count += self.check_dn(obj, attrname, syntax_oid) + else: - values = set() - # check for incorrectly normalised attributes - for val in obj[attrname]: - values.add(str(val)) + values = set() + # check for incorrectly normalised attributes + for val in obj[attrname]: + values.add(str(val)) - normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val]) - if len(normalised) != 1 or normalised[0] != val: - self.err_normalise_mismatch(dn, attrname, obj[attrname]) + normalised = self.samdb.dsdb_normalise_attributes(self.samdb_schema, attrname, [val]) + if len(normalised) != 1 or normalised[0] != val: + self.err_normalise_mismatch(dn, attrname, obj[attrname]) + error_count += 1 + break + + if len(obj[attrname]) != len(values): + self.err_duplicate_values(dn, attrname, obj[attrname], list(values)) error_count += 1 break - if len(obj[attrname]) != len(values): - self.err_duplicate_values(dn, attrname, obj[attrname], list(values)) - error_count += 1 - break - if str(attrname).lower() == "instancetype": calculated_instancetype = self.calculate_instancetype(dn) if len(obj["instanceType"]) != 1 or obj["instanceType"][0] != str(calculated_instancetype): -- 2.7.4 >From 18074b5ab5e2e05e84e2dedf60020864c53ae6b9 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Thu, 30 Jun 2016 16:35:08 +1200 Subject: [PATCH 08/10] dsdb tests: add linked attribute tests Note that this test will not work properly across ldap as the marked-deleted linked attributes will not appear. Signed-off-by: Douglas Bagnall --- source4/dsdb/tests/python/linked_attributes.py | 361 +++++++++++++++++++++++++ source4/selftest/tests.py | 1 + 2 files changed, 362 insertions(+) create mode 100644 source4/dsdb/tests/python/linked_attributes.py diff --git a/source4/dsdb/tests/python/linked_attributes.py b/source4/dsdb/tests/python/linked_attributes.py new file mode 100644 index 0000000..d20ec1a --- /dev/null +++ b/source4/dsdb/tests/python/linked_attributes.py @@ -0,0 +1,361 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# Originally based on ./sam.py +import optparse +import sys +import os +import base64 +import random +import re + +sys.path.insert(0, "bin/python") +import samba +from samba.tests.subunitrun import SubunitOptions, TestProgram + +import samba.getopt as options + +from samba.auth import system_session +import ldb +from samba.samdb import SamDB +from samba.dcerpc import misc + +import time + +parser = optparse.OptionParser("linked_attributes.py [options] ") +sambaopts = options.SambaOptions(parser) +parser.add_option_group(sambaopts) +parser.add_option_group(options.VersionOptions(parser)) +# use command line creds if available +credopts = options.CredentialsOptions(parser) +parser.add_option_group(credopts) +subunitopts = SubunitOptions(parser) +parser.add_option_group(subunitopts) + +parser.add_option('--delete-in-setup', action='store_true', + help="cleanup in setup") + +parser.add_option('--no-cleanup', action='store_true', + help="don't cleanup in teardown") + +parser.add_option('--no-reveal-internals', action='store_true', + help="Only use windows compatible ldap controls") + +opts, args = parser.parse_args() + +if len(args) < 1: + parser.print_usage() + sys.exit(1) + +host = args[0] + +lp = sambaopts.get_loadparm() +creds = credopts.get_credentials(lp) + + +class LATestException(Exception): + pass + + +class LATests(samba.tests.TestCase): + + def setUp(self): + super(LATests, self).setUp() + self.samdb = SamDB(host, credentials=creds, + session_info=system_session(lp), lp=lp) + + self.base_dn = self.samdb.domain_dn() + self.ou = "OU=la,%s" % self.base_dn + if opts.delete_in_setup: + try: + self.samdb.delete(self.ou, ['tree_delete:1']) + except ldb.LdbError, e: + print "tried deleting %s, got error %s" % (self.ou, e) + self.samdb.add({'objectclass': 'organizationalUnit', + 'dn': self.ou}) + + def tearDown(self): + super(LATests, self).tearDown() + if not opts.no_cleanup: + self.samdb.delete(self.ou, ['tree_delete:1']) + + def delete_user(self, user): + self.samdb.delete(user['dn']) + del self.users[self.users.index(user)] + + def add_object(self, cn, objectclass): + dn = "CN=%s,%s" % (cn, self.ou) + self.samdb.add({'cn': cn, + 'objectclass': objectclass, + 'dn': dn}) + + return dn + + def add_objects(self, n, objectclass, prefix=None): + if prefix is None: + prefix = objectclass + dns = [] + for i in range(n): + dns.append(self.add_object("%s%d" % (prefix, i + 1), + objectclass)) + return dns + + def add_linked_attribute(self, src, dest, attr='member'): + m = ldb.Message() + m.dn = ldb.Dn(self.samdb, src) + m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_ADD, attr) + self.samdb.modify(m) + + def remove_linked_attribute(self, src, dest, attr='member'): + m = ldb.Message() + m.dn = ldb.Dn(self.samdb, src) + m[attr] = ldb.MessageElement(dest, ldb.FLAG_MOD_DELETE, attr) + self.samdb.modify(m) + + def attr_search(self, obj, expected, attr, scope=ldb.SCOPE_BASE, + **controls): + if opts.no_reveal_internals: + if 'reveal_internals' in controls: + del controls['reveal_internals'] + + controls = ['%s:%d' % (k, int(v)) for k, v in controls.items()] + + res = self.samdb.search(obj, + scope=scope, + attrs=[attr], + controls=controls) + return res + + def assert_links(self, obj, expected, attr, sorted=False, msg='', + **kwargs): + res = self.attr_search(obj, expected, attr, **kwargs) + + if len(expected) == 0: + if attr in res[0]: + #print res[0][attr] + #self.fail("found attr '%s' %s" % (attr, res[0][attr][0])) + self.fail("found attr '%s' in %s" % (attr, res[0])) + return + + results = list([x[attr] for x in res][0]) + if sorted == False: + results = set(results) + expected = set(expected) + + if expected != results: + print msg + print "expected %s" % expected + print "received %s" % results + + self.assertEqual(results, expected) + + def assert_back_links(self, obj, expected, attr='memberOf', **kwargs): + self.assert_links(obj, expected, attr=attr, + msg='back links do not match', **kwargs) + + def assert_forward_links(self, obj, expected, attr='member', **kwargs): + self.assert_links(obj, expected, attr=attr, + msg='forward links do not match', **kwargs) + + def get_object_guid(self, dn): + res = self.samdb.search(dn, + scope=ldb.SCOPE_BASE, + attrs=['objectGUID']) + return str(misc.GUID(res[0]['objectGUID'][0])) + + def _test_la_backlinks(self, reveal=False): + tag = 'backlinks' + kwargs = {} + if reveal: + tag += '_reveal' + kwargs = {'reveal_internals': 0} + + u1, u2 = self.add_objects(2, 'user', 'u_%s' % tag) + g1, g2 = self.add_objects(2, 'group', 'g_%s' % tag) + + self.add_linked_attribute(g1, u1) + self.add_linked_attribute(g2, u1) + self.add_linked_attribute(g2, u2) + + self.assert_back_links(u1, [g1, g2], **kwargs) + self.assert_back_links(u2, [g2], **kwargs) + + def test_la_backlinks(self): + self._test_la_backlinks() + + def test_la_backlinks_reveal(self): + if opts.no_reveal_internals: + print 'skipping because --no-reveal-internals' + return + self._test_la_backlinks(True) + + def _test_la_backlinks_delete_group(self, reveal=False): + tag = 'del_group' + kwargs = {} + if reveal: + tag += '_reveal' + kwargs = {'reveal_internals': 0} + + u1, u2 = self.add_objects(2, 'user', 'u_' + tag) + g1, g2 = self.add_objects(2, 'group', 'g_' + tag) + + self.add_linked_attribute(g1, u1) + self.add_linked_attribute(g2, u1) + self.add_linked_attribute(g2, u2) + + self.samdb.delete(g2, ['tree_delete:1']) + + self.assert_back_links(u1, [g1], **kwargs) + self.assert_back_links(u2, set(), **kwargs) + + def test_la_backlinks_delete_group(self): + self._test_la_backlinks_delete_group() + + def test_la_backlinks_delete_group_reveal(self): + if opts.no_reveal_internals: + print 'skipping because --no-reveal-internals' + return + self._test_la_backlinks_delete_group(True) + + def test_links_all_delete_group(self): + u1, u2 = self.add_objects(2, 'user', 'u_all_del_group') + g1, g2 = self.add_objects(2, 'group', 'g_all_del_group') + g2guid = self.get_object_guid(g2) + + self.add_linked_attribute(g1, u1) + self.add_linked_attribute(g2, u1) + self.add_linked_attribute(g2, u2) + + self.samdb.delete(g2) + self.assert_back_links(u1, [g1], show_deleted=1, show_recycled=1, + show_deactivated_link=0) + self.assert_back_links(u2, set(), show_deleted=1, show_recycled=1, + show_deactivated_link=0) + self.assert_forward_links(g1, [u1], show_deleted=1, show_recycled=1, + show_deactivated_link=0) + self.assert_forward_links('' % g2guid, + [], show_deleted=1, show_recycled=1, + show_deactivated_link=0) + + def test_links_all_delete_group_reveal(self): + u1, u2 = self.add_objects(2, 'user', 'u_all_del_group_reveal') + g1, g2 = self.add_objects(2, 'group', 'g_all_del_group_reveal') + g2guid = self.get_object_guid(g2) + + self.add_linked_attribute(g1, u1) + self.add_linked_attribute(g2, u1) + self.add_linked_attribute(g2, u2) + + self.samdb.delete(g2) + self.assert_back_links(u1, [g1], show_deleted=1, show_recycled=1, + show_deactivated_link=0, + reveal_internals=0) + self.assert_back_links(u2, set(), show_deleted=1, show_recycled=1, + show_deactivated_link=0, + reveal_internals=0) + self.assert_forward_links(g1, [u1], show_deleted=1, show_recycled=1, + show_deactivated_link=0, + reveal_internals=0) + self.assert_forward_links('' % g2guid, + [], show_deleted=1, show_recycled=1, + show_deactivated_link=0, + reveal_internals=0) + + def test_la_links_delete_link(self): + u1, u2 = self.add_objects(2, 'user', 'u_del_link') + g1, g2 = self.add_objects(2, 'group', 'g_del_link') + + self.add_linked_attribute(g1, u1) + self.add_linked_attribute(g2, u1) + self.add_linked_attribute(g2, u2) + + self.remove_linked_attribute(g2, u1) + + self.assert_forward_links(g1, [u1]) + self.assert_forward_links(g2, [u2]) + + self.add_linked_attribute(g2, u1) + self.assert_forward_links(g2, [u1, u2]) + self.remove_linked_attribute(g2, u2) + self.assert_forward_links(g2, [u1]) + self.remove_linked_attribute(g2, u1) + self.assert_forward_links(g2, []) + + def test_la_links_delete_link_reveal(self): + u1, u2 = self.add_objects(2, 'user', 'u_del_link_reveal') + g1, g2 = self.add_objects(2, 'group', 'g_del_link_reveal') + + self.add_linked_attribute(g1, u1) + self.add_linked_attribute(g2, u1) + self.add_linked_attribute(g2, u2) + + self.remove_linked_attribute(g2, u1) + + self.assert_forward_links(g2, [u1, u2], show_deleted=1, + show_recycled=1, + show_deactivated_link=0, + reveal_internals=0 + ) + + def test_la_links_delete_user(self): + u1, u2 = self.add_objects(2, 'user', 'u_del_user') + g1, g2 = self.add_objects(2, 'group', 'g_del_user') + + self.add_linked_attribute(g1, u1) + self.add_linked_attribute(g2, u1) + self.add_linked_attribute(g2, u2) + + self.samdb.delete(u1) + + self.assert_forward_links(g1, []) + self.assert_forward_links(g2, [u2]) + + def test_la_links_delete_user_reveal(self): + u1, u2 = self.add_objects(2, 'user', 'u_del_user_reveal') + g1, g2 = self.add_objects(2, 'group', 'g_del_user_reveal') + + self.add_linked_attribute(g1, u1) + self.add_linked_attribute(g2, u1) + self.add_linked_attribute(g2, u2) + + self.samdb.delete(u1) + + self.assert_forward_links(g2, [u2], + show_deleted=1, show_recycled=1, + show_deactivated_link=0, + reveal_internals=0) + self.assert_forward_links(g1, [], + show_deleted=1, show_recycled=1, + show_deactivated_link=0, + reveal_internals=0) + + def _test_la_links_sort_order(self): + u1, u2, u3 = self.add_objects(3, 'user', 'u_sort_order') + g1, g2, g3 = self.add_objects(3, 'group', 'g_sort_order') + + # Add these in a haphazard order + self.add_linked_attribute(g2, u3) + self.add_linked_attribute(g3, u2) + self.add_linked_attribute(g1, u3) + self.add_linked_attribute(g1, u1) + self.add_linked_attribute(g2, u1) + self.add_linked_attribute(g2, u2) + self.add_linked_attribute(g3, u3) + self.add_linked_attribute(g3, u1) + + self.assert_forward_links(g1, [u3, u1], sorted=True) + self.assert_forward_links(g2, [u3, u2, u1], sorted=True) + self.assert_forward_links(g3, [u3, u2, u1], sorted=True) + + self.assert_back_links(u1, [g3, g2, g1], sorted=True) + self.assert_back_links(u2, [g3, g2], sorted=True) + self.assert_back_links(u3, [g3, g2, g1], sorted=True) + + +if "://" not in host: + if os.path.isfile(host): + host = "tdb://%s" % host + else: + host = "ldap://%s" % host + + +TestProgram(module=__name__, opts=subunitopts) diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py index f3fe554..940e844 100755 --- a/source4/selftest/tests.py +++ b/source4/selftest/tests.py @@ -583,6 +583,7 @@ plantestsuite_loadlist("samba4.ldap.sites.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [ plantestsuite_loadlist("samba4.ldap.sort.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(samba4srcdir, "dsdb/tests/python/sort.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) plantestsuite_loadlist("samba4.ldap.vlv.python(ad_dc_ntvfs)", "ad_dc_ntvfs", [python, os.path.join(samba4srcdir, "dsdb/tests/python/vlv.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) +plantestsuite_loadlist("samba4.ldap.linked_attributes.python(ad_dc_ntvfs)", "ad_dc_ntvfs:local", [python, os.path.join(samba4srcdir, "dsdb/tests/python/linked_attributes.py"), '$PREFIX_ABS/ad_dc_ntvfs/private/sam.ldb', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) for env in ["ad_dc_ntvfs", "fl2000dc", "fl2003dc", "fl2008r2dc"]: plantestsuite_loadlist("samba4.ldap_schema.python(%s)" % env, env, [python, os.path.join(samba4srcdir, "dsdb/tests/python/ldap_schema.py"), '$SERVER', '-U"$USERNAME%$PASSWORD"', '--workgroup=$DOMAIN', '$LOADLIST', '$LISTOPT']) -- 2.7.4 >From e3340e9b41f1e0c755dc78472818dc697328d7bf Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Tue, 28 Jun 2016 13:58:41 +1200 Subject: [PATCH 09/10] s4/selftest/provisions/dump.sh: dump to target dir if supplied This is clearly what was meant to happen. Signed-off-by: Douglas Bagnall --- source4/selftest/provisions/dump.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/source4/selftest/provisions/dump.sh b/source4/selftest/provisions/dump.sh index 3616d56..1b3ec1e 100755 --- a/source4/selftest/provisions/dump.sh +++ b/source4/selftest/provisions/dump.sh @@ -25,7 +25,7 @@ cd $dirbase for f in $(find . -name '*.tdb'); do dname=$TARGETDIR/$(dirname $f) mkdir -p $dname - outname=$f.dump + outname=$dname/$(basename $f).dump echo "Dumping $f to $outname" $TDBDUMP $f > $outname || { echo "Failed to dump to $outname" @@ -37,7 +37,7 @@ done for f in $(find . -name '*.ldb'); do dname=$TARGETDIR/$(dirname $f) mkdir -p $dname - outname=$f.dump + outname=$dname/$(basename $f).dump echo "Dumping $f to $outname" $TDBDUMP $f > $outname || { echo "Failed to dump to $outname" -- 2.7.4 >From 08ffc09e8b590043f24b1762aca75e6a6897bea2 Mon Sep 17 00:00:00 2001 From: Douglas Bagnall Date: Fri, 1 Jul 2016 11:22:11 +1200 Subject: [PATCH 10/10] blackbox/dbcheck-oldrelease: more accurate temp filename Signed-off-by: Douglas Bagnall --- testprogs/blackbox/dbcheck-oldrelease.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testprogs/blackbox/dbcheck-oldrelease.sh b/testprogs/blackbox/dbcheck-oldrelease.sh index d2226c6..45d36fe 100755 --- a/testprogs/blackbox/dbcheck-oldrelease.sh +++ b/testprogs/blackbox/dbcheck-oldrelease.sh @@ -192,7 +192,7 @@ dbcheck() { check_expected_after_values() { if [ x$RELEASE = x"release-4-1-0rc3" ]; then - tmpldif=$PREFIX_ABS/$RELEASE/expected-replpropertymetadata-before-dbcheck.ldif.tmp + tmpldif=$PREFIX_ABS/$RELEASE/expected-replpropertymetadata-after-dbcheck.ldif.tmp TZ=UTC $ldbsearch -H tdb://$PREFIX_ABS/${RELEASE}/private/sam.ldb cn=ops_run_anything -s one -b OU=SUDOers,DC=release-4-1-0rc3,DC=samba,DC=corp \* replpropertymetadata --sorted --show-binary > $tmpldif diff $tmpldif $release_dir/expected-replpropertymetadata-after-dbcheck.ldif if [ "$?" != "0" ]; then -- 2.7.4