s4:samldb LDB module - make sure to not add identical "servicePrincipalName"s more...
authorMatthias Dieter Wallnöfer <mdw@samba.org>
Thu, 3 May 2012 20:55:06 +0000 (22:55 +0200)
committerMatthias Dieter Wallnöfer <mdw@samba.org>
Thu, 3 May 2012 20:55:06 +0000 (22:55 +0200)
The service principal names need to be case-insensitively unique, otherwise we
end up in a LDB ERR_ATTRIBUTE_OR_VALUE_EXISTS error.
This issue has been discovered on the technical mailing list (thread:
cannot rename windows xp machine in samba4) when trying to rename a AD
client workstation.

source4/dsdb/samdb/ldb_modules/samldb.c
source4/dsdb/tests/python/sam.py

index 84403b0fe5e80bab0f45567417617602a4a7e87f..f670a1cc70a0c4cc1489724d3d447166bbd528df 100644 (file)
@@ -1824,7 +1824,7 @@ static int samldb_service_principal_names_change(struct samldb_ctx *ac)
        struct ldb_result *res;
        const char *dns_hostname = NULL, *old_dns_hostname = NULL,
                   *sam_accountname = NULL, *old_sam_accountname = NULL;
-       unsigned int i;
+       unsigned int i, j;
        int ret;
 
        el = dsdb_get_single_valued_attr(ac->msg, "dNSHostName",
@@ -1965,15 +1965,28 @@ static int samldb_service_principal_names_change(struct samldb_ctx *ac)
        }
 
        if (res->msgs[0]->num_elements == 1) {
-               /* Yes, we do have "servicePrincipalName"s. First we update them
+               /*
+                * Yes, we do have "servicePrincipalName"s. First we update them
                 * locally, that means we do always substitute the current
                 * "dNSHostName" with the new one and/or "sAMAccountName"
-                * without "$" with the new one and then we append this to the
-                * modification request (Windows behaviour). */
+                * without "$" with the new one and then we append the
+                * modified "servicePrincipalName"s as a message element
+                * replace to the modification request (Windows behaviour). We
+                * need also to make sure that the values remain case-
+                * insensitively unique.
+                */
+
+               ret = ldb_msg_add_empty(ac->msg, "servicePrincipalName",
+                                       LDB_FLAG_MOD_REPLACE, &el);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
 
                for (i = 0; i < res->msgs[0]->elements[0].num_values; i++) {
                        char *old_str, *new_str, *pos;
                        const char *tok;
+                       struct ldb_val *vals;
+                       bool found = false;
 
                        old_str = (char *)
                                res->msgs[0]->elements[0].values[i].data;
@@ -2001,16 +2014,32 @@ static int samldb_service_principal_names_change(struct samldb_ctx *ac)
                                }
                        }
 
-                       ret = ldb_msg_add_string(ac->msg,
-                                                "servicePrincipalName",
-                                                new_str);
-                       if (ret != LDB_SUCCESS) {
-                               return ret;
+                       /* Uniqueness check */
+                       for (j = 0; (!found) && (j < el->num_values); j++) {
+                               if (strcasecmp((char *)el->values[j].data,
+                                              new_str) == 0) {
+                                       found = true;
+                               }
+                       }
+                       if (found) {
+                               continue;
                        }
-               }
 
-               el = ldb_msg_find_element(ac->msg, "servicePrincipalName");
-               el->flags = LDB_FLAG_MOD_REPLACE;
+                       /*
+                        * append the new "servicePrincipalName" - code derived
+                        * from ldb_msg_add_value()
+                        */
+                       vals = talloc_realloc(ac->msg, el->values,
+                                             struct ldb_val,
+                                             el->num_values + 1);
+                       if (vals == NULL) {
+                               return ldb_module_oom(ac->module);
+                       }
+                       el->values = vals;
+                       el->values[el->num_values].data = (uint8_t *) new_str;
+                       el->values[el->num_values].length = strlen(new_str);
+                       ++(el->num_values);
+               }
        }
 
        talloc_free(res);
index 688e257ef5c9b633580504a3811241f320808db3..df1915a899fc1f6bdd03eacfe7a2715a109359bd 100755 (executable)
@@ -2440,10 +2440,53 @@ class SamTests(samba.tests.TestCase):
         self.assertTrue(len(res) == 1)
         self.assertEquals(res[0]["dNSHostName"][0], "testname2.testdom")
         self.assertEquals(res[0]["sAMAccountName"][0], "testname2$")
-        self.assertTrue(res[0]["servicePrincipalName"][0] == "HOST/testname2" or
-                        res[0]["servicePrincipalName"][1] == "HOST/testname2")
-        self.assertTrue(res[0]["servicePrincipalName"][0] == "HOST/testname2.testdom" or
-                        res[0]["servicePrincipalName"][1] == "HOST/testname2.testdom")
+        self.assertTrue(len(res[0]["servicePrincipalName"]) == 2)
+        self.assertTrue("HOST/testname2" in res[0]["servicePrincipalName"])
+        self.assertTrue("HOST/testname2.testdom" in res[0]["servicePrincipalName"])
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["servicePrincipalName"] = MessageElement("HOST/testname2.testdom",
+                                                   FLAG_MOD_ADD, "servicePrincipalName")
+        try:
+            ldb.modify(m)
+            self.fail()
+        except LdbError, (num, _):
+            self.assertEquals(num, ERR_ATTRIBUTE_OR_VALUE_EXISTS)
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["servicePrincipalName"] = MessageElement("HOST/testname3",
+                                                   FLAG_MOD_ADD, "servicePrincipalName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["dNSHostName", "sAMAccountName", "servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEquals(res[0]["dNSHostName"][0], "testname2.testdom")
+        self.assertEquals(res[0]["sAMAccountName"][0], "testname2$")
+        self.assertTrue(len(res[0]["servicePrincipalName"]) == 3)
+        self.assertTrue("HOST/testname2" in res[0]["servicePrincipalName"])
+        self.assertTrue("HOST/testname3" in res[0]["servicePrincipalName"])
+        self.assertTrue("HOST/testname2.testdom" in res[0]["servicePrincipalName"])
+
+        m = Message()
+        m.dn = Dn(ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)
+        m["dNSHostName"] = MessageElement("testname3.testdom",
+                                          FLAG_MOD_REPLACE, "dNSHostName")
+        m["servicePrincipalName"] = MessageElement("HOST/testname3.testdom",
+                                                   FLAG_MOD_ADD, "servicePrincipalName")
+        ldb.modify(m)
+
+        res = ldb.search("cn=ldaptestcomputer,cn=computers," + self.base_dn,
+                         scope=SCOPE_BASE, attrs=["dNSHostName", "sAMAccountName", "servicePrincipalName"])
+        self.assertTrue(len(res) == 1)
+        self.assertEquals(res[0]["dNSHostName"][0], "testname3.testdom")
+        self.assertEquals(res[0]["sAMAccountName"][0], "testname2$")
+        self.assertTrue(len(res[0]["servicePrincipalName"]) == 3)
+        self.assertTrue("HOST/testname2" in res[0]["servicePrincipalName"])
+        self.assertTrue("HOST/testname3" in res[0]["servicePrincipalName"])
+        self.assertTrue("HOST/testname3.testdom" in res[0]["servicePrincipalName"])
 
         delete_force(self.ldb, "cn=ldaptestcomputer,cn=computers," + self.base_dn)