Revert "s4-dsdb: Remove strcasecmp() fallback in replmd_ldb_message_element_attid_sort"
[obnox/samba/samba-obnox.git] / source4 / dsdb / samdb / ldb_modules / repl_meta_data.c
index b9e1852f3b8fa5c716e409202a0fe9a461907205..24dcc6f04dce79583fa5e78efeb904bcb51280df 100644 (file)
@@ -94,6 +94,8 @@ struct replmd_replicated_request {
        bool is_urgent;
 };
 
+static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar);
+
 enum urgent_situation {
        REPL_URGENT_ON_CREATE = 1,
        REPL_URGENT_ON_UPDATE = 2,
@@ -2313,12 +2315,28 @@ static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
        bool is_urgent = false, rodc = false;
        unsigned int functional_level;
        const DATA_BLOB *guid_blob;
+       struct ldb_control *sd_propagation_control;
 
        /* do not manipulate our control entries */
        if (ldb_dn_is_special(req->op.mod.message->dn)) {
                return ldb_next_request(module, req);
        }
 
+       sd_propagation_control = ldb_request_get_control(req,
+                                       DSDB_CONTROL_SEC_DESC_PROPAGATION_OID);
+       if (sd_propagation_control != NULL) {
+               if (req->op.mod.message->num_elements != 1) {
+                       return ldb_module_operr(module);
+               }
+               ret = strcmp(req->op.mod.message->elements[0].name,
+                            "nTSecurityDescriptor");
+               if (ret != 0) {
+                       return ldb_module_operr(module);
+               }
+
+               return ldb_next_request(module, req);
+       }
+
        ldb = ldb_module_get_ctx(module);
 
        ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_modify\n");
@@ -2987,7 +3005,7 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                        talloc_free(tmp_ctx);
                        return ret;
                }
-               msg->elements[el_count++].flags = LDB_FLAG_MOD_ADD;
+               msg->elements[el_count++].flags = LDB_FLAG_MOD_REPLACE;
        }
 
        switch (next_deletion_state){
@@ -3326,11 +3344,14 @@ failed:
   callback for conflict DN handling where we have renamed the incoming
   record. After renaming it, we need to ensure the change of name and
   rDN for the incoming record is seen as an originating update by this DC.
+
+  This also handles updating lastKnownParent for entries sent to lostAndFound
  */
 static int replmd_op_name_modify_callback(struct ldb_request *req, struct ldb_reply *ares)
 {
        struct replmd_replicated_request *ar =
                talloc_get_type_abort(req->context, struct replmd_replicated_request);
+       struct ldb_dn *conflict_dn;
        int ret;
 
        if (ares->error != LDB_SUCCESS) {
@@ -3338,13 +3359,52 @@ static int replmd_op_name_modify_callback(struct ldb_request *req, struct ldb_re
                return replmd_op_callback(req, ares);
        }
 
+       switch (req->operation) {
+       case LDB_ADD:
+               conflict_dn = req->op.add.message->dn;
+               break;
+       case LDB_MODIFY:
+               conflict_dn = req->op.mod.message->dn;
+               break;
+       default:
+               smb_panic("replmd_op_name_modify_callback called in unknown circumstances");
+       }
+
        /* perform a modify of the rDN and name of the record */
-       ret = replmd_name_modify(ar, req, req->op.add.message->dn);
+       ret = replmd_name_modify(ar, req, conflict_dn);
        if (ret != LDB_SUCCESS) {
                ares->error = ret;
                return replmd_op_callback(req, ares);
        }
 
+       if (ar->objs->objects[ar->index_current].last_known_parent) {
+               struct ldb_message *msg = ldb_msg_new(req);
+               if (msg == NULL) {
+                       ldb_module_oom(ar->module);
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+
+               msg->dn = req->op.add.message->dn;
+
+               ret = ldb_msg_add_steal_string(msg, "lastKnownParent",
+                                              ldb_dn_get_extended_linearized(msg, ar->objs->objects[ar->index_current].last_known_parent, 1));
+               if (ret != LDB_SUCCESS) {
+                       DEBUG(0,(__location__ ": Failed to add lastKnownParent string to the msg\n"));
+                       ldb_module_oom(ar->module);
+                       return ret;
+               }
+               msg->elements[0].flags = LDB_FLAG_MOD_REPLACE;
+
+               ret = dsdb_module_modify(ar->module, msg, DSDB_FLAG_OWN_MODULE, req);
+               if (ret != LDB_SUCCESS) {
+                       DEBUG(0,(__location__ ": Failed to modify lastKnownParent of lostAndFound DN '%s' - %s",
+                                ldb_dn_get_linearized(msg->dn),
+                                ldb_errstring(ldb_module_get_ctx(ar->module))));
+                       return ret;
+               }
+               TALLOC_FREE(msg);
+       }
+
        return replmd_op_callback(req, ares);
 }
 
@@ -3361,36 +3421,42 @@ static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct
        struct ldb_result *res;
        const char *attrs[] = { "replPropertyMetaData", "objectGUID", NULL };
        int ret;
-       const struct ldb_val *rmd_value, *omd_value;
-       struct replPropertyMetaDataBlob omd, rmd;
+       const struct ldb_val *omd_value;
+       struct replPropertyMetaDataBlob omd, *rmd;
        enum ndr_err_code ndr_err;
        bool rename_incoming_record, rodc;
        struct replPropertyMetaData1 *rmd_name, *omd_name;
+       struct ldb_message *msg;
+
+       req->callback = callback;
 
        if (ares->error != LDB_ERR_ENTRY_ALREADY_EXISTS) {
                /* call the normal callback for everything except
                   conflicts */
-               return callback(req, ares);
+               return ldb_module_done(req, ares->controls, ares->response, ares->error);
        }
 
        ret = samdb_rodc(ldb_module_get_ctx(ar->module), &rodc);
        if (ret != LDB_SUCCESS) {
-               return ret;
+               ldb_asprintf_errstring(ldb_module_get_ctx(ar->module), "Failed to determine if we are an RODC when attempting to form conflict DN: %s", ldb_errstring(ldb_module_get_ctx(ar->module)));
+               return ldb_module_done(req, ares->controls, ares->response, LDB_ERR_OPERATIONS_ERROR);
        }
-       /*
+       /*
         * we have a conflict, and need to decide if we will keep the
         * new record or the old record
         */
 
+       msg = ar->objs->objects[ar->index_current].msg;
+
        switch (req->operation) {
        case LDB_ADD:
-               conflict_dn = req->op.add.message->dn;
+               conflict_dn = msg->dn;
                break;
        case LDB_RENAME:
                conflict_dn = req->op.rename.newdn;
                break;
        default:
-               return ldb_module_operr(ar->module);
+               return ldb_module_done(req, ares->controls, ares->response, ldb_module_operr(ar->module));
        }
 
        if (rodc) {
@@ -3437,28 +3503,11 @@ static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct
                goto failed;
        }
 
-       /*
-        * and the replPropertyMetaData attribute from the
-        * new record
-        */
-       rmd_value = ldb_msg_find_ldb_val(req->op.add.message, "replPropertyMetaData");
-       if (rmd_value == NULL) {
-               DEBUG(0,(__location__ ": Unable to find replPropertyMetaData for new record '%s'\n",
-                        ldb_dn_get_linearized(conflict_dn)));
-               goto failed;
-       }
-
-       ndr_err = ndr_pull_struct_blob(rmd_value, req, &rmd,
-                                      (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
-       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
-               DEBUG(0,(__location__ ": Failed to parse new replPropertyMetaData for %s\n",
-                        ldb_dn_get_linearized(conflict_dn)));
-               goto failed;
-       }
+       rmd = ar->objs->objects[ar->index_current].meta_data;
 
        /* we decide which is newer based on the RPMD on the name
           attribute.  See [MS-DRSR] ResolveNameConflict */
-       rmd_name = replmd_replPropertyMetaData1_find_attid(&rmd, DRSUAPI_ATTID_name);
+       rmd_name = replmd_replPropertyMetaData1_find_attid(rmd, DRSUAPI_ATTID_name);
        omd_name = replmd_replPropertyMetaData1_find_attid(&omd, DRSUAPI_ATTID_name);
        if (!rmd_name || !omd_name) {
                DEBUG(0,(__location__ ": Failed to find name attribute in replPropertyMetaData for %s\n",
@@ -3474,14 +3523,24 @@ static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct
                struct ldb_dn *new_dn;
                struct ldb_message *new_msg;
 
+               /*
+                * We want to run the original callback here, which
+                * will return LDB_ERR_ENTRY_ALREADY_EXISTS to the
+                * caller, which will in turn know to rename the
+                * incoming record.  The error string is set in case
+                * this isn't handled properly at some point in the
+                * future.
+                */
                if (req->operation == LDB_RENAME) {
-                       DEBUG(0,(__location__ ": Unable to handle incoming renames where this would "
-                                "create a conflict. Incoming record is %s\n",
-                                ldb_dn_get_extended_linearized(req, conflict_dn, 1)));
+                       ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+                                              "Unable to handle incoming renames where this would "
+                                              "create a conflict. Incoming record is %s (caller to handle)\n",
+                                              ldb_dn_get_extended_linearized(req, conflict_dn, 1));
+
                        goto failed;
                }
 
-               guid = samdb_result_guid(req->op.add.message, "objectGUID");
+               guid = samdb_result_guid(msg, "objectGUID");
                if (GUID_all_zero(&guid)) {
                        DEBUG(0,(__location__ ": Failed to find objectGUID for conflicting incoming record %s\n",
                                 ldb_dn_get_linearized(conflict_dn)));
@@ -3494,30 +3553,20 @@ static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct
                        goto failed;
                }
 
-               DEBUG(1,(__location__ ": Resolving conflict record via incoming rename '%s' -> '%s'\n",
+               DEBUG(2,(__location__ ": Resolving conflict record via incoming rename '%s' -> '%s'\n",
                         ldb_dn_get_linearized(conflict_dn), ldb_dn_get_linearized(new_dn)));
 
-               switch (req->operation) {
-               case LDB_ADD:
-                       /* re-submit the request, but with a different
-                          callback, so we don't loop forever. */
-                       new_msg = ldb_msg_copy_shallow(req, req->op.add.message);
-                       if (!new_msg) {
-                               goto failed;
-                               DEBUG(0,(__location__ ": Failed to copy conflict DN message for %s\n",
-                                        ldb_dn_get_linearized(conflict_dn)));
-                       }
-                       new_msg->dn = new_dn;
-                       req->op.add.message = new_msg;
-                       req->callback = replmd_op_name_modify_callback;
-                       break;
-               case LDB_RENAME:
-                       req->op.rename.newdn = new_dn;
-                       req->callback = callback;
-                       break;
-               default:
-                       return ldb_module_operr(ar->module);
+               /* re-submit the request, but with a different
+                  callback, so we don't loop forever. */
+               new_msg = ldb_msg_copy_shallow(req, msg);
+               if (!new_msg) {
+                       goto failed;
+                       DEBUG(0,(__location__ ": Failed to copy conflict DN message for %s\n",
+                                ldb_dn_get_linearized(conflict_dn)));
                }
+               new_msg->dn = new_dn;
+               req->op.add.message = new_msg;
+               req->callback = replmd_op_name_modify_callback;
 
                return ldb_next_request(ar->module, req);
        } else {
@@ -3539,7 +3588,7 @@ static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct
                        goto failed;
                }
 
-               DEBUG(1,(__location__ ": Resolving conflict record via existing rename '%s' -> '%s'\n",
+               DEBUG(2,(__location__ ": Resolving conflict record via existing rename '%s' -> '%s'\n",
                         ldb_dn_get_linearized(conflict_dn), ldb_dn_get_linearized(new_dn)));
 
                ret = dsdb_module_rename(ar->module, conflict_dn, new_dn,
@@ -3561,8 +3610,6 @@ static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct
                        goto failed;
                }
 
-               req->callback = callback;
-
                return ldb_next_request(ar->module, req);
        }
 
@@ -3571,7 +3618,7 @@ failed:
         * will stop with an error, but there is not much else we can
         * do
         */
-       return callback(req, ares);
+       return ldb_module_done(req, ares->controls, ares->response, ares->error);
 }
 
 /*
@@ -3581,6 +3628,15 @@ failed:
  */
 static int replmd_op_add_callback(struct ldb_request *req, struct ldb_reply *ares)
 {
+       struct replmd_replicated_request *ar =
+               talloc_get_type_abort(req->context, struct replmd_replicated_request);
+
+       if (ar->objs->objects[ar->index_current].last_known_parent) {
+               /* This is like a conflict DN, where we put the object in LostAndFound
+                  see MS-DRSR 4.1.10.6.10 FindBestParentObject */
+               return replmd_op_possible_conflict_callback(req, ares, replmd_op_name_modify_callback);
+       }
+
        return replmd_op_possible_conflict_callback(req, ares, replmd_op_callback);
 }
 
@@ -3607,15 +3663,7 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
        struct ldb_val md_value;
        unsigned int i;
        int ret;
-
-       /*
-        * TODO: check if the parent object exist
-        */
-
-       /*
-        * TODO: handle the conflict case where an object with the
-        *       same name exist
-        */
+       bool remote_isDeleted = false;
 
        ldb = ldb_module_get_ctx(ar->module);
        msg = ar->objs->objects[ar->index_current].msg;
@@ -3660,6 +3708,9 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
                }
        }
 
+       remote_isDeleted = ldb_msg_find_attr_as_bool(msg,
+                                                    "isDeleted", false);
+
        /*
         * the meta data array is already sorted by the caller
         */
@@ -3679,6 +3730,15 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
 
        replmd_ldb_message_sort(msg, ar->schema);
 
+       if (!remote_isDeleted) {
+               ret = dsdb_module_schedule_sd_propagation(ar->module,
+                                                         ar->objs->partition_dn,
+                                                         msg->dn, true);
+               if (ret != LDB_SUCCESS) {
+                       return replmd_replicated_request_error(ar, ret);
+               }
+       }
+
        if (DEBUGLVL(4)) {
                char *s = ldb_ldif_message_string(ldb, ar, LDB_CHANGETYPE_ADD, msg);
                DEBUG(4, ("DRS replication add message:\n%s\n", s));
@@ -3727,6 +3787,10 @@ static int replmd_replicated_apply_search_for_parent_callback(struct ldb_request
        }
        if (ares->error != LDB_SUCCESS &&
            ares->error != LDB_ERR_NO_SUCH_OBJECT) {
+               /*
+                * TODO: deal with the above error that the parent object doesn't exist
+                */
+
                return ldb_module_done(ar->req, ares->controls,
                                        ares->response, ares->error);
        }
@@ -3737,13 +3801,8 @@ static int replmd_replicated_apply_search_for_parent_callback(struct ldb_request
                struct ldb_message *parent_msg = ares->message;
                struct ldb_message *msg = ar->objs->objects[ar->index_current].msg;
                struct ldb_dn *parent_dn;
-               int comp_num = ldb_dn_get_comp_num(msg->dn);
-               if (comp_num > 1) {
-                       if (!ldb_dn_remove_base_components(msg->dn, comp_num - 1)) {
-                               talloc_free(ares);
-                               return ldb_module_done(ar->req, NULL, NULL, ldb_module_operr(ar->module));
-                       }
-               }
+               int comp_num;
+
                if (!ldb_msg_check_string_attribute(msg, "isDeleted", "TRUE")
                    && ldb_msg_check_string_attribute(parent_msg, "isDeleted", "TRUE")) {
                        /* Per MS-DRSR 4.1.10.6.10
@@ -3787,9 +3846,19 @@ static int replmd_replicated_apply_search_for_parent_callback(struct ldb_request
                                                       ldb_dn_get_linearized(parent_msg->dn));
                                return ldb_module_done(ar->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
                        }
+                       ar->objs->objects[ar->index_current].last_known_parent
+                               = talloc_steal(ar->objs->objects[ar->index_current].msg, parent_msg->dn);
                } else {
                        parent_dn = parent_msg->dn;
                }
+
+               comp_num = ldb_dn_get_comp_num(msg->dn);
+               if (comp_num > 1) {
+                       if (!ldb_dn_remove_base_components(msg->dn, comp_num - 1)) {
+                               talloc_free(ares);
+                               return ldb_module_done(ar->req, NULL, NULL, ldb_module_operr(ar->module));
+                       }
+               }
                if (!ldb_dn_add_base(msg->dn, parent_dn)) {
                        talloc_free(ares);
                        return ldb_module_done(ar->req, NULL, NULL, ldb_module_operr(ar->module));
@@ -3801,7 +3870,11 @@ static int replmd_replicated_apply_search_for_parent_callback(struct ldb_request
                break;
 
        case LDB_REPLY_DONE:
-               ret = replmd_replicated_apply_add(ar);
+               if (ar->search_msg != NULL) {
+                       ret = replmd_replicated_apply_merge(ar);
+               } else {
+                       ret = replmd_replicated_apply_add(ar);
+               }
                if (ret != LDB_SUCCESS) {
                        return ldb_module_done(ar->req, NULL, NULL, ret);
                }
@@ -3827,7 +3900,11 @@ static int replmd_replicated_apply_search_for_parent(struct replmd_replicated_re
        ldb = ldb_module_get_ctx(ar->module);
 
        if (!ar->objs->objects[ar->index_current].parent_guid_value.data) {
-               return replmd_replicated_apply_add(ar);
+               if (ar->search_msg != NULL) {
+                       return replmd_replicated_apply_merge(ar);
+               } else {
+                       return replmd_replicated_apply_add(ar);
+               }
        }
 
        tmp_str = ldb_binary_encode(ar, ar->objs->objects[ar->index_current].parent_guid_value);
@@ -3850,8 +3927,10 @@ static int replmd_replicated_apply_search_for_parent(struct replmd_replicated_re
                                   ar->req);
        LDB_REQ_SET_LOCATION(search_req);
 
-       ret = ldb_request_add_control(search_req, LDB_CONTROL_SHOW_RECYCLED_OID,
-                                     true, NULL);
+       ret = dsdb_request_add_controls(search_req, 
+                                       DSDB_SEARCH_SHOW_RECYCLED|
+                                       DSDB_SEARCH_SHOW_DELETED|
+                                       DSDB_SEARCH_SHOW_EXTENDED_DN);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -3864,81 +3943,52 @@ static int replmd_replicated_apply_search_for_parent(struct replmd_replicated_re
  */
 static int replmd_replicated_handle_rename(struct replmd_replicated_request *ar,
                                           struct ldb_message *msg,
-                                          struct replPropertyMetaDataBlob *rmd,
-                                          struct replPropertyMetaDataBlob *omd,
                                           struct ldb_request *parent)
 {
-       struct replPropertyMetaData1 *md_remote;
-       struct replPropertyMetaData1 *md_local;
-
-       if (ldb_dn_compare(msg->dn, ar->search_msg->dn) == 0) {
-               /* no rename */
-               return LDB_SUCCESS;
-       }
-
-       /* now we need to check for double renames. We could have a
-        * local rename pending which our replication partner hasn't
-        * received yet. We choose which one wins by looking at the
-        * attribute stamps on the two objects, the newer one wins
-        */
-       md_remote = replmd_replPropertyMetaData1_find_attid(rmd, DRSUAPI_ATTID_name);
-       md_local  = replmd_replPropertyMetaData1_find_attid(omd, DRSUAPI_ATTID_name);
-       /* if there is no name attribute then we have to assume the
-          object we've received is in fact newer */
-       if (ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING ||
-           !md_remote || !md_local ||
-           replmd_replPropertyMetaData1_is_newer(md_local, md_remote)) {
-               struct ldb_request *req;
-               int ret;
-               TALLOC_CTX *tmp_ctx = talloc_new(msg);
-               struct ldb_result *res;
-
-               DEBUG(4,("replmd_replicated_request rename %s => %s\n",
-                        ldb_dn_get_linearized(ar->search_msg->dn),
-                        ldb_dn_get_linearized(msg->dn)));
-
+       struct ldb_request *req;
+       int ret;
+       TALLOC_CTX *tmp_ctx = talloc_new(msg);
+       struct ldb_result *res;
 
-               res = talloc_zero(tmp_ctx, struct ldb_result);
-               if (!res) {
-                       talloc_free(tmp_ctx);
-                       return ldb_oom(ldb_module_get_ctx(ar->module));
-               }
-
-               /* pass rename to the next module
-                * so it doesn't appear as an originating update */
-               ret = ldb_build_rename_req(&req, ldb_module_get_ctx(ar->module), tmp_ctx,
-                                          ar->search_msg->dn, msg->dn,
-                                          NULL,
-                                          ar,
-                                          replmd_op_rename_callback,
-                                          parent);
-               LDB_REQ_SET_LOCATION(req);
-               if (ret != LDB_SUCCESS) {
-                       talloc_free(tmp_ctx);
-                       return ret;
-               }
+       DEBUG(4,("replmd_replicated_request rename %s => %s\n",
+                ldb_dn_get_linearized(ar->search_msg->dn),
+                ldb_dn_get_linearized(msg->dn)));
 
-               ret = dsdb_request_add_controls(req, DSDB_MODIFY_RELAX);
-               if (ret != LDB_SUCCESS) {
-                       talloc_free(tmp_ctx);
-                       return ret;
-               }
 
-               ret = ldb_next_request(ar->module, req);
+       res = talloc_zero(tmp_ctx, struct ldb_result);
+       if (!res) {
+               talloc_free(tmp_ctx);
+               return ldb_oom(ldb_module_get_ctx(ar->module));
+       }
 
-               if (ret == LDB_SUCCESS) {
-                       ret = ldb_wait(req->handle, LDB_WAIT_ALL);
-               }
+       /* pass rename to the next module
+        * so it doesn't appear as an originating update */
+       ret = ldb_build_rename_req(&req, ldb_module_get_ctx(ar->module), tmp_ctx,
+                                  ar->search_msg->dn, msg->dn,
+                                  NULL,
+                                  ar,
+                                  replmd_op_rename_callback,
+                                  parent);
+       LDB_REQ_SET_LOCATION(req);
+       if (ret != LDB_SUCCESS) {
+               talloc_free(tmp_ctx);
+               return ret;
+       }
 
+       ret = dsdb_request_add_controls(req, DSDB_MODIFY_RELAX);
+       if (ret != LDB_SUCCESS) {
                talloc_free(tmp_ctx);
                return ret;
        }
 
-       /* we're going to keep our old object */
-       DEBUG(4,(__location__ ": Keeping object %s and rejecting older rename to %s\n",
-                ldb_dn_get_linearized(ar->search_msg->dn),
-                ldb_dn_get_linearized(msg->dn)));
-       return LDB_SUCCESS;
+       ret = ldb_next_request(ar->module, req);
+
+       if (ret == LDB_SUCCESS) {
+               ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+       }
+
+       talloc_free(tmp_ctx);
+       return ret;
 }
 
 
@@ -3957,9 +4007,17 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
        uint32_t j,ni=0;
        unsigned int removed_attrs = 0;
        int ret;
+       int (*callback)(struct ldb_request *req, struct ldb_reply *ares) = replmd_op_callback;
+       bool isDeleted = false;
+       bool local_isDeleted = false;
+       bool remote_isDeleted = false;
+       bool take_remote_isDeleted = false;
+       bool sd_updated = false;
+       bool renamed = false;
 
        ldb = ldb_module_get_ctx(ar->module);
        msg = ar->objs->objects[ar->index_current].msg;
+
        rmd = ar->objs->objects[ar->index_current].meta_data;
        ZERO_STRUCT(omd);
        omd.version = 1;
@@ -3979,9 +4037,67 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                }
        }
 
-       /* handle renames that come in over DRS */
-       ret = replmd_replicated_handle_rename(ar, msg, rmd, &omd, ar->req);
-       if (ret != LDB_SUCCESS) {
+       local_isDeleted = ldb_msg_find_attr_as_bool(ar->search_msg,
+                                                   "isDeleted", false);
+       remote_isDeleted = ldb_msg_find_attr_as_bool(msg,
+                                                    "isDeleted", false);
+
+       if (strcmp(ldb_dn_get_linearized(msg->dn), ldb_dn_get_linearized(ar->search_msg->dn)) == 0) {
+               ret = LDB_SUCCESS;
+       } else {
+               /*
+                * handle renames, even just by case that come in over
+                * DRS.  Changes in the parent DN don't hit us here,
+                * because the search for a parent will clean up those
+                * components.
+                *
+                * We also have already filtered out the case where
+                * the peer has an older name to what we have (see
+                * replmd_replicated_apply_search_callback())
+                */
+               renamed = true;
+               ret = replmd_replicated_handle_rename(ar, msg, ar->req);
+       }
+
+       /*
+        * This particular error code means that we already tried the
+        * conflict algrorithm, and the existing record name was newer, so we
+        * need to rename the incoming record
+        */
+       if (ret == LDB_ERR_ENTRY_ALREADY_EXISTS) {
+               struct GUID guid;
+               NTSTATUS status;
+               struct ldb_dn *new_dn;
+               status = GUID_from_ndr_blob(&ar->objs->objects[ar->index_current].guid_value, &guid);
+               /* This really, really can't fail */
+               SMB_ASSERT(NT_STATUS_IS_OK(status));
+
+               new_dn = replmd_conflict_dn(msg, msg->dn, &guid);
+               if (new_dn == NULL) {
+                       ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+                                                                 "Failed to form conflict DN for %s\n",
+                                                                 ldb_dn_get_linearized(msg->dn));
+
+                       return replmd_replicated_request_werror(ar, WERR_NOMEM);
+               }
+
+               ret = dsdb_module_rename(ar->module, ar->search_msg->dn, new_dn,
+                                        DSDB_FLAG_NEXT_MODULE, ar->req);
+               if (ret != LDB_SUCCESS) {
+                       ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+                                              "Failed to rename incoming conflicting dn '%s' (was '%s') to '%s' - %s\n",
+                                              ldb_dn_get_linearized(msg->dn),
+                                              ldb_dn_get_linearized(ar->search_msg->dn),
+                                              ldb_dn_get_linearized(new_dn),
+                                              ldb_errstring(ldb_module_get_ctx(ar->module)));
+                       return replmd_replicated_request_werror(ar, WERR_DS_DRA_DB_ERROR);
+               }
+
+               /* Set the callback to one that will fix up the name to be a conflict DN */
+               callback = replmd_op_name_modify_callback;
+               msg->dn = new_dn;
+               renamed = true;
+       } else if (ret != LDB_SUCCESS) {
                ldb_debug(ldb, LDB_DEBUG_FATAL,
                          "replmd_replicated_request rename %s => %s failed - %s\n",
                          ldb_dn_get_linearized(ar->search_msg->dn),
@@ -4038,6 +4154,16 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                                        }
                                }
                                nmd.ctr.ctr1.array[j].local_usn = ar->seq_num;
+                               switch (nmd.ctr.ctr1.array[j].attid) {
+                               case DRSUAPI_ATTID_ntSecurityDescriptor:
+                                       sd_updated = true;
+                                       break;
+                               case DRSUAPI_ATTID_isDeleted:
+                                       take_remote_isDeleted = true;
+                                       break;
+                               default:
+                                       break;
+                               }
                                found = true;
                                break;
                        }
@@ -4067,6 +4193,16 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                        }
                }
                nmd.ctr.ctr1.array[ni].local_usn = ar->seq_num;
+               switch (nmd.ctr.ctr1.array[ni].attid) {
+               case DRSUAPI_ATTID_ntSecurityDescriptor:
+                       sd_updated = true;
+                       break;
+               case DRSUAPI_ATTID_isDeleted:
+                       take_remote_isDeleted = true;
+                       break;
+               default:
+                       break;
+               }
                ni++;
        }
 
@@ -4101,6 +4237,25 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
        ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_replicated_apply_merge[%u]: replace %u attributes\n",
                  ar->index_current, msg->num_elements);
 
+       if (take_remote_isDeleted) {
+               isDeleted = remote_isDeleted;
+       } else {
+               isDeleted = local_isDeleted;
+       }
+
+       if (renamed) {
+               sd_updated = true;
+       }
+
+       if (sd_updated && !isDeleted) {
+               ret = dsdb_module_schedule_sd_propagation(ar->module,
+                                                         ar->objs->partition_dn,
+                                                         msg->dn, true);
+               if (ret != LDB_SUCCESS) {
+                       return ldb_operr(ldb);
+               }
+       }
+
        /* create the meta data value */
        ndr_err = ndr_push_struct_blob(&nmd_value, msg, &nmd,
                                       (ndr_push_flags_fn_t)ndr_push_replPropertyMetaDataBlob);
@@ -4145,7 +4300,7 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                                msg,
                                ar->controls,
                                ar,
-                               replmd_op_callback,
+                               callback,
                                ar->req);
        LDB_REQ_SET_LOCATION(change_req);
        if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
@@ -4186,15 +4341,91 @@ static int replmd_replicated_apply_search_callback(struct ldb_request *req,
                break;
 
        case LDB_REPLY_DONE:
-               if (ar->search_msg != NULL) {
-                       ret = replmd_replicated_apply_merge(ar);
-               } else {
+       {
+               struct replPropertyMetaData1 *md_remote;
+               struct replPropertyMetaData1 *md_local;
+
+               struct replPropertyMetaDataBlob omd;
+               const struct ldb_val *omd_value;
+               struct replPropertyMetaDataBlob *rmd;
+               struct ldb_message *msg;
+
+               ar->objs->objects[ar->index_current].last_known_parent = NULL;
+
+               /*
+                * This is the ADD case, find the appropriate parent,
+                * as this object doesn't exist locally:
+                */
+               if (ar->search_msg == NULL) {
+                       ret = replmd_replicated_apply_search_for_parent(ar);
+                       if (ret != LDB_SUCCESS) {
+                               return ldb_module_done(ar->req, NULL, NULL, ret);
+                       }
+                       talloc_free(ares);
+                       return LDB_SUCCESS;
+               }
+
+               /*
+                * Otherwise, in the MERGE case, work out if we are
+                * attempting a rename, and if so find the parent the
+                * newly renamed object wants to belong under (which
+                * may not be the parent in it's attached string DN
+                */
+               rmd = ar->objs->objects[ar->index_current].meta_data;
+               ZERO_STRUCT(omd);
+               omd.version = 1;
+
+               /* find existing meta data */
+               omd_value = ldb_msg_find_ldb_val(ar->search_msg, "replPropertyMetaData");
+               if (omd_value) {
+                       enum ndr_err_code ndr_err;
+                       ndr_err = ndr_pull_struct_blob(omd_value, ar, &omd,
+                                                      (ndr_pull_flags_fn_t)ndr_pull_replPropertyMetaDataBlob);
+                       if (!NDR_ERR_CODE_IS_SUCCESS(ndr_err)) {
+                               NTSTATUS nt_status = ndr_map_error2ntstatus(ndr_err);
+                               return replmd_replicated_request_werror(ar, ntstatus_to_werror(nt_status));
+                       }
+
+                       if (omd.version != 1) {
+                               return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
+                       }
+               }
+
+               /*
+                * now we need to check for double renames. We could have a
+                * local rename pending which our replication partner hasn't
+                * received yet. We choose which one wins by looking at the
+                * attribute stamps on the two objects, the newer one wins
+                */
+               md_remote = replmd_replPropertyMetaData1_find_attid(rmd, DRSUAPI_ATTID_name);
+               md_local  = replmd_replPropertyMetaData1_find_attid(&omd, DRSUAPI_ATTID_name);
+               /* if there is no name attribute then we have to assume the
+                  object we've received is in fact newer */
+               if (ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING ||
+                   !md_remote || !md_local ||
+                   replmd_replPropertyMetaData1_is_newer(md_local, md_remote)) {
                        ret = replmd_replicated_apply_search_for_parent(ar);
+               } else {
+                       msg = ar->objs->objects[ar->index_current].msg;
+
+                       /* Otherwise, just merge on the existing object, force no rename */
+                       DEBUG(4,(__location__ ": Keeping object %s and rejecting older rename to %s\n",
+                                ldb_dn_get_linearized(ar->search_msg->dn),
+                                ldb_dn_get_linearized(msg->dn)));
+
+                       /*
+                        * This assignment ensures that the strcmp()
+                        * in replmd_replicated_apply_merge() avoids
+                        * the rename call
+                        */
+                       msg->dn = ar->search_msg->dn;
+                       ret = replmd_replicated_apply_merge(ar);
                }
                if (ret != LDB_SUCCESS) {
                        return ldb_module_done(ar->req, NULL, NULL, ret);
                }
        }
+       }
 
        talloc_free(ares);
        return LDB_SUCCESS;
@@ -4365,7 +4596,7 @@ static int replmd_replicated_uptodate_modify(struct replmd_replicated_request *a
         *
         * plus optional values from our old vector and the one from the source_dsa
         */
-       nuv.ctr.ctr2.count = 1 + ouv.ctr.ctr2.count;
+       nuv.ctr.ctr2.count = ouv.ctr.ctr2.count;
        if (ruv) nuv.ctr.ctr2.count += ruv->count;
        nuv.ctr.ctr2.cursors = talloc_array(ar,
                                            struct drsuapi_DsReplicaCursor2,
@@ -4399,12 +4630,8 @@ static int replmd_replicated_uptodate_modify(struct replmd_replicated_request *a
 
                        found = true;
 
-                       /*
-                        * we update only the highest_usn and not the latest_sync_success time,
-                        * because the last success stands for direct replication
-                        */
                        if (ruv->cursors[i].highest_usn > nuv.ctr.ctr2.cursors[j].highest_usn) {
-                               nuv.ctr.ctr2.cursors[j].highest_usn = ruv->cursors[i].highest_usn;
+                               nuv.ctr.ctr2.cursors[j] = ruv->cursors[i];
                        }
                        break;
                }
@@ -4416,43 +4643,6 @@ static int replmd_replicated_uptodate_modify(struct replmd_replicated_request *a
                ni++;
        }
 
-       /*
-        * merge in the current highwatermark for the source_dsa
-        */
-       found = false;
-       for (j=0; j < ni; j++) {
-               if (!GUID_equal(&ar->objs->source_dsa->source_dsa_invocation_id,
-                               &nuv.ctr.ctr2.cursors[j].source_dsa_invocation_id)) {
-                       continue;
-               }
-
-               found = true;
-
-               /*
-                * here we update the highest_usn and last_sync_success time
-                * because we're directly replicating from the source_dsa
-                *
-                * and use the tmp_highest_usn because this is what we have just applied
-                * to our ldb
-                */
-               nuv.ctr.ctr2.cursors[j].highest_usn             = ar->objs->source_dsa->highwatermark.tmp_highest_usn;
-               nuv.ctr.ctr2.cursors[j].last_sync_success       = now;
-               break;
-       }
-       if (!found) {
-               /*
-                * here we update the highest_usn and last_sync_success time
-                * because we're directly replicating from the source_dsa
-                *
-                * and use the tmp_highest_usn because this is what we have just applied
-                * to our ldb
-                */
-               nuv.ctr.ctr2.cursors[ni].source_dsa_invocation_id= ar->objs->source_dsa->source_dsa_invocation_id;
-               nuv.ctr.ctr2.cursors[ni].highest_usn            = ar->objs->source_dsa->highwatermark.tmp_highest_usn;
-               nuv.ctr.ctr2.cursors[ni].last_sync_success      = now;
-               ni++;
-       }
-
        /*
         * finally correct the size of the cursors array
         */
@@ -4488,7 +4678,9 @@ static int replmd_replicated_uptodate_modify(struct replmd_replicated_request *a
        ZERO_STRUCT(nrf);
        nrf.version                                     = 1;
        nrf.ctr.ctr1                                    = *ar->objs->source_dsa;
-       nrf.ctr.ctr1.highwatermark.highest_usn          = nrf.ctr.ctr1.highwatermark.tmp_highest_usn;
+       nrf.ctr.ctr1.last_attempt                       = now;
+       nrf.ctr.ctr1.last_success                       = now;
+       nrf.ctr.ctr1.result_last_attempt                = WERR_OK;
 
        /*
         * first see if we already have a repsFrom value for the current source dsa