s4 dsdb: Use the changereplmetadata control
authorMatthieu Patou <mat@matws.net>
Wed, 16 Jun 2010 14:47:18 +0000 (18:47 +0400)
committerAndrew Bartlett <abartlet@samba.org>
Thu, 15 Jul 2010 12:08:20 +0000 (22:08 +1000)
This control allow to specify the replPropertyMetaData attribute to
be specified on modify request. It can be used for very specific needs
to tweak the content of the replication data.

Signed-off-by: Andrew Bartlett <abartlet@samba.org>
source4/dsdb/samdb/ldb_modules/repl_meta_data.c
source4/scripting/python/samba/tests/dsdb.py

index 90af17f7ec3c0498b8d0216358073710fbd2c570..2205be07c18e7a3c1093036f34a4f9df930a279d 100644 (file)
@@ -5,6 +5,7 @@
    Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
    Copyright (C) Andrew Tridgell 2005
    Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
+   Copyright (C) Matthieu Patou <mat@samba.org> 2010
 
    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
@@ -1073,6 +1074,20 @@ static int replmd_update_rpmd_element(struct ldb_context *ldb,
        return LDB_SUCCESS;
 }
 
+static uint64_t find_max_local_usn(struct replPropertyMetaDataBlob omd)
+{
+       uint32_t count = omd.ctr.ctr1.count;
+       uint64_t max = 0;
+       uint32_t i;
+       for (i=0; i < count; i++) {
+               struct replPropertyMetaData1 m = omd.ctr.ctr1.array[i];
+               if (max < m.local_usn) {
+                       max = m.local_usn;
+               }
+       }
+       return max;
+}
+
 /*
  * update the replPropertyMetaData object each time we modify an
  * object. This is needed for DRS replication, as the merge on the
@@ -1093,11 +1108,12 @@ static int replmd_update_rpmd(struct ldb_module *module,
        const struct GUID *our_invocation_id;
        int ret;
        const char *attrs[] = { "replPropertyMetaData", "*", NULL };
+       const char *attrs2[] = { "uSNChanged", "objectClass", NULL };
        struct ldb_result *res;
        struct ldb_context *ldb;
        struct ldb_message_element *objectclass_el;
        enum urgent_situation situation;
-       bool rodc;
+       bool rodc, rmd_is_provided;
 
        ldb = ldb_module_get_ctx(module);
 
@@ -1111,21 +1127,10 @@ static int replmd_update_rpmd(struct ldb_module *module,
 
        unix_to_nt_time(&now, t);
 
-       /* search for the existing replPropertyMetaDataBlob. We need
-        * to use REVEAL and ask for DNs in storage format to support
-        * the check for values being the same in
-        * replmd_update_rpmd_element()
-        */
-       ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
-                                   DSDB_FLAG_NEXT_MODULE |
-                                   DSDB_SEARCH_SHOW_DELETED |
-                                   DSDB_SEARCH_SHOW_EXTENDED_DN |
-                                   DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
-                                   DSDB_SEARCH_REVEAL_INTERNALS);
-       if (ret != LDB_SUCCESS || res->count != 1) {
-               DEBUG(0,(__location__ ": Object %s failed to find replPropertyMetaData\n",
-                        ldb_dn_get_linearized(msg->dn)));
-               return LDB_ERR_OPERATIONS_ERROR;
+       if (ldb_request_get_control(req, DSDB_CONTROL_CHANGEREPLMETADATA_OID)) {
+               rmd_is_provided = true;
+       } else {
+               rmd_is_provided = false;
        }
 
        /* if isDeleted is present and is TRUE, then we consider we are deleting,
@@ -1136,62 +1141,140 @@ static int replmd_update_rpmd(struct ldb_module *module,
                situation = REPL_URGENT_ON_UPDATE;
        }
 
-       objectclass_el = ldb_msg_find_element(res->msgs[0], "objectClass");
-       if (is_urgent && replmd_check_urgent_objectclass(objectclass_el,
-                                                       situation)) {
-               *is_urgent = true;
-       }
+       if (rmd_is_provided) {
+               /* In this case the change_replmetadata control was supplied */
+               /* We check that it's the only attribute that is provided
+                * (it's a rare case so it's better to keep the code simplier)
+                * We also check that the highest local_usn is bigger than
+                * uSNChanged. */
+               uint64_t db_seq;
+               if( msg->num_elements != 1 ||
+                       strncmp(msg->elements[0].name,
+                               "replPropertyMetaData", 20) ) {
+                       DEBUG(0,(__location__ ": changereplmetada control called without "\
+                               "a specified replPropertyMetaData attribute or with others\n"));
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+               if (situation == REPL_URGENT_ON_DELETE) {
+                       DEBUG(0,(__location__ ": changereplmetada control can't be called when deleting an object\n"));
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+               omd_value = ldb_msg_find_ldb_val(msg, "replPropertyMetaData");
+               if (!omd_value) {
+                       DEBUG(0,(__location__ ": replPropertyMetaData was not specified for Object %s\n",
+                                ldb_dn_get_linearized(msg->dn)));
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+               ndr_err = ndr_pull_struct_blob(omd_value, msg, &omd,
+                                              (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+               if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+                       DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s\n",
+                                ldb_dn_get_linearized(msg->dn)));
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+               *seq_num = find_max_local_usn(omd);
 
-       omd_value = ldb_msg_find_ldb_val(res->msgs[0], "replPropertyMetaData");
-       if (!omd_value) {
-               DEBUG(0,(__location__ ": Object %s does not have a replPropertyMetaData attribute\n",
-                        ldb_dn_get_linearized(msg->dn)));
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
+               ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs2,
+                                           DSDB_FLAG_NEXT_MODULE |
+                                           DSDB_SEARCH_SHOW_DELETED |
+                                           DSDB_SEARCH_SHOW_EXTENDED_DN |
+                                           DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
+                                           DSDB_SEARCH_REVEAL_INTERNALS);
 
-       ndr_err = ndr_pull_struct_blob(omd_value, msg, &omd,
-                                      (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
-       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
-               DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s\n",
-                        ldb_dn_get_linearized(msg->dn)));
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
+               if (ret != LDB_SUCCESS || res->count != 1) {
+                       DEBUG(0,(__location__ ": Object %s failed to find uSNChanged\n",
+                                ldb_dn_get_linearized(msg->dn)));
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
 
-       if (omd.version != 1) {
-               DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s\n",
-                        omd.version, ldb_dn_get_linearized(msg->dn)));
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
+               objectclass_el = ldb_msg_find_element(res->msgs[0], "objectClass");
+               if (is_urgent && replmd_check_urgent_objectclass(objectclass_el,
+                                                               situation)) {
+                       *is_urgent = true;
+               }
 
-       /*we have elements that will be modified*/
-       if (msg->num_elements > 0) {
-               /*if we are RODC and this is a DRSR update then its ok*/
-               if (!ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
-                       ret = samdb_rodc(ldb, &rodc);
-                       if (ret != LDB_SUCCESS) {
-                               DEBUG(4, (__location__ ": unable to tell if we are an RODC\n"));
-                       } else if (rodc) {
-                               ldb_asprintf_errstring(ldb, "RODC modify is forbidden\n");
-                               return LDB_ERR_REFERRAL;
-                       }
+               db_seq = ldb_msg_find_attr_as_uint64(res->msgs[0], "uSNChanged", 0);
+               if (*seq_num <= db_seq) {
+                       DEBUG(0,(__location__ ": changereplmetada control provided but max(local_usn)"\
+                                             " is less or equal to uSNChanged (max = %lld uSNChanged = %lld)\n",
+                                             *seq_num, db_seq));
+                       return LDB_ERR_OPERATIONS_ERROR;
                }
-       }
 
-       for (i=0; i<msg->num_elements; i++) {
-               struct ldb_message_element *old_el;
-               old_el = ldb_msg_find_element(res->msgs[0], msg->elements[i].name);
-               ret = replmd_update_rpmd_element(ldb, msg, &msg->elements[i], old_el, &omd, schema, seq_num,
-                                                our_invocation_id, now);
-               if (ret != LDB_SUCCESS) {
-                       return ret;
+       } else {
+               /* search for the existing replPropertyMetaDataBlob. We need
+                * to use REVEAL and ask for DNs in storage format to support
+                * the check for values being the same in
+                * replmd_update_rpmd_element()
+                */
+               ret = dsdb_module_search_dn(module, msg, &res, msg->dn, attrs,
+                                           DSDB_FLAG_NEXT_MODULE |
+                                           DSDB_SEARCH_SHOW_DELETED |
+                                           DSDB_SEARCH_SHOW_EXTENDED_DN |
+                                           DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
+                                           DSDB_SEARCH_REVEAL_INTERNALS);
+               if (ret != LDB_SUCCESS || res->count != 1) {
+                       DEBUG(0,(__location__ ": Object %s failed to find replPropertyMetaData\n",
+                                ldb_dn_get_linearized(msg->dn)));
+                       return LDB_ERR_OPERATIONS_ERROR;
                }
 
-               if (is_urgent && !*is_urgent && (situation == REPL_URGENT_ON_UPDATE)) {
-                       *is_urgent = replmd_check_urgent_attribute(&msg->elements[i]);
+               objectclass_el = ldb_msg_find_element(res->msgs[0], "objectClass");
+               if (is_urgent && replmd_check_urgent_objectclass(objectclass_el,
+                                                               situation)) {
+                       *is_urgent = true;
                }
 
-       }
+               omd_value = ldb_msg_find_ldb_val(res->msgs[0], "replPropertyMetaData");
+               if (!omd_value) {
+                       DEBUG(0,(__location__ ": Object %s does not have a replPropertyMetaData attribute\n",
+                                ldb_dn_get_linearized(msg->dn)));
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
 
+               ndr_err = ndr_pull_struct_blob(omd_value, msg, &omd,
+                                              (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+               if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+                       DEBUG(0,(__location__ ": Failed to parse replPropertyMetaData for %s\n",
+                                ldb_dn_get_linearized(msg->dn)));
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+
+               if (omd.version != 1) {
+                       DEBUG(0,(__location__ ": bad version %u in replPropertyMetaData for %s\n",
+                                omd.version, ldb_dn_get_linearized(msg->dn)));
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+
+               /*we have elements that will be modified*/
+               if (msg->num_elements > 0) {
+                       /*if we are RODC and this is a DRSR update then its ok*/
+                       if (!ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
+                               ret = samdb_rodc(ldb, &rodc);
+                               if (ret != LDB_SUCCESS) {
+                                       DEBUG(4, (__location__ ": unable to tell if we are an RODC\n"));
+                               } else if (rodc) {
+                                       ldb_asprintf_errstring(ldb, "RODC modify is forbidden\n");
+                                       return LDB_ERR_REFERRAL;
+                               }
+                       }
+               }
+
+               for (i=0; i<msg->num_elements; i++) {
+                       struct ldb_message_element *old_el;
+                       old_el = ldb_msg_find_element(res->msgs[0], msg->elements[i].name);
+                       ret = replmd_update_rpmd_element(ldb, msg, &msg->elements[i], old_el, &omd, schema, seq_num,
+                                                        our_invocation_id, now);
+                       if (ret != LDB_SUCCESS) {
+                               return ret;
+                       }
+
+                       if (is_urgent && !*is_urgent && (situation == REPL_URGENT_ON_UPDATE)) {
+                               *is_urgent = replmd_check_urgent_attribute(&msg->elements[i]);
+                       }
+
+               }
+       }
        /*
         * replmd_update_rpmd_element has done an update if the
         * seq_num is set
index c19dbaab47adae1b97c61a29ea0e014c58cc8270..d28be82984752cded4e00def5639bf7ec9d56c9a 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 
-# Unix SMB/CIFS implementation. Tests for dsdb 
+# Unix SMB/CIFS implementation. Tests for dsdb
 # Copyright (C) Matthieu Patou <mat@matws.net> 2010
 #
 # This program is free software; you can redistribute it and/or modify
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 
-import samba.dsdb
 from samba.credentials import Credentials
 from samba.samdb import SamDB
 from samba.auth import system_session
 from testtools.testcase import TestCase
+from samba.ndr import ndr_unpack, ndr_pack
+from samba.dcerpc import drsblobs
+import ldb
 import os
+import samba.dsdb
 
 
 class DsdbTests(TestCase):
 
-    def _baseprovpath(self):
+
+    def setUp(self):
+        super(DsdbTests, self).setUp()
+        self.lp = samba.param.LoadParm()
+        self.lp.load(os.path.join(os.path.join(self.baseprovpath(), "etc"), "smb.conf"))
+        self.creds = Credentials()
+        self.creds.guess(self.lp)
+        self.session = system_session()
+        self.samdb = SamDB(os.path.join(self.baseprovpath(), "private", "sam.ldb"),
+            session_info=self.session, credentials=self.creds,lp=self.lp)
+
+
+    def baseprovpath(self):
         return os.path.join(os.environ['SELFTEST_PREFIX'], "dc")
 
+
     def test_get_oid_from_attrid(self):
-        lp = samba.param.LoadParm()
-        lp.load(os.path.join(os.path.join(self._baseprovpath(), "etc"), "smb.conf"))
-        creds = Credentials()
-        creds.guess(lp)
-        session = system_session()
-        test_ldb = SamDB(os.path.join(self._baseprovpath(), "private", "sam.ldb"),
-            session_info=session, credentials=creds,lp=lp)
-        oid = test_ldb.get_oid_from_attid(591614)
+        oid = self.samdb.get_oid_from_attid(591614)
         self.assertEquals(oid, "1.2.840.113556.1.4.1790")
+
+    def test_error_replpropertymetadata(self):
+        res = self.samdb.search(expression="cn=Administrator",
+                            scope=ldb.SCOPE_SUBTREE,
+                            attrs=["replPropertyMetaData"])
+        repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+                            str(res[0]["replPropertyMetaData"]))
+        ctr = repl.ctr
+        for o in ctr.array:
+            # Search for Description
+            if o.attid == 13:
+                old_version = o.version
+                o.version = o.version + 1
+        replBlob = ndr_pack(repl)
+        msg = ldb.Message()
+        msg.dn = res[0].dn
+        msg["replPropertyMetaData"] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData")
+        self.assertRaises(ldb.LdbError, self.samdb.modify, msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
+
+    def test_twoatt_replpropertymetadata(self):
+        res = self.samdb.search(expression="cn=Administrator",
+                            scope=ldb.SCOPE_SUBTREE,
+                            attrs=["replPropertyMetaData", "uSNChanged"])
+        repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+                            str(res[0]["replPropertyMetaData"]))
+        ctr = repl.ctr
+        for o in ctr.array:
+            # Search for Description
+            if o.attid == 13:
+                old_version = o.version
+                o.version = o.version + 1
+                o.local_usn = long(str(res[0]["uSNChanged"])) + 1
+        replBlob = ndr_pack(repl)
+        msg = ldb.Message()
+        msg.dn = res[0].dn
+        msg["replPropertyMetaData"] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData")
+        msg["description"] = ldb.MessageElement("new val", ldb.FLAG_MOD_REPLACE, "description")
+        self.assertRaises(ldb.LdbError, self.samdb.modify, msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])
+
+    def test_set_replpropertymetadata(self):
+        res = self.samdb.search(expression="cn=Administrator",
+                            scope=ldb.SCOPE_SUBTREE,
+                            attrs=["replPropertyMetaData", "uSNChanged"])
+        repl = ndr_unpack(drsblobs.replPropertyMetaDataBlob,
+                            str(res[0]["replPropertyMetaData"]))
+        ctr = repl.ctr
+        for o in ctr.array:
+            # Search for Description
+            if o.attid == 13:
+                old_version = o.version
+                o.version = o.version + 1
+                o.local_usn = long(str(res[0]["uSNChanged"])) + 1
+                o.originating_usn = long(str(res[0]["uSNChanged"])) + 1
+        replBlob = ndr_pack(repl)
+        msg = ldb.Message()
+        msg.dn = res[0].dn
+        msg["replPropertyMetaData"] = ldb.MessageElement(replBlob, ldb.FLAG_MOD_REPLACE, "replPropertyMetaData")
+        self.samdb.modify(msg, ["local_oid:1.3.6.1.4.1.7165.4.3.14:0"])