[PATCH] Fix dn+binary linked attribute behavior (bug 11139)

Bob Campbell bobcampbell at catalyst.net.nz
Tue Feb 7 22:25:38 UTC 2017


Hi again -

If samldb_generate_next_linkid encountered an error, it would return
that error code... which would then be interpreted as a linkID. Oops.
These patches are updated to fix this.

Thanks,
Bob

On 08/02/17 10:33, Bob Campbell wrote:
> Hi,
>
> Here's an updated patchset for the dn+binary linked attribute fixes.
> This patchset adds a couple of things only tangentially related to the
> actual dn+binary fixes.
>
> Firstly, we now support MS-ADTS 3.1.1.2.3.1. In short, assigning the OID
> 1.2.840.113556.1.2.50 as the linkID of a new attribute now makes us
> generate a new, unused, even linkID. Also, assigning an attributeID or
> ldapDisplayName of an existing forward link as the linkID of a new
> attribute makes the new attribute a backlink of the specified one. They
> build on top of my earlier patches which disallow adding duplicate
> linkIDs. We've ordered the patches such that we don't need to put an
> entry into knownfail, because it seems that this test failing tends to
> mess up the tests after it since it modifies the schema.
>
> One thing that would be great to do in the future is to use an unsigned
> int for linkID everywhere in the code, but we're not sure that we do
> this, which may cause compatibility issues with Windows, since Windows
> seems to assign values higher than 2^31 to generated linkIDs. We
> definitely compare it as an unsigned int when checking for collisions,
> though.
>
> We got into fixing this because we didn't want to specify a linkID to
> use in the tests, and so we implemented the entire behavior for
> generating linkIDs and a test.
>
> Secondly, we added the "vampire_2000_dc" environment so that we can run
> the repl_schema tests on it as well.
>
> Please review and push if appropriate.
>
> Thanks,
> Bob

-------------- next part --------------
From e41aee836a90af1f69d1f3ff195be51ab409181a Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Tue, 7 Feb 2017 14:42:56 +1300
Subject: [PATCH 1/9] dsdb/common: add function to get functional level of a
 single dc

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 source4/dsdb/common/util.c | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/source4/dsdb/common/util.c b/source4/dsdb/common/util.c
index 8f74b45..f108ac3 100644
--- a/source4/dsdb/common/util.c
+++ b/source4/dsdb/common/util.c
@@ -4,7 +4,7 @@
 
    Copyright (C) Andrew Tridgell 2004
    Copyright (C) Volker Lendecke 2004
-   Copyright (C) Andrew Bartlett <abartlet at samba.org> 2006
+   Copyright (C) Andrew Bartlett <abartlet at samba.org> 2017
    Copyright (C) Jelmer Vernooij <jelmer at samba.org> 2007
 
    This program is free software; you can redistribute it and/or modify
@@ -3662,6 +3662,21 @@ int dsdb_functional_level(struct ldb_context *ldb)
 }
 
 /*
+ * This detects and returns the functional level of a dc (DS_DOMAIN_FUNCTION_*)
+ */
+int dsdb_dc_functional_level(struct ldb_context *ldb)
+{
+	int *domainControllerFunctionality =
+		talloc_get_type(ldb_get_opaque(ldb, "domainControllerFunctionality"), int);
+	if (!domainControllerFunctionality) {
+		/* this is expected during initial provision */
+		DEBUG(4,(__location__ ": WARNING: domainControllerFunctionality not setup\n"));
+		return DS_DOMAIN_FUNCTION_2000;
+	}
+	return *domainControllerFunctionality;
+}
+
+/*
  * This detects and returns the forest functional level (DS_DOMAIN_FUNCTION_*)
  */
 int dsdb_forest_functional_level(struct ldb_context *ldb)
-- 
1.9.1


From c1c12ee73418cd4a69f7458ba2e1b1f4b862ffef Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Wed, 1 Feb 2017 11:54:40 +1300
Subject: [PATCH 2/9] samldb: Prevent duplicate linkID in schema

Previously, we could run into issues when trying to delete backlinks with
duplicate linkIDs. The deletion code could try to follow the incorrect
attribute, thus failing to remove the backlink.

Signed-off-by: Bob Campbell <bobcampbell at catalyst.net.nz>
Pair-programmed-with: Andrew Bartlett <abartlet at samba.org>
---
 source4/dsdb/samdb/ldb_modules/samldb.c | 36 +++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c
index b33cf24..6792b2a 100644
--- a/source4/dsdb/samdb/ldb_modules/samldb.c
+++ b/source4/dsdb/samdb/ldb_modules/samldb.c
@@ -272,6 +272,37 @@ static int samldb_schema_ldapdisplayname_valid_check(struct samldb_ctx *ac)
 	return ret;
 }
 
+static int samldb_schema_linkid_valid_check(struct samldb_ctx *ac)
+{
+	int ret;
+	bool is_zero;
+	struct ldb_message_element *el;
+	const char *enc_str;
+	struct ldb_dn *schema_dn;
+
+	el = dsdb_get_single_valued_attr(ac->msg, "linkID",
+					 ac->req->operation);
+	if (el == NULL) {
+		return LDB_SUCCESS;
+	}
+
+	enc_str = ldb_binary_encode(ac, el->values[0]);
+	if (enc_str == NULL) {
+		return ldb_module_oom(ac->module);
+	}
+
+	is_zero = strcmp(enc_str, "0") == 0;
+	if (is_zero) {
+		return LDB_SUCCESS;
+	}
+
+	schema_dn = ldb_get_schema_basedn(ldb_module_get_ctx(ac->module));
+	ret = samldb_unique_attr_check(ac, "linkID", NULL, schema_dn);
+	if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+		ret = LDB_ERR_UNWILLING_TO_PERFORM;
+	}
+	return ret;
+}
 
 /* sAMAccountName handling */
 
@@ -3227,6 +3258,11 @@ static int samldb_add(struct ldb_module *module, struct ldb_request *req)
 			if (ret != LDB_SUCCESS) {
 				return ret;
 			}
+
+			ret = samldb_schema_linkid_valid_check(ac);
+			if (ret != LDB_SUCCESS) {
+				return ret;
+			}
 		}
 
 		ret = samldb_schema_ldapdisplayname_valid_check(ac);
-- 
1.9.1


From c0478024ff4dc8f671071c00101be5a9eec3e48d Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Wed, 1 Feb 2017 16:38:34 +1300
Subject: [PATCH 3/9] samldb: Allow automatic generation of linkIDs

As per MS-ADTS 3.1.1.2.3.1, this allows specifying the OID
1.2.840.113556.1.2.50 as the linkID of a new linked attribute in the
schema in order to automatically assign it an unused even linkID.

Specifying the attributeID or ldapDisplayName of an existing forward
link will now also add the new linked attribute as the backlink of that
existing link.

Signed-off-by: Bob Campbell <bobcampbell at catalyst.net.nz>
---
 source4/dsdb/samdb/ldb_modules/samldb.c | 184 ++++++++++++++++++++++++++++----
 1 file changed, 164 insertions(+), 20 deletions(-)

diff --git a/source4/dsdb/samdb/ldb_modules/samldb.c b/source4/dsdb/samdb/ldb_modules/samldb.c
index 6792b2a..9c341d4 100644
--- a/source4/dsdb/samdb/ldb_modules/samldb.c
+++ b/source4/dsdb/samdb/ldb_modules/samldb.c
@@ -5,6 +5,7 @@
    Copyright (C) Simo Sorce  2004-2008
    Copyright (C) Matthias Dieter Wallnöfer 2009-2011
    Copyright (C) Matthieu Patou 2012
+   Copyright (C) Catalyst.Net Ltd 2017
 
    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
@@ -272,36 +273,179 @@ static int samldb_schema_ldapdisplayname_valid_check(struct samldb_ctx *ac)
 	return ret;
 }
 
-static int samldb_schema_linkid_valid_check(struct samldb_ctx *ac)
+static int samldb_check_linkid_used(struct samldb_ctx *ac,
+				    struct dsdb_schema *schema,
+				    struct ldb_dn *schema_dn,
+				    struct ldb_context *ldb,
+				    int32_t linkID,
+				    bool *found)
 {
 	int ret;
-	bool is_zero;
-	struct ldb_message_element *el;
-	const char *enc_str;
-	struct ldb_dn *schema_dn;
+	struct ldb_result *ldb_res;
 
-	el = dsdb_get_single_valued_attr(ac->msg, "linkID",
-					 ac->req->operation);
-	if (el == NULL) {
+	if (dsdb_attribute_by_linkID(schema, linkID)) {
+		*found = true;
 		return LDB_SUCCESS;
 	}
 
-	enc_str = ldb_binary_encode(ac, el->values[0]);
-	if (enc_str == NULL) {
-		return ldb_module_oom(ac->module);
+	ret = dsdb_module_search(ac->module, ac,
+				 &ldb_res,
+				 schema_dn, LDB_SCOPE_ONELEVEL, NULL,
+				 DSDB_FLAG_NEXT_MODULE,
+				 ac->req,
+				 "(linkID=%d)", linkID);
+	if (ret != LDB_SUCCESS) {
+		ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+			      __location__": Searching for linkID=%d failed - %s\n",
+			      linkID,
+			      ldb_errstring(ldb));
+		return ldb_operr(ldb);
 	}
 
-	is_zero = strcmp(enc_str, "0") == 0;
-	if (is_zero) {
-		return LDB_SUCCESS;
+	*found = (ldb_res->count != 0);
+	talloc_free(ldb_res);
+
+	return LDB_SUCCESS;
+}
+
+/* Find the next open forward linkID in the schema. */
+static int samldb_generate_next_linkid(struct samldb_ctx *ac,
+				       struct dsdb_schema *schema,
+				       int32_t *next_linkID)
+{
+	int ret;
+	struct ldb_context *ldb;
+	struct ldb_dn *schema_dn;
+	bool linkID_used = true;
+
+	/*
+	 * Windows starts at about 0xB0000000 in order to stop potential
+	 * collisions with future additions to the schema. We pass this
+	 * around as a signed int sometimes, but this should be sufficient.
+	 */
+	*next_linkID = 0x40000000;
+
+	ldb = ldb_module_get_ctx(ac->module);
+	schema_dn = ldb_get_schema_basedn(ldb);
+
+	while (linkID_used) {
+		*next_linkID += 2;
+		ret = samldb_check_linkid_used(ac, schema,
+					       schema_dn, ldb,
+					       *next_linkID, &linkID_used);
+		if (ret != LDB_SUCCESS) {
+			return ret;
+		}
+	}
+
+	return LDB_SUCCESS;
+}
+
+static int samldb_schema_add_handle_linkid(struct samldb_ctx *ac)
+{
+	int ret;
+	bool ok, found;
+	struct ldb_message_element *el;
+	const char *enc_str;
+	const struct dsdb_attribute *attr;
+	struct ldb_context *ldb;
+	struct ldb_dn *schema_dn;
+	unsigned int functional_level;
+	struct dsdb_schema *schema;
+	int32_t new_linkID = 0;
+
+	ldb = ldb_module_get_ctx(ac->module);
+	functional_level = dsdb_dc_functional_level(ldb);
+
+	/*
+	 * In a functional level of >= Win2003, the linkID must
+	 * be processed in extra ways shown in [MS-ADTS] 3.1.1.2.3.1
+	 */
+	if (functional_level >= DS_DOMAIN_FUNCTION_2003) {
+		schema = dsdb_get_schema(ldb, ac);
+		schema_dn = ldb_get_schema_basedn(ldb);
+
+		el = dsdb_get_single_valued_attr(ac->msg, "linkID",
+						 ac->req->operation);
+		if (el == NULL) {
+			return LDB_SUCCESS;
+		}
+
+		enc_str = ldb_binary_encode(ac, el->values[0]);
+		if (enc_str == NULL) {
+			return ldb_module_oom(ac->module);
+		}
+
+		ok = (strcmp(enc_str, "0") == 0);
+		if (ok) {
+			return LDB_SUCCESS;
+		}
+
+		/*
+		 * This OID indicates that the caller wants the linkID
+		 * to be automatically generated. We therefore assign
+		 * it the next open linkID.
+		 */
+		ok = (strcmp(enc_str, "1.2.840.113556.1.2.50") == 0);
+		if (ok) {
+			ret = samldb_generate_next_linkid(ac, schema, &new_linkID);
+			if (ret != LDB_SUCCESS) {
+				return ret;
+			}
+
+			ldb_msg_remove_element(ac->msg, el);
+			ret = samdb_msg_add_int(ldb, ac->msg, ac->msg, "linkID",
+						new_linkID);
+			return ret;
+		}
+
+		/*
+		 * Using either the attributeID or lDAPDisplayName of
+		 * another attribute in the linkID field indicates that
+		 * we should make this the backlink of that attribute.
+		 */
+		attr = dsdb_attribute_by_attributeID_oid(schema, enc_str);
+		if (attr == NULL) {
+			attr = dsdb_attribute_by_lDAPDisplayName(schema, enc_str);
+		}
+
+		if (attr != NULL) {
+			/*
+			 * The attribute we're adding this as a backlink of must
+			 * be a forward link.
+			 */
+			if (attr->linkID % 2 != 0) {
+				return LDB_ERR_UNWILLING_TO_PERFORM;
+			}
+
+			new_linkID = attr->linkID + 1;
+
+			/* Make sure that this backlink doesn't already exist. */
+			ret = samldb_check_linkid_used(ac, schema,
+						       schema_dn, ldb,
+						       new_linkID, &found);
+			if (ret != LDB_SUCCESS) {
+				return ret;
+			}
+
+			if (found) {
+				return LDB_ERR_UNWILLING_TO_PERFORM;
+			}
+
+			ldb_msg_remove_element(ac->msg, el);
+			ret = samdb_msg_add_int(ldb, ac->msg, ac->msg, "linkID",
+						new_linkID);
+			return ret;
+		}
 	}
 
 	schema_dn = ldb_get_schema_basedn(ldb_module_get_ctx(ac->module));
 	ret = samldb_unique_attr_check(ac, "linkID", NULL, schema_dn);
 	if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
-		ret = LDB_ERR_UNWILLING_TO_PERFORM;
+		return LDB_ERR_UNWILLING_TO_PERFORM;
+	} else {
+		return ret;
 	}
-	return ret;
 }
 
 /* sAMAccountName handling */
@@ -3258,11 +3402,11 @@ static int samldb_add(struct ldb_module *module, struct ldb_request *req)
 			if (ret != LDB_SUCCESS) {
 				return ret;
 			}
+		}
 
-			ret = samldb_schema_linkid_valid_check(ac);
-			if (ret != LDB_SUCCESS) {
-				return ret;
-			}
+		ret = samldb_schema_add_handle_linkid(ac);
+		if (ret != LDB_SUCCESS) {
+			return ret;
 		}
 
 		ret = samldb_schema_ldapdisplayname_valid_check(ac);
-- 
1.9.1


From 8b457758cb0ff9402d8fd4b6b992f64304181c63 Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Tue, 7 Feb 2017 15:42:29 +1300
Subject: [PATCH 4/9] torture/drs: generate linkID for test rather than
 specifying

Signed-off-by: Bob Campbell <bobcampbell at catalyst.net.nz>
---
 source4/torture/drs/python/repl_schema.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/source4/torture/drs/python/repl_schema.py b/source4/torture/drs/python/repl_schema.py
index 865260c..1387ec2 100644
--- a/source4/torture/drs/python/repl_schema.py
+++ b/source4/torture/drs/python/repl_schema.py
@@ -242,7 +242,7 @@ class DrsReplSchemaTestCase(drs_base.DrsBaseTestCase):
            AttributeID_id in Schema cache"""
         # add new attributeSchema object
         (a_ldn, a_dn) = self._schema_new_attr(self.ldb_dc1, "attr-Link-X", 10,
-                                              attrs={'linkID':"99990",
+                                              attrs={'linkID':"1.2.840.113556.1.2.50",
                                                      "attributeSyntax": "2.5.5.1",
                                                      "omSyntax": "127"})
         # add a base classSchema class so we can use our new
-- 
1.9.1


From 1838452ef2381094e3fb5c3de7cc5168b9f4e34e Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Thu, 2 Feb 2017 09:46:26 +1300
Subject: [PATCH 5/9] python/tests: add test for generated and duplicate
 linkIDs

Signed-off-by: Bob Campbell <bobcampbell at catalyst.net.nz>
---
 source4/dsdb/tests/python/ldap_schema.py | 285 +++++++++++++++++++++++++++++++
 1 file changed, 285 insertions(+)

diff --git a/source4/dsdb/tests/python/ldap_schema.py b/source4/dsdb/tests/python/ldap_schema.py
index c920296..6263a57 100755
--- a/source4/dsdb/tests/python/ldap_schema.py
+++ b/source4/dsdb/tests/python/ldap_schema.py
@@ -3,6 +3,7 @@
 # This is a port of the original in testprogs/ejs/ldap.js
 
 # Copyright (C) Jelmer Vernooij <jelmer at samba.org> 2008-2011
+# Copyright (C) Catalyst.Net Ltd 2017
 #
 # 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
@@ -817,6 +818,290 @@ attributeId: """ + attributeID + """
             self.assertEquals(enum, ERR_CONSTRAINT_VIOLATION)
 
 
+    def test_generated_linkID(self):
+        """
+        Test that we automatically generate a linkID if the
+        OID "1.2.840.113556.1.2.50" is given as the linkID
+        of a new attribute, and that we don't get/can't add
+        duplicate linkIDs. Also test that we can add a backlink
+        by providing the attributeID or ldapDisplayName of
+        a forwards link in the linkID attribute.
+        """
+
+        # linkID generation isn't available before 2003
+        res = self.ldb.search(base="", expression="", scope=SCOPE_BASE,
+                         attrs=["domainControllerFunctionality"])
+        self.assertEquals(len(res), 1)
+        dc_level = int(res[0]["domainControllerFunctionality"][0])
+        if dc_level < DS_DOMAIN_FUNCTION_2003:
+            return
+
+        rand = str(random.randint(1,100000))
+
+        attr_name_1 = "test-generated-linkID" + time.strftime("%s", time.gmtime()) + "-" + rand
+        attr_ldap_display_name_1 = attr_name_1.replace("-", "")
+        attributeID_1 = "1.3.6.1.4.1.7165.4.6.1.6.16." + rand
+        ldif = """
+dn: CN=%s,%s""" % (attr_name_1, self.schema_dn) + """
+objectClass: top
+objectClass: attributeSchema
+adminDescription: """ + attr_name_1 + """
+adminDisplayName: """ + attr_name_1 + """
+cn: """ + attr_name_1 + """
+attributeId: """ + attributeID_1 + """
+linkID: 1.2.840.113556.1.2.50
+attributeSyntax: 2.5.5.1
+ldapDisplayName: """ + attr_ldap_display_name_1 + """
+omSyntax: 127
+instanceType: 4
+isSingleValued: TRUE
+systemOnly: FALSE
+"""
+
+        try:
+            self.ldb.add_ldif(ldif)
+        except LdbError, (enum, estr):
+            self.fail(estr)
+
+        attr_name_2 = "test-generated-linkID-2" + time.strftime("%s", time.gmtime()) + "-" + rand
+        attr_ldap_display_name_2 = attr_name_2.replace("-", "")
+        attributeID_2 = "1.3.6.1.4.1.7165.4.6.1.6.17." + rand
+        ldif = """
+dn: CN=%s,%s""" % (attr_name_2, self.schema_dn) + """
+objectClass: top
+objectClass: attributeSchema
+adminDescription: """ + attr_name_2 + """
+adminDisplayName: """ + attr_name_2 + """
+cn: """ + attr_name_2 + """
+attributeId: """ + attributeID_2 + """
+linkID: 1.2.840.113556.1.2.50
+attributeSyntax: 2.5.5.1
+ldapDisplayName: """ + attr_ldap_display_name_2 + """
+omSyntax: 127
+instanceType: 4
+isSingleValued: TRUE
+systemOnly: FALSE
+"""
+
+        try:
+            self.ldb.add_ldif(ldif)
+        except LdbError, (enum, estr):
+            self.fail(estr)
+
+        res = self.ldb.search("CN=%s,%s" % (attr_name_1, self.schema_dn),
+                              scope=SCOPE_BASE,
+                              attrs=["linkID"])
+        self.assertEquals(len(res), 1)
+        linkID_1 = int(res[0]["linkID"][0])
+
+        res = self.ldb.search("CN=%s,%s" % (attr_name_2, self.schema_dn),
+                              scope=SCOPE_BASE,
+                              attrs=["linkID"])
+        self.assertEquals(len(res), 1)
+        linkID_2 = int(res[0]["linkID"][0])
+
+        # 0 should never be generated as a linkID
+        self.assertFalse(linkID_1 == 0)
+        self.assertFalse(linkID_2 == 0)
+
+        # The generated linkID should always be even, because
+        # it should assume we're adding a forward link.
+        self.assertTrue(linkID_1 % 2 == 0)
+        self.assertTrue(linkID_2 % 2 == 0)
+
+        self.assertFalse(linkID_1 == linkID_2)
+
+        # This is only necessary against Windows, since we depend
+        # on the previously added links in the next ones and Windows
+        # won't refresh the schema as we add them.
+        ldif = """
+dn:
+changetype: modify
+replace: schemaupdatenow
+schemaupdatenow: 1
+"""
+        self.ldb.modify_ldif(ldif)
+
+        # If we add a new link with the same linkID, it should fail
+        attr_name = "test-generated-linkID-duplicate" + time.strftime("%s", time.gmtime()) + "-" + rand
+        attr_ldap_display_name = attr_name.replace("-", "")
+        attributeID = "1.3.6.1.4.1.7165.4.6.1.6.18." + rand
+        ldif = """
+dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
+objectClass: top
+objectClass: attributeSchema
+adminDescription: """ + attr_name + """
+adminDisplayName: """ + attr_name + """
+cn: """ + attr_name + """
+attributeId: """ + attributeID + """
+linkID: """ + str(linkID_1) + """
+attributeSyntax: 2.5.5.1
+ldapDisplayName: """ + attr_ldap_display_name + """
+omSyntax: 127
+instanceType: 4
+isSingleValued: TRUE
+systemOnly: FALSE
+"""
+
+        try:
+            self.ldb.add_ldif(ldif)
+            self.fail("Should have failed to add duplicate linkID value")
+        except LdbError, (enum, estr):
+            self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
+
+        # If we add another attribute with the attributeID or lDAPDisplayName
+        # of a forward link in its linkID field, it should add as a backlink
+
+        attr_name_3 = "test-generated-linkID-backlink" + time.strftime("%s", time.gmtime()) + "-" + rand
+        attr_ldap_display_name_3 = attr_name_3.replace("-", "")
+        attributeID_3 = "1.3.6.1.4.1.7165.4.6.1.6.19." + rand
+        ldif = """
+dn: CN=%s,%s""" % (attr_name_3, self.schema_dn) + """
+objectClass: top
+objectClass: attributeSchema
+adminDescription: """ + attr_name_3 + """
+adminDisplayName: """ + attr_name_3 + """
+cn: """ + attr_name_3 + """
+attributeId: """ + attributeID_3 + """
+linkID: """ + str(linkID_1+1) + """
+attributeSyntax: 2.5.5.1
+ldapDisplayName: """ + attr_ldap_display_name_3 + """
+omSyntax: 127
+instanceType: 4
+isSingleValued: TRUE
+systemOnly: FALSE
+"""
+
+        try:
+            self.ldb.add_ldif(ldif)
+        except LdbError, (enum, estr):
+            self.fail(estr)
+
+        res = self.ldb.search("CN=%s,%s" % (attr_name_3, self.schema_dn),
+                              scope=SCOPE_BASE,
+                              attrs=["linkID"])
+        self.assertEquals(len(res), 1)
+        linkID = int(res[0]["linkID"][0])
+        self.assertEquals(linkID, linkID_1 + 1)
+
+        attr_name_4 = "test-generated-linkID-backlink-2" + time.strftime("%s", time.gmtime()) + "-" + rand
+        attr_ldap_display_name_4 = attr_name_4.replace("-", "")
+        attributeID_4 = "1.3.6.1.4.1.7165.4.6.1.6.20." + rand
+        ldif = """
+dn: CN=%s,%s""" % (attr_name_4, self.schema_dn) + """
+objectClass: top
+objectClass: attributeSchema
+adminDescription: """ + attr_name_4 + """
+adminDisplayName: """ + attr_name_4 + """
+cn: """ + attr_name_4 + """
+attributeId: """ + attributeID_4 + """
+linkID: """ + attr_ldap_display_name_2 + """
+attributeSyntax: 2.5.5.1
+ldapDisplayName: """ + attr_ldap_display_name_4 + """
+omSyntax: 127
+instanceType: 4
+isSingleValued: TRUE
+systemOnly: FALSE
+"""
+
+        try:
+            self.ldb.add_ldif(ldif)
+        except LdbError, (enum, estr):
+            self.fail(estr)
+
+        res = self.ldb.search("CN=%s,%s" % (attr_name_4, self.schema_dn),
+                              scope=SCOPE_BASE,
+                              attrs=["linkID"])
+        self.assertEquals(len(res), 1)
+        linkID = int(res[0]["linkID"][0])
+        self.assertEquals(linkID, linkID_2 + 1)
+
+        # If we then try to add another backlink in the same way
+        # for the same forwards link, we should fail.
+
+        attr_name = "test-generated-linkID-backlink-duplicate" + time.strftime("%s", time.gmtime()) + "-" + rand
+        attr_ldap_display_name = attr_name.replace("-", "")
+        attributeID = "1.3.6.1.4.1.7165.4.6.1.6.21." + rand
+        ldif = """
+dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
+objectClass: top
+objectClass: attributeSchema
+adminDescription: """ + attr_name + """
+adminDisplayName: """ + attr_name + """
+cn: """ + attr_name + """
+attributeId: """ + attributeID + """
+linkID: """ + attributeID_1 + """
+attributeSyntax: 2.5.5.1
+ldapDisplayName: """ + attr_ldap_display_name + """
+omSyntax: 127
+instanceType: 4
+isSingleValued: TRUE
+systemOnly: FALSE
+"""
+
+        try:
+            self.ldb.add_ldif(ldif)
+            self.fail("Should have failed to add duplicate backlink")
+        except LdbError, (enum, estr):
+            self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
+
+        # If we try to supply the attributeID or ldapDisplayName
+        # of an existing backlink in the linkID field of a new link,
+        # it should fail.
+
+        attr_name = "test-generated-linkID-backlink-invalid" + time.strftime("%s", time.gmtime()) + "-" + rand
+        attr_ldap_display_name = attr_name.replace("-", "")
+        attributeID = "1.3.6.1.4.1.7165.4.6.1.6.22." + rand
+        ldif = """
+dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
+objectClass: top
+objectClass: attributeSchema
+adminDescription: """ + attr_name + """
+adminDisplayName: """ + attr_name + """
+cn: """ + attr_name + """
+attributeId: """ + attributeID + """
+linkID: """ + attributeID_3 + """
+attributeSyntax: 2.5.5.1
+ldapDisplayName: """ + attr_ldap_display_name + """
+omSyntax: 127
+instanceType: 4
+isSingleValued: TRUE
+systemOnly: FALSE
+"""
+
+        try:
+            self.ldb.add_ldif(ldif)
+            self.fail("Should have failed to add backlink of backlink")
+        except LdbError, (enum, estr):
+            self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
+
+        attr_name = "test-generated-linkID-backlink-invalid-2" + time.strftime("%s", time.gmtime()) + "-" + rand
+        attr_ldap_display_name = attr_name.replace("-", "")
+        attributeID = "1.3.6.1.4.1.7165.4.6.1.6.23." + rand
+        ldif = """
+dn: CN=%s,%s""" % (attr_name, self.schema_dn) + """
+objectClass: top
+objectClass: attributeSchema
+adminDescription: """ + attr_name + """
+adminDisplayName: """ + attr_name + """
+cn: """ + attr_name + """
+attributeId: """ + attributeID + """
+linkID: """ + attr_ldap_display_name_4 + """
+attributeSyntax: 2.5.5.1
+ldapDisplayName: """ + attr_ldap_display_name + """
+omSyntax: 127
+instanceType: 4
+isSingleValued: TRUE
+systemOnly: FALSE
+"""
+
+        try:
+            self.ldb.add_ldif(ldif)
+            self.fail("Should have failed to add backlink of backlink")
+        except LdbError, (enum, estr):
+            self.assertEquals(enum, ERR_UNWILLING_TO_PERFORM)
+
+
     def test_change_governsID(self):
         """Testing change the governsID"""
         rand = str(random.randint(1,100000))
-- 
1.9.1


From 10240459e6045bb9673aa320022b2fb90cc0936a Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Wed, 8 Feb 2017 09:16:41 +1300
Subject: [PATCH 6/9] selftest: add vampire_2000_dc environment

This is the equivalent of vampire_dc, but using a domain functional
level of DS_DOMAIN_FUNCTION_2000.

Using this functional level is useful for tests involving replication
and linked attributes, as they behave differently at it.

Signed-off-by: Andrew Bartlett <abartlet at samba.org>
Pair-programmed-with: Bob Campbell <bobcampbell at catalyst.net.nz>
---
 selftest/selftest.pl      |  7 +++++++
 selftest/target/Samba.pm  |  1 +
 selftest/target/Samba4.pm | 46 +++++++++++++++++++++++++++++++---------------
 3 files changed, 39 insertions(+), 15 deletions(-)

diff --git a/selftest/selftest.pl b/selftest/selftest.pl
index 14ac58f..933ce3c 100755
--- a/selftest/selftest.pl
+++ b/selftest/selftest.pl
@@ -811,6 +811,13 @@ my @exported_envvars = (
 	"VAMPIRE_DC_NETBIOSNAME",
 	"VAMPIRE_DC_NETBIOSALIAS",
 
+	# domain controller stuff for FL 2000 Vampired DC
+	"VAMPIRE_2000_DC_SERVER",
+	"VAMPIRE_2000_DC_SERVER_IP",
+	"VAMPIRE_2000_DC_SERVER_IPV6",
+	"VAMPIRE_2000_DC_NETBIOSNAME",
+	"VAMPIRE_2000_DC_NETBIOSALIAS",
+
 	"PROMOTED_DC_SERVER",
 	"PROMOTED_DC_SERVER_IP",
 	"PROMOTED_DC_SERVER_IPV6",
diff --git a/selftest/target/Samba.pm b/selftest/target/Samba.pm
index 24484c9..e5c7f93 100644
--- a/selftest/target/Samba.pm
+++ b/selftest/target/Samba.pm
@@ -308,6 +308,7 @@ sub get_interface($)
     $interfaces{"fakednsforwarder1"} = 36;
     $interfaces{"fakednsforwarder2"} = 37;
     $interfaces{"s4member_dflt"} = 38;
+    $interfaces{"vampire2000dc"} = 39;
 
     # update lib/socket_wrapper/socket_wrapper.c
     #  #define MAX_WRAPPED_INTERFACES 40
diff --git a/selftest/target/Samba4.pm b/selftest/target/Samba4.pm
index dacdab4..8b5e699 100755
--- a/selftest/target/Samba4.pm
+++ b/selftest/target/Samba4.pm
@@ -1246,15 +1246,20 @@ sub provision_promoted_dc($$$)
 
 sub provision_vampire_dc($$$)
 {
-	my ($self, $prefix, $dcvars) = @_;
-	print "PROVISIONING VAMPIRE DC...\n";
+	my ($self, $prefix, $dcvars, $fl) = @_;
+	print "PROVISIONING VAMPIRE DC @ FL $fl...\n";
+	my $name = "localvampiredc";
+
+	if ($fl == "2000") {
+	    $name = "vampire2000dc";
+	}
 
 	# We do this so that we don't run the provision.  That's the job of 'net vampire'.
 	my $ctx = $self->provision_raw_prepare($prefix, "domain controller",
-					       "localvampiredc",
-					       "SAMBADOMAIN",
-					       "samba.example.com",
-					       "2008",
+					       $name,
+					       $dcvars->{DOMAIN},
+					       $dcvars->{REALM},
+					       $fl,
 					       $dcvars->{PASSWORD},
 					       $dcvars->{SERVER_IP},
 					       $dcvars->{SERVER_IPV6});
@@ -1299,11 +1304,17 @@ sub provision_vampire_dc($$$)
 		return undef;
 	}
 
-	$ret->{VAMPIRE_DC_SERVER} = $ret->{SERVER};
-	$ret->{VAMPIRE_DC_SERVER_IP} = $ret->{SERVER_IP};
-	$ret->{VAMPIRE_DC_SERVER_IPV6} = $ret->{SERVER_IPV6};
-	$ret->{VAMPIRE_DC_NETBIOSNAME} = $ret->{NETBIOSNAME};
-
+        if ($fl == "2000") {
+		$ret->{VAMPIRE_2000_DC_SERVER} = $ret->{SERVER};
+		$ret->{VAMPIRE_2000_DC_SERVER_IP} = $ret->{SERVER_IP};
+		$ret->{VAMPIRE_2000_DC_SERVER_IPV6} = $ret->{SERVER_IPV6};
+		$ret->{VAMPIRE_2000_DC_NETBIOSNAME} = $ret->{NETBIOSNAME};
+        } else {
+		$ret->{VAMPIRE_DC_SERVER} = $ret->{SERVER};
+		$ret->{VAMPIRE_DC_SERVER_IP} = $ret->{SERVER_IP};
+		$ret->{VAMPIRE_DC_SERVER_IPV6} = $ret->{SERVER_IPV6};
+		$ret->{VAMPIRE_DC_NETBIOSNAME} = $ret->{NETBIOSNAME};
+        }
 	$ret->{DC_SERVER} = $dcvars->{DC_SERVER};
 	$ret->{DC_SERVER_IP} = $dcvars->{DC_SERVER_IP};
 	$ret->{DC_SERVER_IPV6} = $dcvars->{DC_SERVER_IPV6};
@@ -2002,6 +2013,11 @@ sub setup_env($$$)
 		return $self->setup_ad_dc_ntvfs("$path/ad_dc_ntvfs");
 	} elsif ($envname eq "fl2000dc") {
 		return $self->setup_fl2000dc("$path/fl2000dc");
+	} elsif ($envname eq "vampire_2000_dc") {
+		if (not defined($self->{vars}->{fl2000dc})) {
+			$self->setup_fl2000dc("$path/fl2000dc");
+		}
+		return $self->setup_vampire_dc("$path/vampire_2000_dc", $self->{vars}->{fl2000dc}, "2000");
 	} elsif ($envname eq "fl2003dc") {
 		if (not defined($self->{vars}->{ad_dc})) {
 			$self->setup_ad_dc("$path/ad_dc");
@@ -2021,7 +2037,7 @@ sub setup_env($$$)
 		if (not defined($self->{vars}->{ad_dc_ntvfs})) {
 			$self->setup_ad_dc_ntvfs("$path/ad_dc_ntvfs");
 		}
-		return $self->setup_vampire_dc("$path/vampire_dc", $self->{vars}->{ad_dc_ntvfs});
+		return $self->setup_vampire_dc("$path/vampire_dc", $self->{vars}->{ad_dc_ntvfs}, "2008");
 	} elsif ($envname eq "promoted_dc") {
 		if (not defined($self->{vars}->{ad_dc_ntvfs})) {
 			$self->setup_ad_dc_ntvfs("$path/ad_dc_ntvfs");
@@ -2211,11 +2227,11 @@ sub setup_fl2008r2dc($$$)
 	return $env;
 }
 
-sub setup_vampire_dc($$$)
+sub setup_vampire_dc($$$$)
 {
-	my ($self, $path, $dc_vars) = @_;
+	my ($self, $path, $dc_vars, $fl) = @_;
 
-	my $env = $self->provision_vampire_dc($path, $dc_vars);
+	my $env = $self->provision_vampire_dc($path, $dc_vars, $fl);
 
 	if (defined $env) {
 	        if (not defined($self->check_or_start($env, "single"))) {
-- 
1.9.1


From 32b799fcf8f2dfffe047b6ad8c6617e005fe8a88 Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Fri, 3 Feb 2017 10:33:54 +1300
Subject: [PATCH 7/9] torture/drs: run repl_schema in vampire_2000_dc
 environment as well

This will be necessary as linked attributes are handled differently in
Windows 2000.

We also only check msDS-IntId if we have a functional level of > Windows
2000, as this attribute is not present on lower domain function levels.

Signed-off-by: Bob Campbell <bobcampbell at catalyst.net.nz>
---
 source4/selftest/tests.py                 | 11 ++++++-----
 source4/torture/drs/python/repl_schema.py | 20 +++++++++++++-------
 2 files changed, 19 insertions(+), 12 deletions(-)

diff --git a/source4/selftest/tests.py b/source4/selftest/tests.py
index 98974dc..016ce51 100755
--- a/source4/selftest/tests.py
+++ b/source4/selftest/tests.py
@@ -683,11 +683,6 @@ for env in ['vampire_dc', 'promoted_dc']:
                            extra_path=[os.path.join(samba4srcdir, 'torture/drs/python')],
                            environ={'DC1': "$DC_SERVER", 'DC2': '$%s_SERVER' % env.upper()},
                            extra_args=['-U$DOMAIN/$DC_USERNAME%$DC_PASSWORD'])
-    planoldpythontestsuite(env, "repl_schema",
-                           extra_path=[os.path.join(samba4srcdir, 'torture/drs/python')],
-                           name="samba4.drs.repl_schema.python(%s)" % env,
-                           environ={'DC1': "$DC_SERVER", 'DC2': '$%s_SERVER' % env.upper()},
-                           extra_args=['-U$DOMAIN/$DC_USERNAME%$DC_PASSWORD'])
     planoldpythontestsuite(env, "repl_move",
                            extra_path=[os.path.join(samba4srcdir, 'torture/drs/python')],
                            name="samba4.drs.repl_move.python(%s)" % env,
@@ -704,6 +699,12 @@ for env in ['vampire_dc', 'promoted_dc']:
                            environ={'DC1': "$DC_SERVER", 'DC2': '$%s_SERVER' % env.upper()},
                            extra_args=['-U$DOMAIN/$DC_USERNAME%$DC_PASSWORD'])
 
+for env in ['vampire_dc', 'promoted_dc', 'vampire_2000_dc']:
+    planoldpythontestsuite(env, "repl_schema",
+                           extra_path=[os.path.join(samba4srcdir, 'torture/drs/python')],
+                           name="samba4.drs.repl_schema.python(%s)" % env,
+                           environ={'DC1': "$DC_SERVER", 'DC2': '$%s_SERVER' % env.upper()},
+                           extra_args=['-U$DOMAIN/$DC_USERNAME%$DC_PASSWORD'])
 
 planoldpythontestsuite("chgdcpass:local", "samba.tests.blackbox.samba_dnsupdate",
                        environ={'DNS_SERVER_IP': '$SERVER_IP'})
diff --git a/source4/torture/drs/python/repl_schema.py b/source4/torture/drs/python/repl_schema.py
index 1387ec2..cf02608 100644
--- a/source4/torture/drs/python/repl_schema.py
+++ b/source4/torture/drs/python/repl_schema.py
@@ -43,6 +43,7 @@ import ldb
 import drs_base
 from samba.dcerpc import drsuapi, misc
 from samba.drs_utils import drs_DsBind
+from samba import dsdb
 
 class DrsReplSchemaTestCase(drs_base.DrsBaseTestCase):
 
@@ -262,14 +263,19 @@ class DrsReplSchemaTestCase(drs_base.DrsBaseTestCase):
         self._check_object(c_dn)
         self._check_object(a_dn)
 
-        res = self.ldb_dc1.search(base=a_dn,
+        res = self.ldb_dc1.search(base="",
                                   scope=SCOPE_BASE,
-                                  attrs=["msDS-IntId"])
-        self.assertEqual(1, len(res))
-        self.assertTrue("msDS-IntId" in res[0])
-        int_id = int(res[0]["msDS-IntId"][0])
-        if int_id < 0:
-            int_id += (1 << 32)
+                                  attrs=["domainFunctionality"])
+
+        if int(res[0]["domainFunctionality"][0]) > dsdb.DS_DOMAIN_FUNCTION_2000:
+            res = self.ldb_dc1.search(base=a_dn,
+                                      scope=SCOPE_BASE,
+                                      attrs=["msDS-IntId"])
+            self.assertEqual(1, len(res))
+            self.assertTrue("msDS-IntId" in res[0])
+            int_id = int(res[0]["msDS-IntId"][0])
+            if int_id < 0:
+                int_id += (1 << 32)
 
         dc_guid_1 = self.ldb_dc1.get_invocation_id()
 
-- 
1.9.1


From c3841708cddd1a53ae13cab948e6f46252b3b68b Mon Sep 17 00:00:00 2001
From: Bob Campbell <bobcampbell at catalyst.net.nz>
Date: Fri, 3 Feb 2017 10:34:14 +1300
Subject: [PATCH 8/9] torture/drs: Add a test for dn+binary linked attributes

---
 selftest/knownfail                        |  2 +
 source4/torture/drs/python/repl_schema.py | 70 +++++++++++++++++++++++++++++--
 2 files changed, 68 insertions(+), 4 deletions(-)

diff --git a/selftest/knownfail b/selftest/knownfail
index d96e238..1f659c8 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -312,3 +312,5 @@
 ^samba.tests.dcerpc.dnsserver.samba.tests.dcerpc.dnsserver.DnsserverTests.test_add_duplicate_different_type.*
 ^samba.tests.dcerpc.dnsserver.samba.tests.dcerpc.dnsserver.DnsserverTests.test_rank_none.*
 ^samba.tests.dcerpc.dnsserver.samba.tests.dcerpc.dnsserver.DnsserverTests.test_security_descriptor.*
+#we don't properly handle deleting dn+binary linked attributes
+^samba4.drs.repl_schema.python.*repl_schema.DrsReplSchemaTestCase.test_classWithCustomBinaryDNLinkAttribute.*
diff --git a/source4/torture/drs/python/repl_schema.py b/source4/torture/drs/python/repl_schema.py
index cf02608..e57d79f 100644
--- a/source4/torture/drs/python/repl_schema.py
+++ b/source4/torture/drs/python/repl_schema.py
@@ -29,6 +29,8 @@
 
 import time
 import random
+import ldb
+import drs_base
 
 from ldb import (
     ERR_NO_SUCH_OBJECT,
@@ -36,11 +38,8 @@ from ldb import (
     SCOPE_BASE,
     Message,
     FLAG_MOD_ADD,
-    FLAG_MOD_REPLACE,
+    FLAG_MOD_REPLACE
     )
-import ldb
-
-import drs_base
 from samba.dcerpc import drsuapi, misc
 from samba.drs_utils import drs_DsBind
 from samba import dsdb
@@ -356,3 +355,66 @@ class DrsReplSchemaTestCase(drs_base.DrsBaseTestCase):
         # check objects are replicated
         self._check_object(c_dn)
         self._check_object(a_dn)
+
+    def test_classWithCustomBinaryDNLinkAttribute(self):
+        # Add a new attribute to the schema, which has binary DN syntax (2.5.5.7)
+        (bin_ldn, bin_dn) = self._schema_new_attr(self.ldb_dc1, "attr-Link-Bin", 18,
+                                                  attrs={"linkID": "1.2.840.113556.1.2.50",
+                                                         "attributeSyntax": "2.5.5.7",
+                                                         "omSyntax": "127"})
+
+        (bin_ldn_b, bin_dn_b) = self._schema_new_attr(self.ldb_dc1, "attr-Link-Bin-Back", 19,
+                                                      attrs={"linkID": bin_ldn,
+                                                             "attributeSyntax": "2.5.5.1",
+                                                             "omSyntax": "127"})
+
+        # Add a new class to the schema which can have the binary DN attribute
+        (c_ldn, c_dn) = self._schema_new_class(self.ldb_dc1, "cls-Link-Bin", 20,
+                                               3,
+                                               {"mayContain": bin_ldn})
+        (c_ldn_b, c_dn_b) = self._schema_new_class(self.ldb_dc1, "cls-Link-Bin-Back", 21,
+                                                   3,
+                                                   {"mayContain": bin_ldn_b})
+
+        link_end_dn = ldb.Dn(self.ldb_dc1, "ou=X")
+        link_end_dn.add_base(self.ldb_dc1.get_default_basedn())
+        link_end_dn.set_component(0, "OU", bin_dn_b.get_component_value(0))
+
+        ou_dn = ldb.Dn(self.ldb_dc1, "ou=X")
+        ou_dn.add_base(self.ldb_dc1.get_default_basedn())
+        ou_dn.set_component(0, "OU", bin_dn.get_component_value(0))
+
+        # Add an instance of the class to be pointed at 
+        rec = {"dn": link_end_dn,
+               "objectClass": ["top", "organizationalUnit", c_ldn_b],
+               "ou": link_end_dn.get_component_value(0)}
+        self.ldb_dc1.add(rec)
+
+        # .. and one that does, and points to the first one
+        rec = {"dn": ou_dn,
+               "objectClass": ["top", "organizationalUnit", c_ldn],
+               "ou": ou_dn.get_component_value(0)}
+        self.ldb_dc1.add(rec)
+
+        m = Message.from_dict(self.ldb_dc1,
+                              {"dn": ou_dn,
+                               bin_ldn: "B:8:1234ABCD:%s" % str(link_end_dn)},
+                              FLAG_MOD_ADD)
+        self.ldb_dc1.modify(m)
+
+        self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1,
+                                nc_dn=self.schema_dn, forced=True)
+        self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1,
+                                nc_dn=self.domain_dn, forced=True)
+
+        self._check_object(c_dn)
+        self._check_object(bin_dn)
+
+        # Make sure we can delete the backlink
+        self.ldb_dc1.delete(link_end_dn)
+
+        self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1,
+                                nc_dn=self.schema_dn, forced=True)
+        self._net_drs_replicate(DC=self.dnsname_dc2, fromDC=self.dnsname_dc1,
+                                nc_dn=self.domain_dn, forced=True)
+
-- 
1.9.1


From a5c9bed583b92c3d83756ca294dff4b1d11f43e9 Mon Sep 17 00:00:00 2001
From: Andrew Bartlett <abartlet at samba.org>
Date: Mon, 12 Oct 2015 15:51:37 +1300
Subject: [PATCH 9/9] repl_meta_data: Remove the correct linked attribute
 forward link

The previous code assumed that only plain DNs could be linked attributes.

We need to look over the list of attribute values and find the value
that causes this particular backlink to exist, so we can remove it.

We do not know (until we search) of the binary portion, so we must
search over all the attribute values at this layer, using the
parsed_dn_find() routine used elsewhere in this code.

Found attempting to demote an RODC in a clone of a Windows 2012R2
domain, due to the msDS-RevealedUsers attribute.

BUG: https://bugzilla.samba.org/show_bug.cgi?id=11139
Signed-off-by: Andrew Bartlett <abartlet at samba.org>
---
 selftest/knownfail                              |  2 -
 source4/dsdb/samdb/ldb_modules/repl_meta_data.c | 79 +++++++++++++++++++------
 2 files changed, 62 insertions(+), 19 deletions(-)

diff --git a/selftest/knownfail b/selftest/knownfail
index 1f659c8..d96e238 100644
--- a/selftest/knownfail
+++ b/selftest/knownfail
@@ -312,5 +312,3 @@
 ^samba.tests.dcerpc.dnsserver.samba.tests.dcerpc.dnsserver.DnsserverTests.test_add_duplicate_different_type.*
 ^samba.tests.dcerpc.dnsserver.samba.tests.dcerpc.dnsserver.DnsserverTests.test_rank_none.*
 ^samba.tests.dcerpc.dnsserver.samba.tests.dcerpc.dnsserver.DnsserverTests.test_security_descriptor.*
-#we don't properly handle deleting dn+binary linked attributes
-^samba4.drs.repl_schema.python.*repl_schema.DrsReplSchemaTestCase.test_classWithCustomBinaryDNLinkAttribute.*
diff --git a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
index 7d12c23..e63872d 100644
--- a/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
+++ b/source4/dsdb/samdb/ldb_modules/repl_meta_data.c
@@ -3146,6 +3146,7 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are
 static int replmd_delete_remove_link(struct ldb_module *module,
 				     const struct dsdb_schema *schema,
 				     struct ldb_dn *dn,
+				     struct GUID *guid,
 				     struct ldb_message_element *el,
 				     const struct dsdb_attribute *sa,
 				     struct ldb_request *parent)
@@ -3156,14 +3157,19 @@ static int replmd_delete_remove_link(struct ldb_module *module,
 
 	for (i=0; i<el->num_values; i++) {
 		struct dsdb_dn *dsdb_dn;
-		NTSTATUS status;
 		int ret;
-		struct GUID guid2;
 		struct ldb_message *msg;
 		const struct dsdb_attribute *target_attr;
 		struct ldb_message_element *el2;
+		const char *dn_str;
 		struct ldb_val dn_val;
 		uint32_t dsdb_flags = 0;
+		const char *attrs[] = { NULL, NULL };
+		struct ldb_result *link_res;
+		struct ldb_message *link_msg;
+		struct ldb_message_element *link_el;
+		struct parsed_dn *p;
+		struct parsed_dn *link_dns;
 
 		if (dsdb_dn_is_deleted_val(&el->values[i])) {
 			continue;
@@ -3175,12 +3181,6 @@ static int replmd_delete_remove_link(struct ldb_module *module,
 			return LDB_ERR_OPERATIONS_ERROR;
 		}
 
-		status = dsdb_get_extended_dn_guid(dsdb_dn->dn, &guid2, "GUID");
-		if (!NT_STATUS_IS_OK(status)) {
-			talloc_free(tmp_ctx);
-			return LDB_ERR_OPERATIONS_ERROR;
-		}
-
 		/* remove the link */
 		msg = ldb_msg_new(tmp_ctx);
 		if (!msg) {
@@ -3196,14 +3196,58 @@ static int replmd_delete_remove_link(struct ldb_module *module,
 		if (target_attr == NULL) {
 			continue;
 		}
+		attrs[0] = target_attr->lDAPDisplayName;
 
-		ret = ldb_msg_add_empty(msg, target_attr->lDAPDisplayName, LDB_FLAG_MOD_DELETE, &el2);
+		ret = ldb_msg_add_empty(msg, target_attr->lDAPDisplayName,
+					LDB_FLAG_MOD_DELETE, &el2);
 		if (ret != LDB_SUCCESS) {
 			ldb_module_oom(module);
 			talloc_free(tmp_ctx);
 			return LDB_ERR_OPERATIONS_ERROR;
 		}
-		dn_val = data_blob_string_const(ldb_dn_get_linearized(dn));
+
+		ret = dsdb_module_search_dn(module, tmp_ctx, &link_res,
+					    msg->dn, attrs,
+					    DSDB_FLAG_NEXT_MODULE, parent);
+
+		if (ret != LDB_SUCCESS) {
+			talloc_free(tmp_ctx);
+			return ret;
+		}
+
+		link_msg = link_res->msgs[0];
+		link_el = ldb_msg_find_element(link_msg,
+					       target_attr->lDAPDisplayName);
+		if (link_el == NULL) {
+			talloc_free(tmp_ctx);
+			return LDB_ERR_NO_SUCH_ATTRIBUTE;
+		}
+
+		ret = get_parsed_dns(module, tmp_ctx,
+				     link_el, &link_dns,
+				     target_attr->syntax->ldap_oid, parent);
+		if (ret != LDB_SUCCESS) {
+			talloc_free(tmp_ctx);
+			return ret;
+		}
+		p = parsed_dn_find(link_dns, link_el->num_values, guid, dn);
+		if (p == NULL) {
+			ldb_asprintf_errstring(ldb_module_get_ctx(module),
+					       "Failed to find forward link on %s "
+					       "as %s to remove backlink %s on %s",
+					       ldb_dn_get_linearized(msg->dn),
+					       target_attr->lDAPDisplayName,
+					       sa->lDAPDisplayName,
+					       ldb_dn_get_linearized(dn));
+			talloc_free(tmp_ctx);
+			return LDB_ERR_NO_SUCH_ATTRIBUTE;
+		}
+
+
+		/* This needs to get the Binary DN, by first searching */
+		dn_str = dsdb_dn_get_linearized(tmp_ctx,
+						p->dsdb_dn);
+		dn_val = data_blob_string_const(dn_str);
 		el2->values = &dn_val;
 		el2->num_values = 1;
 
@@ -3446,10 +3490,10 @@ static int replmd_delete_internals(struct ldb_module *module, struct ldb_request
 		}
 	}
 
-	if (deletion_state == OBJECT_NOT_DELETED) {
-		/* get the objects GUID from the search we just did */
-		guid = samdb_result_guid(old_msg, "objectGUID");
+	/* get the objects GUID from the search we just did */
+	guid = samdb_result_guid(old_msg, "objectGUID");
 
+	if (deletion_state == OBJECT_NOT_DELETED) {
 		/* Add a formatted child */
 		retb = ldb_dn_add_child_fmt(new_dn, "%s=%s\\0ADEL:%s",
 					    rdn_name,
@@ -3626,7 +3670,7 @@ static int replmd_delete_internals(struct ldb_module *module, struct ldb_request
 				/* don't remove the rDN */
 				continue;
 			}
-			if (sa->linkID && (sa->linkID & 1)) {
+			if (sa->linkID & 1) {
 				/*
 				  we have a backlink in this object
 				  that needs to be removed. We're not
@@ -3635,7 +3679,9 @@ static int replmd_delete_internals(struct ldb_module *module, struct ldb_request
 				  modify to delete the corresponding
 				  forward link
 				 */
-				ret = replmd_delete_remove_link(module, schema, old_dn, el, sa, req);
+				ret = replmd_delete_remove_link(module, schema,
+								old_dn, &guid,
+								el, sa, req);
 				if (ret != LDB_SUCCESS) {
 					const char *old_dn_str
 						= ldb_dn_get_linearized(old_dn);
@@ -3654,8 +3700,7 @@ static int replmd_delete_internals(struct ldb_module *module, struct ldb_request
 				   directly
 				*/
 				continue;
-			}
-			if (!sa->linkID) {
+			} else if (sa->linkID == 0) {
 				if (ldb_attr_in_list(preserved_attrs, el->name)) {
 					continue;
 				}
-- 
1.9.1



More information about the samba-technical mailing list