s4-drs: When comparing DN, check the DN itself and its extra bits if present
[mat/samba.git] / source4 / dsdb / samdb / ldb_modules / repl_meta_data.c
index cda9b99c7bc5a436ecb082be1d9029918eb0de18..ad703286eecf69008e032e1a4fe773480e6651d1 100644 (file)
@@ -2,10 +2,10 @@
    ldb database library
 
    Copyright (C) Simo Sorce  2004-2008
-   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005
-   Copyright (C) Andrew Tridgell 2005
+   Copyright (C) Andrew Bartlett <abartlet@samba.org> 2005-2013
+   Copyright (C) Andrew Tridgell 2005-2009
    Copyright (C) Stefan Metzmacher <metze@samba.org> 2007
-   Copyright (C) Matthieu Patou <mat@samba.org> 2010
+   Copyright (C) Matthieu Patou <mat@samba.org> 2010-2011
 
    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
@@ -92,14 +92,83 @@ struct replmd_replicated_request {
 
        uint64_t seq_num;
        bool is_urgent;
+
+       bool isDeleted;
 };
 
+static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar);
+static int replmd_delete_internals(struct ldb_module *module, struct ldb_request *req, bool re_delete);
+
 enum urgent_situation {
        REPL_URGENT_ON_CREATE = 1,
        REPL_URGENT_ON_UPDATE = 2,
        REPL_URGENT_ON_DELETE = 4
 };
 
+enum deletion_state {
+       OBJECT_NOT_DELETED=1,
+       OBJECT_DELETED=2,
+       OBJECT_RECYCLED=3,
+       OBJECT_TOMBSTONE=4,
+       OBJECT_REMOVED=5
+};
+
+static void replmd_deletion_state(struct ldb_module *module,
+                                 const struct ldb_message *msg,
+                                 enum deletion_state *current_state,
+                                 enum deletion_state *next_state)
+{
+       int ret;
+       bool enabled = false;
+
+       if (msg == NULL) {
+               *current_state = OBJECT_REMOVED;
+               if (next_state != NULL) {
+                       *next_state = OBJECT_REMOVED;
+               }
+               return;
+       }
+
+       ret = dsdb_recyclebin_enabled(module, &enabled);
+       if (ret != LDB_SUCCESS) {
+               enabled = false;
+       }
+
+       if (ldb_msg_check_string_attribute(msg, "isDeleted", "TRUE")) {
+               if (!enabled) {
+                       *current_state = OBJECT_TOMBSTONE;
+                       if (next_state != NULL) {
+                               *next_state = OBJECT_REMOVED;
+                       }
+                       return;
+               }
+
+               if (ldb_msg_check_string_attribute(msg, "isRecycled", "TRUE")) {
+                       *current_state = OBJECT_RECYCLED;
+                       if (next_state != NULL) {
+                               *next_state = OBJECT_REMOVED;
+                       }
+                       return;
+               }
+
+               *current_state = OBJECT_DELETED;
+               if (next_state != NULL) {
+                       *next_state = OBJECT_RECYCLED;
+               }
+               return;
+       }
+
+       *current_state = OBJECT_NOT_DELETED;
+       if (next_state == NULL) {
+               return;
+       }
+
+       if (enabled) {
+               *next_state = OBJECT_DELETED;
+       } else {
+               *next_state = OBJECT_TOMBSTONE;
+       }
+}
 
 static const struct {
        const char *update_name;
@@ -152,7 +221,7 @@ static bool replmd_check_urgent_attribute(const struct ldb_message_element *el)
 }
 
 
-static int replmd_replicated_apply_next(struct replmd_replicated_request *ar);
+static int replmd_replicated_apply_isDeleted(struct replmd_replicated_request *ar);
 
 /*
   initialise the module
@@ -454,10 +523,7 @@ static int replmd_op_callback(struct ldb_request *req, struct ldb_reply *ares)
        }
 
        if (ac->apply_mode) {
-               talloc_free(ares);
-               ac->index_current++;
-
-               ret = replmd_replicated_apply_next(ac);
+               ret = replmd_replicated_apply_isDeleted(ac);
                if (ret != LDB_SUCCESS) {
                        return ldb_module_done(ac->req, NULL, NULL, ret);
                }
@@ -594,7 +660,15 @@ static int replmd_replPropertyMetaData1_attid_sort(const struct replPropertyMeta
                                                   const struct replPropertyMetaData1 *m2,
                                                   const uint32_t *rdn_attid)
 {
-       if (m1->attid == m2->attid) {
+       /*
+        * This assignment seems inoccous, but it is critical for the
+        * system, as we need to do the comparisons as a unsigned
+        * quantity, not signed (enums are signed integers)
+        */
+       uint32_t attid_1 = m1->attid;
+       uint32_t attid_2 = m2->attid;
+
+       if (attid_1 == attid_2) {
                return 0;
        }
 
@@ -603,7 +677,7 @@ static int replmd_replPropertyMetaData1_attid_sort(const struct replPropertyMeta
         * so we need to return a value greater than zero
         * which means m1 is greater than m2
         */
-       if (m1->attid == *rdn_attid) {
+       if (attid_1 == *rdn_attid) {
                return 1;
        }
 
@@ -612,38 +686,78 @@ static int replmd_replPropertyMetaData1_attid_sort(const struct replPropertyMeta
         * so we need to return a value less than zero
         * which means m2 is greater than m1
         */
-       if (m2->attid == *rdn_attid) {
+       if (attid_2 == *rdn_attid) {
                return -1;
        }
 
-       return m1->attid > m2->attid ? 1 : -1;
+       /*
+        * See above regarding this being an unsigned comparison.
+        * Otherwise when the high bit is set on non-standard
+        * attributes, they would end up first, before objectClass
+        * (0).
+        */
+       return attid_1 > attid_2 ? 1 : -1;
+}
+
+static int replmd_replPropertyMetaDataCtr1_verify(struct ldb_context *ldb,
+                                                 struct replPropertyMetaDataCtr1 *ctr1,
+                                                 const struct dsdb_attribute *rdn_sa,
+                                                 struct ldb_dn *dn)
+{
+       if (ctr1->count == 0) {
+               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+                             "No elements found in replPropertyMetaData for %s!\n",
+                             ldb_dn_get_linearized(dn));
+               return LDB_ERR_CONSTRAINT_VIOLATION;
+       }
+       if (ctr1->array[ctr1->count - 1].attid != rdn_sa->attributeID_id) {
+               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+                             "No rDN found in replPropertyMetaData for %s!\n",
+                             ldb_dn_get_linearized(dn));
+               return LDB_ERR_CONSTRAINT_VIOLATION;
+       }
+
+       /* the objectClass attribute is value 0x00000000, so must be first */
+       if (ctr1->array[0].attid != DRSUAPI_ATTID_objectClass) {
+               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+                             "No objectClass found in replPropertyMetaData for %s!\n",
+                             ldb_dn_get_linearized(dn));
+               return LDB_ERR_OBJECT_CLASS_VIOLATION;
+       }
+
+       return LDB_SUCCESS;
 }
 
-static int replmd_replPropertyMetaDataCtr1_sort(struct replPropertyMetaDataCtr1 *ctr1,
-                                               const struct dsdb_schema *schema,
-                                               struct ldb_dn *dn)
+static int replmd_replPropertyMetaDataCtr1_sort_and_verify(struct ldb_context *ldb,
+                                                          struct replPropertyMetaDataCtr1 *ctr1,
+                                                          const struct dsdb_schema *schema,
+                                                          struct ldb_dn *dn)
 {
        const char *rdn_name;
        const struct dsdb_attribute *rdn_sa;
 
        rdn_name = ldb_dn_get_rdn_name(dn);
        if (!rdn_name) {
-               DEBUG(0,(__location__ ": No rDN for %s?\n", ldb_dn_get_linearized(dn)));
-               return LDB_ERR_OPERATIONS_ERROR;
+               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+                             __location__ ": No rDN for %s?\n",
+                             ldb_dn_get_linearized(dn));
+               return LDB_ERR_INVALID_DN_SYNTAX;
        }
 
        rdn_sa = dsdb_attribute_by_lDAPDisplayName(schema, rdn_name);
        if (rdn_sa == NULL) {
-               DEBUG(0,(__location__ ": No sa found for rDN %s for %s\n", rdn_name, ldb_dn_get_linearized(dn)));
-               return LDB_ERR_OPERATIONS_ERROR;
+               ldb_debug_set(ldb, LDB_DEBUG_FATAL,
+                             __location__ ": No sa found for rDN %s for %s\n",
+                             rdn_name, ldb_dn_get_linearized(dn));
+               return LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE;
        }
 
        DEBUG(6,("Sorting rpmd with attid exception %u rDN=%s DN=%s\n",
                 rdn_sa->attributeID_id, rdn_name, ldb_dn_get_linearized(dn)));
 
-       LDB_TYPESAFE_QSORT(ctr1->array, ctr1->count, &rdn_sa->attributeID_id, replmd_replPropertyMetaData1_attid_sort);
-
-       return LDB_SUCCESS;
+       LDB_TYPESAFE_QSORT(ctr1->array, ctr1->count, &rdn_sa->attributeID_id,
+                          replmd_replPropertyMetaData1_attid_sort);
+       return replmd_replPropertyMetaDataCtr1_verify(ldb, ctr1, rdn_sa, dn);
 }
 
 static int replmd_ldb_message_element_attid_sort(const struct ldb_message_element *e1,
@@ -751,6 +865,7 @@ static int replmd_add_fix_la(struct ldb_module *module, struct ldb_message_eleme
  */
 static int replmd_add(struct ldb_module *module, struct ldb_request *req)
 {
+       struct samldb_msds_intid_persistant *msds_intid_struct;
        struct ldb_context *ldb;
         struct ldb_control *control;
        struct replmd_replicated_request *ac;
@@ -958,8 +1073,9 @@ static int replmd_add(struct ldb_module *module, struct ldb_request *req)
        /*
         * sort meta data array, and move the rdn attribute entry to the end
         */
-       ret = replmd_replPropertyMetaDataCtr1_sort(&nmd.ctr.ctr1, ac->schema, msg->dn);
+       ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &nmd.ctr.ctr1, ac->schema, msg->dn);
        if (ret != LDB_SUCCESS) {
+               ldb_asprintf_errstring(ldb, "%s: error during direct ADD: %s", __func__, ldb_errstring(ldb));
                talloc_free(ac);
                return ret;
        }
@@ -1013,7 +1129,17 @@ static int replmd_add(struct ldb_module *module, struct ldb_request *req)
         */
        replmd_ldb_message_sort(msg, ac->schema);
 
+       /*
+        * Assert that we do have an objectClass
+        */
        objectclass_el = ldb_msg_find_element(msg, "objectClass");
+       if (objectclass_el == NULL) {
+               ldb_asprintf_errstring(ldb, __location__
+                                      ": objectClass missing on %s\n",
+                                      ldb_dn_get_linearized(msg->dn));
+               talloc_free(ac);
+               return LDB_ERR_OBJECT_CLASS_VIOLATION;
+       }
        is_urgent = replmd_check_urgent_objectclass(objectclass_el,
                                                        REPL_URGENT_ON_CREATE);
 
@@ -1053,7 +1179,14 @@ static int replmd_add(struct ldb_module *module, struct ldb_request *req)
        if (control) {
                control->critical = 0;
        }
+       if (ldb_dn_compare_base(ac->schema->base_dn, req->op.add.message->dn) != 0) {
 
+               /* Update the usn in the SAMLDB_MSDS_INTID_OPAQUE opaque */
+               msds_intid_struct = (struct samldb_msds_intid_persistant *) ldb_get_opaque(ldb, SAMLDB_MSDS_INTID_OPAQUE);
+               if (msds_intid_struct) {
+                       msds_intid_struct->usn = ac->seq_num;
+               }
+       }
        /* go on with the call chain */
        return ldb_next_request(module, down_req);
 }
@@ -1076,6 +1209,7 @@ static int replmd_update_rpmd_element(struct ldb_context *ldb,
        uint32_t i;
        const struct dsdb_attribute *a;
        struct replPropertyMetaData1 *md1;
+       bool may_skip = false;
 
        a = dsdb_attribute_by_lDAPDisplayName(schema, el->name);
        if (a == NULL) {
@@ -1094,13 +1228,34 @@ static int replmd_update_rpmd_element(struct ldb_context *ldb,
                return LDB_SUCCESS;
        }
 
-       /* if the attribute's value haven't changed then return LDB_SUCCESS
-        * Unless we have the provision control or if the attribute is
-        * interSiteTopologyGenerator as this page explain: http://support.microsoft.com/kb/224815
-        * this attribute is periodicaly written by the DC responsible for the intersite generation
-        * in a given site
+       /*
+        * if the attribute's value haven't changed, and this isn't
+        * just a delete of everything then return LDB_SUCCESS Unless
+        * we have the provision control or if the attribute is
+        * interSiteTopologyGenerator as this page explain:
+        * http://support.microsoft.com/kb/224815 this attribute is
+        * periodicaly written by the DC responsible for the intersite
+        * generation in a given site
+        *
+        * Unchanged could be deleting or replacing an already-gone
+        * thing with an unconstrained delete/empty replace or a
+        * replace with the same value, but not an add with the same
+        * value because that could be about adding a duplicate (which
+        * is for someone else to error out on).
         */
-       if (old_el != NULL && ldb_msg_element_compare(el, old_el) == 0) {
+       if (old_el != NULL && ldb_msg_element_equal_ordered(el, old_el)) {
+               if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_REPLACE) {
+                       may_skip = true;
+               }
+       } else if (old_el == NULL && el->num_values == 0) {
+               if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_REPLACE) {
+                       may_skip = true;
+               } else if (LDB_FLAG_MOD_TYPE(el->flags) == LDB_FLAG_MOD_DELETE) {
+                       may_skip = true;
+               }
+       }
+
+       if (may_skip) {
                if (strcmp(el->name, "interSiteTopologyGenerator") != 0 &&
                    !ldb_request_get_control(req, LDB_CONTROL_PROVISION_OID)) {
                        /*
@@ -1302,12 +1457,6 @@ static int replmd_update_rpmd(struct ldb_module *module,
                        return ret;
                }
 
-               objectclass_el = ldb_msg_find_element(res->msgs[0], "objectClass");
-               if (is_urgent && replmd_check_urgent_objectclass(objectclass_el,
-                                                               situation)) {
-                       *is_urgent = true;
-               }
-
                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)"\
@@ -1332,12 +1481,6 @@ static int replmd_update_rpmd(struct ldb_module *module,
                        return ret;
                }
 
-               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",
@@ -1368,12 +1511,33 @@ static int replmd_update_rpmd(struct ldb_module *module,
                                return ret;
                        }
 
-                       if (is_urgent && !*is_urgent && (situation == REPL_URGENT_ON_UPDATE)) {
+                       if (!*is_urgent && (situation == REPL_URGENT_ON_UPDATE)) {
                                *is_urgent = replmd_check_urgent_attribute(&msg->elements[i]);
                        }
 
                }
        }
+
+       /*
+        * Assert that we have an objectClass attribute - this is major
+        * corruption if we don't have this!
+        */
+       objectclass_el = ldb_msg_find_element(res->msgs[0], "objectClass");
+       if (objectclass_el != NULL) {
+               /*
+                * Now check if this objectClass means we need to do urgent replication
+                */
+               if (!*is_urgent && replmd_check_urgent_objectclass(objectclass_el,
+                                                                  situation)) {
+                       *is_urgent = true;
+               }
+       } else if (!ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) {
+               ldb_asprintf_errstring(ldb, __location__
+                                      ": objectClass missing on %s\n",
+                                      ldb_dn_get_linearized(msg->dn));
+               return LDB_ERR_OBJECT_CLASS_VIOLATION;
+       }
+
        /*
         * replmd_update_rpmd_element has done an update if the
         * seq_num is set
@@ -1383,7 +1547,8 @@ static int replmd_update_rpmd(struct ldb_module *module,
                struct ldb_message_element *el;
 
                /*if we are RODC and this is a DRSR update then its ok*/
-               if (!ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
+               if (!ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)
+                   && !ldb_request_get_control(req, DSDB_CONTROL_DBCHECK_MODIFY_RO_REPLICA)) {
                        unsigned instanceType;
 
                        ret = samdb_rodc(ldb, rodc);
@@ -1407,8 +1572,9 @@ static int replmd_update_rpmd(struct ldb_module *module,
                        return LDB_ERR_OPERATIONS_ERROR;
                }
 
-               ret = replmd_replPropertyMetaDataCtr1_sort(&omd.ctr.ctr1, schema, msg->dn);
+               ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &omd.ctr.ctr1, schema, msg->dn);
                if (ret != LDB_SUCCESS) {
+                       ldb_asprintf_errstring(ldb, "%s: %s", __func__, ldb_errstring(ldb));
                        return ret;
                }
 
@@ -1447,7 +1613,8 @@ static int parsed_dn_compare(struct parsed_dn *pdn1, struct parsed_dn *pdn2)
 
 static struct parsed_dn *parsed_dn_find(struct parsed_dn *pdn,
                                        unsigned int count, struct GUID *guid,
-                                       struct ldb_dn *dn)
+                                       struct ldb_dn *dn,
+                                       DATA_BLOB extra)
 {
        struct parsed_dn *ret;
        unsigned int i;
@@ -1463,6 +1630,18 @@ static struct parsed_dn *parsed_dn_find(struct parsed_dn *pdn,
                return NULL;
        }
        BINARY_ARRAY_SEARCH(pdn, count, guid, guid, GUID_compare, ret);
+       if (extra.length && ret) {
+               /*
+                * Check the content of the extra it's a match only
+                * if the dn match but also the extra.
+                */
+               if (ret->dsdb_dn->extra_part.length != extra.length) {
+                       return NULL;
+               }
+               if (memcmp(ret->dsdb_dn->extra_part.data, extra.data, extra.length) != 0) {
+                       return NULL;
+               }
+       }
        return ret;
 }
 
@@ -1813,7 +1992,9 @@ static int replmd_modify_la_add(struct ldb_module *module,
 
        /* for each new value, see if it exists already with the same GUID */
        for (i=0; i<el->num_values; i++) {
-               struct parsed_dn *p = parsed_dn_find(old_dns, old_num_values, dns[i].guid, NULL);
+               struct parsed_dn *p = parsed_dn_find(old_dns, old_num_values,
+                                                    dns[i].guid, NULL,
+                                                    dns[i].dsdb_dn->extra_part);
                if (p == NULL) {
                        /* this is a new linked attribute value */
                        new_values = talloc_realloc(tmp_ctx, new_values, struct ldb_val, num_new_values+1);
@@ -1952,7 +2133,8 @@ static int replmd_modify_la_delete(struct ldb_module *module,
                struct parsed_dn *p2;
                uint32_t rmd_flags;
 
-               p2 = parsed_dn_find(old_dns, old_el->num_values, p->guid, NULL);
+               p2 = parsed_dn_find(old_dns, old_el->num_values, p->guid, NULL,
+                                   p->dsdb_dn->extra_part);
                if (!p2) {
                        ldb_asprintf_errstring(ldb, "Attribute %s doesn't exist for target GUID %s",
                                               el->name, GUID_string(tmp_ctx, p->guid));
@@ -1981,7 +2163,9 @@ static int replmd_modify_la_delete(struct ldb_module *module,
                struct parsed_dn *p = &old_dns[i];
                uint32_t rmd_flags;
 
-               if (el->num_values && parsed_dn_find(dns, el->num_values, p->guid, NULL) == NULL) {
+               if (el->num_values && parsed_dn_find(dns, el->num_values,
+                                                    p->guid, NULL,
+                                                    p->dsdb_dn->extra_part) == NULL) {
                        continue;
                }
 
@@ -2084,7 +2268,8 @@ static int replmd_modify_la_replace(struct ldb_module *module,
                        return ret;
                }
 
-               p = parsed_dn_find(dns, el->num_values, old_p->guid, NULL);
+               p = parsed_dn_find(dns, el->num_values, old_p->guid, NULL,
+                                  old_p->dsdb_dn->extra_part);
                if (p) {
                        /* we don't delete it if we are re-adding it */
                        continue;
@@ -2106,7 +2291,8 @@ static int replmd_modify_la_replace(struct ldb_module *module,
 
                if (old_dns &&
                    (old_p = parsed_dn_find(old_dns,
-                                           old_num_values, p->guid, NULL)) != NULL) {
+                                           old_num_values, p->guid, NULL,
+                                           p->dsdb_dn->extra_part)) != NULL) {
                        /* update in place */
                        ret = replmd_update_la_val(old_el->values, old_p->v, p->dsdb_dn,
                                                   old_p->dsdb_dn, invocation_id,
@@ -2294,6 +2480,7 @@ static int replmd_modify_handle_linked_attribs(struct ldb_module *module,
 
 static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
 {
+       struct samldb_msds_intid_persistant *msds_intid_struct;
        struct ldb_context *ldb;
        struct replmd_replicated_request *ac;
        struct ldb_request *down_req;
@@ -2303,12 +2490,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");
@@ -2426,6 +2629,14 @@ static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
                }
        }
 
+       if (!ldb_dn_compare_base(ac->schema->base_dn, msg->dn)) {
+               /* Update the usn in the SAMLDB_MSDS_INTID_OPAQUE opaque */
+               msds_intid_struct = (struct samldb_msds_intid_persistant *) ldb_get_opaque(ldb, SAMLDB_MSDS_INTID_OPAQUE);
+               if (msds_intid_struct) {
+                       msds_intid_struct->usn = ac->seq_num;
+               }
+       }
+
        /* go on with the call chain */
        return ldb_next_request(module, down_req);
 }
@@ -2677,8 +2888,11 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are
 }
 
 /*
-   remove links from objects that point at this object when an object
-   is deleted
+ * remove links from objects that point at this object when an object
+ * is deleted.  We remove it from the NEXT module per MS-DRSR 5.160
+ * RemoveObj which states that link removal due to the object being
+ * deleted is NOT an originating update - they just go away!
+ *
  */
 static int replmd_delete_remove_link(struct ldb_module *module,
                                     const struct dsdb_schema *schema,
@@ -2759,8 +2973,13 @@ static int replmd_delete_remove_link(struct ldb_module *module,
 
   This also handles the mapping of delete to a rename operation
   to allow deletes to be replicated.
+
+  It also handles the incoming deleted objects, to ensure they are
+  fully deleted here.  In that case re_delete is true, and we do not
+  use this as a signal to change the deleted state, just reinforce it.
+
  */
-static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
+static int replmd_delete_internals(struct ldb_module *module, struct ldb_request *req, bool re_delete)
 {
        int ret = LDB_ERR_OTHER;
        bool retb, disallow_move_on_delete;
@@ -2786,16 +3005,26 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                "trustType", "trustAttributes", "userAccountControl", "uSNChanged", "uSNCreated", "whenCreated",
                "whenChanged", NULL};
        unsigned int i, el_count = 0;
-       enum deletion_state { OBJECT_NOT_DELETED=1, OBJECT_DELETED=2, OBJECT_RECYCLED=3,
-                                               OBJECT_TOMBSTONE=4, OBJECT_REMOVED=5 };
        enum deletion_state deletion_state, next_deletion_state;
-       bool enabled;
-       int functional_level;
 
        if (ldb_dn_is_special(req->op.del.dn)) {
                return ldb_next_request(module, req);
        }
 
+       /*
+        * We have to allow dbcheck to remove an object that
+        * is beyond repair, and to do so totally.  This could
+        * mean we we can get a partial object from the other
+        * DC, causing havoc, so dbcheck suggests
+        * re-replication first.  dbcheck sets both DBCHECK
+        * and RELAX in this situation.
+        */
+       if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)
+           && ldb_request_get_control(req, DSDB_CONTROL_DBCHECK)) {
+               /* really, really remove it */
+               return ldb_next_request(module, req);
+       }
+
        tmp_ctx = talloc_new(ldb);
        if (!tmp_ctx) {
                ldb_oom(ldb);
@@ -2804,11 +3033,10 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
 
        schema = dsdb_get_schema(ldb, tmp_ctx);
        if (!schema) {
+               talloc_free(tmp_ctx);
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
-       functional_level = dsdb_functional_level(ldb);
-
        old_dn = ldb_dn_copy(tmp_ctx, req->op.del.dn);
 
        /* we need the complete msg off disk, so we can work out which
@@ -2819,50 +3047,46 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                                    DSDB_SEARCH_REVEAL_INTERNALS |
                                    DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT, req);
        if (ret != LDB_SUCCESS) {
+               ldb_asprintf_errstring(ldb_module_get_ctx(module),
+                                      "repmd_delete: Failed to %s %s, because we failed to find it: %s",
+                                      re_delete ? "re-delete" : "delete",
+                                      ldb_dn_get_linearized(old_dn),
+                                      ldb_errstring(ldb_module_get_ctx(module)));
                talloc_free(tmp_ctx);
                return ret;
        }
        old_msg = res->msgs[0];
 
+       replmd_deletion_state(module, old_msg,
+                             &deletion_state,
+                             &next_deletion_state);
 
-       ret = dsdb_recyclebin_enabled(module, &enabled);
-       if (ret != LDB_SUCCESS) {
-               talloc_free(tmp_ctx);
-               return ret;
-       }
-
-       if (ldb_msg_check_string_attribute(old_msg, "isDeleted", "TRUE")) {
-               if (!enabled) {
-                       deletion_state = OBJECT_TOMBSTONE;
-                       next_deletion_state = OBJECT_REMOVED;
-               } else if (ldb_msg_check_string_attribute(old_msg, "isRecycled", "TRUE")) {
-                       deletion_state = OBJECT_RECYCLED;
-                       next_deletion_state = OBJECT_REMOVED;
-               } else {
-                       deletion_state = OBJECT_DELETED;
-                       next_deletion_state = OBJECT_RECYCLED;
-               }
-       } else {
-               deletion_state = OBJECT_NOT_DELETED;
-               if (enabled) {
-                       next_deletion_state = OBJECT_DELETED;
-               } else {
-                       next_deletion_state = OBJECT_TOMBSTONE;
-               }
+       /* This supports us noticing an incoming isDeleted and acting on it */
+       if (re_delete) {
+               SMB_ASSERT(deletion_state > OBJECT_NOT_DELETED);
+               next_deletion_state = deletion_state;
        }
 
        if (next_deletion_state == OBJECT_REMOVED) {
-               struct auth_session_info *session_info =
-                               (struct auth_session_info *)ldb_get_opaque(ldb, "sessionInfo");
-               if (security_session_user_level(session_info, NULL) != SECURITY_SYSTEM) {
-                       ldb_asprintf_errstring(ldb, "Refusing to delete deleted object %s",
-                                       ldb_dn_get_linearized(old_msg->dn));
-                       return LDB_ERR_UNWILLING_TO_PERFORM;
+               /*
+                * We have to prevent objects being deleted, even if
+                * the administrator really wants them gone, as
+                * without the tombstone, we can get a partial object
+                * from the other DC, causing havoc.
+                *
+                * The only other valid case is when the 180 day
+                * timeout has expired, when relax is specified.
+                */
+               if (ldb_request_get_control(req, LDB_CONTROL_RELAX_OID)) {
+                       /* it is already deleted - really remove it this time */
+                       talloc_free(tmp_ctx);
+                       return ldb_next_request(module, req);
                }
 
-               /* it is already deleted - really remove it this time */
-               talloc_free(tmp_ctx);
-               return ldb_next_request(module, req);
+               ldb_asprintf_errstring(ldb, "Refusing to delete tombstone object %s.  "
+                                      "This check is to prevent corruption of the replicated state.",
+                                      ldb_dn_get_linearized(old_msg->dn));
+               return LDB_ERR_UNWILLING_TO_PERFORM;
        }
 
        rdn_name = ldb_dn_get_rdn_name(old_dn);
@@ -2881,45 +3105,62 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
 
        msg->dn = old_dn;
 
-       if (deletion_state == OBJECT_NOT_DELETED){
-               /* consider the SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE flag */
-               disallow_move_on_delete =
-                       (ldb_msg_find_attr_as_int(old_msg, "systemFlags", 0)
-                               & SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE);
+       /* consider the SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE flag */
+       disallow_move_on_delete =
+               (ldb_msg_find_attr_as_int(old_msg, "systemFlags", 0)
+                & SYSTEM_FLAG_DISALLOW_MOVE_ON_DELETE);
 
-               /* work out where we will be renaming this object to */
-               if (!disallow_move_on_delete) {
-                       ret = dsdb_get_deleted_objects_dn(ldb, tmp_ctx, old_dn,
-                                                         &new_dn);
-                       if (ret != LDB_SUCCESS) {
-                               /* this is probably an attempted delete on a partition
-                                * that doesn't allow delete operations, such as the
-                                * schema partition */
-                               ldb_asprintf_errstring(ldb, "No Deleted Objects container for DN %s",
-                                                          ldb_dn_get_linearized(old_dn));
-                               talloc_free(tmp_ctx);
-                               return LDB_ERR_UNWILLING_TO_PERFORM;
-                       }
-               } else {
+       /* work out where we will be renaming this object to */
+       if (!disallow_move_on_delete) {
+               struct ldb_dn *deleted_objects_dn;
+               ret = dsdb_get_deleted_objects_dn(ldb, tmp_ctx, old_dn,
+                                                 &deleted_objects_dn);
+
+               /*
+                * We should not move objects if we can't find the
+                * deleted objects DN.  Not moving (or otherwise
+                * harming) the Deleted Objects DN itself is handled
+                * in the caller.
+                */
+               if (re_delete && (ret != LDB_SUCCESS)) {
                        new_dn = ldb_dn_get_parent(tmp_ctx, old_dn);
                        if (new_dn == NULL) {
                                ldb_module_oom(module);
                                talloc_free(tmp_ctx);
                                return LDB_ERR_OPERATIONS_ERROR;
                        }
+               } else if (ret != LDB_SUCCESS) {
+                       /* this is probably an attempted delete on a partition
+                        * that doesn't allow delete operations, such as the
+                        * schema partition */
+                       ldb_asprintf_errstring(ldb, "No Deleted Objects container for DN %s",
+                                              ldb_dn_get_linearized(old_dn));
+                       talloc_free(tmp_ctx);
+                       return LDB_ERR_UNWILLING_TO_PERFORM;
+               } else {
+                       new_dn = deleted_objects_dn;
                }
+       } else {
+               new_dn = ldb_dn_get_parent(tmp_ctx, old_dn);
+               if (new_dn == NULL) {
+                       ldb_module_oom(module);
+                       talloc_free(tmp_ctx);
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+       }
 
+       if (deletion_state == OBJECT_NOT_DELETED) {
                /* get the objects GUID from the search we just did */
                guid = samdb_result_guid(old_msg, "objectGUID");
 
                /* Add a formatted child */
                retb = ldb_dn_add_child_fmt(new_dn, "%s=%s\\0ADEL:%s",
-                                               rdn_name,
-                                               ldb_dn_escape_value(tmp_ctx, *rdn_value),
-                                               GUID_string(tmp_ctx, &guid));
+                                           rdn_name,
+                                           ldb_dn_escape_value(tmp_ctx, *rdn_value),
+                                           GUID_string(tmp_ctx, &guid));
                if (!retb) {
                        DEBUG(0,(__location__ ": Unable to add a formatted child to dn: %s",
-                                       ldb_dn_get_linearized(new_dn)));
+                                ldb_dn_get_linearized(new_dn)));
                        talloc_free(tmp_ctx);
                        return LDB_ERR_OPERATIONS_ERROR;
                }
@@ -2932,6 +3173,30 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                        return ret;
                }
                msg->elements[el_count++].flags = LDB_FLAG_MOD_REPLACE;
+       } else {
+               /*
+                * No matter what has happened with other renames etc, try again to
+                * get this to be under the deleted DN. See MS-DRSR 5.160 RemoveObj
+                */
+
+               struct ldb_dn *rdn = ldb_dn_copy(tmp_ctx, old_dn);
+               retb = ldb_dn_remove_base_components(rdn, ldb_dn_get_comp_num(rdn) - 1);
+               if (!retb) {
+                       DEBUG(0,(__location__ ": Unable to add a prepare rdn of %s",
+                                ldb_dn_get_linearized(rdn)));
+                       talloc_free(tmp_ctx);
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+               SMB_ASSERT(ldb_dn_get_comp_num(rdn) == 1);
+
+               retb = ldb_dn_add_child(new_dn, rdn);
+               if (!retb) {
+                       DEBUG(0,(__location__ ": Unable to add rdn %s to base dn: %s",
+                                ldb_dn_get_linearized(rdn),
+                                ldb_dn_get_linearized(new_dn)));
+                       talloc_free(tmp_ctx);
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
        }
 
        /*
@@ -2951,62 +3216,63 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
          see MS-ADTS "Tombstone Requirements" section 3.1.1.5.5.1.1
         */
 
-       /* we need the storage form of the parent GUID */
-       ret = dsdb_module_search_dn(module, tmp_ctx, &parent_res,
-                                   ldb_dn_get_parent(tmp_ctx, old_dn), NULL,
-                                   DSDB_FLAG_NEXT_MODULE |
-                                   DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
-                                   DSDB_SEARCH_REVEAL_INTERNALS|
-                                   DSDB_SEARCH_SHOW_RECYCLED, req);
-       if (ret != LDB_SUCCESS) {
-               talloc_free(tmp_ctx);
-               return ret;
-       }
-
-       if (deletion_state == OBJECT_NOT_DELETED){
-               ret = ldb_msg_add_steal_string(msg, "lastKnownParent",
-                                                  ldb_dn_get_extended_linearized(tmp_ctx, parent_res->msgs[0]->dn, 1));
+       if (deletion_state == OBJECT_NOT_DELETED) {
+               /* we need the storage form of the parent GUID */
+               ret = dsdb_module_search_dn(module, tmp_ctx, &parent_res,
+                                           ldb_dn_get_parent(tmp_ctx, old_dn), NULL,
+                                           DSDB_FLAG_NEXT_MODULE |
+                                           DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
+                                           DSDB_SEARCH_REVEAL_INTERNALS|
+                                           DSDB_SEARCH_SHOW_RECYCLED, req);
                if (ret != LDB_SUCCESS) {
-                       DEBUG(0,(__location__ ": Failed to add lastKnownParent string to the msg\n"));
-                       ldb_module_oom(module);
+                       ldb_asprintf_errstring(ldb_module_get_ctx(module),
+                                              "repmd_delete: Failed to %s %s, because we failed to find it's parent (%s): %s",
+                                              re_delete ? "re-delete" : "delete",
+                                              ldb_dn_get_linearized(old_dn),
+                                              ldb_dn_get_linearized(ldb_dn_get_parent(tmp_ctx, old_dn)),
+                                              ldb_errstring(ldb_module_get_ctx(module)));
                        talloc_free(tmp_ctx);
                        return ret;
                }
-               msg->elements[el_count++].flags = LDB_FLAG_MOD_ADD;
-       }
 
-       switch (next_deletion_state){
-
-       case OBJECT_DELETED:
-
-               ret = ldb_msg_add_value(msg, "msDS-LastKnownRDN", rdn_value, NULL);
+               ret = ldb_msg_add_steal_string(msg, "lastKnownParent",
+                                                  ldb_dn_get_extended_linearized(tmp_ctx, parent_res->msgs[0]->dn, 1));
                if (ret != LDB_SUCCESS) {
-                       DEBUG(0,(__location__ ": Failed to add msDS-LastKnownRDN string to the msg\n"));
+                       DEBUG(0,(__location__ ": Failed to add lastKnownParent string to the msg\n"));
                        ldb_module_oom(module);
                        talloc_free(tmp_ctx);
                        return ret;
                }
-               msg->elements[el_count++].flags = LDB_FLAG_MOD_ADD;
-
-               ret = ldb_msg_add_empty(msg, "objectCategory", LDB_FLAG_MOD_REPLACE, NULL);
-               if (ret != LDB_SUCCESS) {
-                       talloc_free(tmp_ctx);
-                       ldb_module_oom(module);
-                       return ret;
-               }
+               msg->elements[el_count++].flags = LDB_FLAG_MOD_REPLACE;
 
-               ret = ldb_msg_add_empty(msg, "sAMAccountType", LDB_FLAG_MOD_REPLACE, NULL);
-               if (ret != LDB_SUCCESS) {
-                       talloc_free(tmp_ctx);
-                       ldb_module_oom(module);
-                       return ret;
+               if (next_deletion_state == OBJECT_DELETED) {
+                       ret = ldb_msg_add_value(msg, "msDS-LastKnownRDN", rdn_value, NULL);
+                       if (ret != LDB_SUCCESS) {
+                               DEBUG(0,(__location__ ": Failed to add msDS-LastKnownRDN string to the msg\n"));
+                               ldb_module_oom(module);
+                               talloc_free(tmp_ctx);
+                               return ret;
+                       }
+                       msg->elements[el_count++].flags = LDB_FLAG_MOD_ADD;
                }
+       }
 
-               break;
+       switch (next_deletion_state) {
 
        case OBJECT_RECYCLED:
        case OBJECT_TOMBSTONE:
 
+               /*
+                * MS-ADTS 3.1.1.5.5.1.1 Tombstone Requirements
+                * describes what must be removed from a tombstone
+                * object
+                *
+                * MS-ADTS 3.1.1.5.5.1.3 Recycled-Object Requirements
+                * describes what must be removed from a recycled
+                * object
+                *
+                */
+
                /*
                 * we also mark it as recycled, meaning this object can't be
                 * recovered (we are stripping its attributes).
@@ -3023,7 +3289,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;
                }
 
                /* work out which of the old attributes we will be removing */
@@ -3059,8 +3325,13 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                                */
                                continue;
                        }
-                       if (!sa->linkID && ldb_attr_in_list(preserved_attrs, el->name)) {
-                               continue;
+                       if (!sa->linkID) {
+                               if (ldb_attr_in_list(preserved_attrs, el->name)) {
+                                       continue;
+                               }
+                               if (sa->searchFlags & SEARCH_FLAG_PRESERVEONDELETE) {
+                                       continue;
+                               }
                        }
                        ret = ldb_msg_add_empty(msg, el->name, LDB_FLAG_MOD_DELETE, &el);
                        if (ret != LDB_SUCCESS) {
@@ -3069,6 +3340,41 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                                return ret;
                        }
                }
+
+               /* Duplicate with the below - we remove the
+                * samAccountType as an originating update, in case it
+                * somehow came back.  The objectCategory will have
+                * gone in the above */
+               ret = ldb_msg_add_empty(msg, "sAMAccountType", LDB_FLAG_MOD_REPLACE, NULL);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(tmp_ctx);
+                       ldb_module_oom(module);
+                       return ret;
+               }
+
+               break;
+
+       case OBJECT_DELETED:
+               /*
+                * MS-ADTS 3.1.1.5.5.1.2 Deleted-Object Requirements
+                * describes what must be removed from a deleted
+                * object
+                */
+
+               ret = ldb_msg_add_empty(msg, "objectCategory", LDB_FLAG_MOD_REPLACE, NULL);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(tmp_ctx);
+                       ldb_module_oom(module);
+                       return ret;
+               }
+
+               ret = ldb_msg_add_empty(msg, "sAMAccountType", LDB_FLAG_MOD_REPLACE, NULL);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(tmp_ctx);
+                       ldb_module_oom(module);
+                       return ret;
+               }
+
                break;
 
        default:
@@ -3111,6 +3417,11 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                }
        }
 
+       /*
+        * TODO: Per MS-DRSR 5.160 RemoveObj we should remove links directly, not as an originating update!
+        *
+        */
+
        ret = dsdb_module_modify(module, msg, DSDB_FLAG_OWN_MODULE, req);
        if (ret != LDB_SUCCESS) {
                ldb_asprintf_errstring(ldb, "replmd_delete: Failed to modify object %s in delete - %s",
@@ -3119,7 +3430,11 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                return ret;
        }
 
-       if (deletion_state == OBJECT_NOT_DELETED) {
+       /*
+        * No matter what has happned with other renames, try again to
+        * get this to be under the deleted DN.
+        */
+       if (strcmp(ldb_dn_get_linearized(old_dn), ldb_dn_get_linearized(new_dn)) != 0) {
                /* now rename onto the new DN */
                ret = dsdb_module_rename(module, old_dn, new_dn, DSDB_FLAG_NEXT_MODULE, req);
                if (ret != LDB_SUCCESS){
@@ -3137,6 +3452,10 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
        return ldb_module_done(req, NULL, NULL, LDB_SUCCESS);
 }
 
+static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
+{
+       return replmd_delete_internals(module, req, false);
+}
 
 
 static int replmd_replicated_request_error(struct replmd_replicated_request *ar, int ret)
@@ -3311,11 +3630,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) {
@@ -3323,22 +3645,61 @@ 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);
 }
 
 /*
-  callback for replmd_replicated_apply_add()
+  callback for replmd_replicated_apply_add() and replmd_replicated_handle_rename()
   This copes with the creation of conflict records in the case where
   the DN exists, but with a different objectGUID
  */
-static int replmd_op_add_callback(struct ldb_request *req, struct ldb_reply *ares)
+static int replmd_op_possible_conflict_callback(struct ldb_request *req, struct ldb_reply *ares, int (*callback)(struct ldb_request *req, struct ldb_reply *ares))
 {
        struct ldb_dn *conflict_dn;
        struct replmd_replicated_request *ar =
@@ -3346,27 +3707,43 @@ static int replmd_op_add_callback(struct ldb_request *req, struct ldb_reply *are
        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 replmd_op_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
         */
-       conflict_dn = req->op.add.message->dn;
+
+       msg = ar->objs->objects[ar->index_current].msg;
+
+       switch (req->operation) {
+       case LDB_ADD:
+               conflict_dn = msg->dn;
+               break;
+       case LDB_RENAME:
+               conflict_dn = req->op.rename.newdn;
+               break;
+       default:
+               return ldb_module_done(req, ares->controls, ares->response, ldb_module_operr(ar->module));
+       }
 
        if (rodc) {
                /*
@@ -3382,12 +3759,6 @@ static int replmd_op_add_callback(struct ldb_request *req, struct ldb_reply *are
                goto failed;
        }
 
-       /*
-        * we have a conflict, and need to decide if we will keep the
-        * new record or the old record
-        */
-       conflict_dn = req->op.add.message->dn;
-
        /*
         * first we need the replPropertyMetaData attribute from the
         * old record
@@ -3418,28 +3789,11 @@ static int replmd_op_add_callback(struct ldb_request *req, struct ldb_reply *are
                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",
@@ -3447,14 +3801,31 @@ static int replmd_op_add_callback(struct ldb_request *req, struct ldb_reply *are
                goto failed;
        }
 
-       rename_incoming_record = !replmd_replPropertyMetaData1_is_newer(omd_name, rmd_name);
+       rename_incoming_record = !(ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING) &&
+               !replmd_replPropertyMetaData1_is_newer(omd_name, rmd_name);
 
        if (rename_incoming_record) {
                struct GUID guid;
                struct ldb_dn *new_dn;
-               struct ldb_message *new_msg;
 
-               guid = samdb_result_guid(req->op.add.message, "objectGUID");
+               /*
+                * 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) {
+                       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(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)));
@@ -3467,19 +3838,12 @@ static int replmd_op_add_callback(struct ldb_request *req, struct ldb_reply *are
                        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)));
 
                /* 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;
+               msg->dn = new_dn;
                req->callback = replmd_op_name_modify_callback;
 
                return ldb_next_request(ar->module, req);
@@ -3502,7 +3866,7 @@ static int replmd_op_add_callback(struct ldb_request *req, struct ldb_reply *are
                        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,
@@ -3524,8 +3888,6 @@ static int replmd_op_add_callback(struct ldb_request *req, struct ldb_reply *are
                        goto failed;
                }
 
-               req->callback = replmd_op_callback;
-
                return ldb_next_request(ar->module, req);
        }
 
@@ -3534,7 +3896,36 @@ failed:
         * will stop with an error, but there is not much else we can
         * do
         */
-       return replmd_op_callback(req, ares);
+       return ldb_module_done(req, ares->controls, ares->response, ares->error);
+}
+
+/*
+  callback for replmd_replicated_apply_add()
+  This copes with the creation of conflict records in the case where
+  the DN exists, but with a different objectGUID
+ */
+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);
+}
+
+/*
+  callback for replmd_replicated_handle_rename()
+  This copes with the creation of conflict records in the case where
+  the DN exists, but with a different objectGUID
+ */
+static int replmd_op_rename_callback(struct ldb_request *req, struct ldb_reply *ares)
+{
+       return replmd_op_possible_conflict_callback(req, ares, ldb_modify_default_callback);
 }
 
 /*
@@ -3550,15 +3941,9 @@ 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;
+       const struct dsdb_attribute *rdn_sa;
+       const char *rdn_name;
 
        ldb = ldb_module_get_ctx(ar->module);
        msg = ar->objs->objects[ar->index_current].msg;
@@ -3594,6 +3979,13 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
                struct ldb_message_element *el = &msg->elements[i];
 
                if (el->num_values == 0) {
+                       if (ldb_attr_cmp(msg->elements[i].name, "objectClass") == 0) {
+                               ldb_asprintf_errstring(ldb, __location__
+                                                      ": empty objectClass sent on %s, aborting replication\n",
+                                                      ldb_dn_get_linearized(msg->dn));
+                               return replmd_replicated_request_error(ar, LDB_ERR_OBJECT_CLASS_VIOLATION);
+                       }
+
                        DEBUG(4,(__location__ ": Removing attribute %s with num_values==0\n",
                                 el->name));
                        memmove(el, el+1, sizeof(*el)*(msg->num_elements - (i+1)));
@@ -3603,9 +3995,38 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
                }
        }
 
+       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));
+               talloc_free(s);
+       }
+
+       remote_isDeleted = ldb_msg_find_attr_as_bool(msg,
+                                                    "isDeleted", false);
+
        /*
         * the meta data array is already sorted by the caller
         */
+
+       rdn_name = ldb_dn_get_rdn_name(msg->dn);
+       if (rdn_name == NULL) {
+               ldb_asprintf_errstring(ldb, __location__ ": No rDN for %s?\n", ldb_dn_get_linearized(msg->dn));
+               return replmd_replicated_request_error(ar, LDB_ERR_INVALID_DN_SYNTAX);
+       }
+
+       rdn_sa = dsdb_attribute_by_lDAPDisplayName(ar->schema, rdn_name);
+       if (rdn_sa == NULL) {
+               ldb_asprintf_errstring(ldb, ": No schema attribute found for rDN %s for %s\n",
+                                      rdn_name, ldb_dn_get_linearized(msg->dn));
+               return replmd_replicated_request_error(ar, LDB_ERR_UNDEFINED_ATTRIBUTE_TYPE);
+       }
+
+       ret = replmd_replPropertyMetaDataCtr1_verify(ldb, &md->ctr.ctr1, rdn_sa, msg->dn);
+       if (ret != LDB_SUCCESS) {
+               ldb_asprintf_errstring(ldb, "%s: error during DRS repl ADD: %s", __func__, ldb_errstring(ldb));
+               return replmd_replicated_request_error(ar, ret);
+       }
+
        for (i=0; i < md->ctr.ctr1.count; i++) {
                md->ctr.ctr1.array[i].local_usn = ar->seq_num;
        }
@@ -3622,12 +4043,17 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
 
        replmd_ldb_message_sort(msg, ar->schema);
 
-       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));
-               talloc_free(s);
+       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);
+               }
        }
 
+       ar->isDeleted = remote_isDeleted;
+
        ret = ldb_build_add_req(&change_req,
                                ldb,
                                ar,
@@ -3657,49 +4083,224 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
        return ldb_next_request(ar->module, change_req);
 }
 
+static int replmd_replicated_apply_search_for_parent_callback(struct ldb_request *req,
+                                                             struct ldb_reply *ares)
+{
+       struct replmd_replicated_request *ar = talloc_get_type(req->context,
+                                              struct replmd_replicated_request);
+       int ret;
+
+       if (!ares) {
+               return ldb_module_done(ar->req, NULL, NULL,
+                                       LDB_ERR_OPERATIONS_ERROR);
+       }
+       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);
+       }
+
+       switch (ares->type) {
+       case LDB_REPLY_ENTRY:
+       {
+               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;
+
+               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
+                        * FindBestParentObject we need to move this
+                        * new object under a deleted object to
+                        * lost-and-found */
+                       struct ldb_dn *nc_root;
+
+                       ret = dsdb_find_nc_root(ldb_module_get_ctx(ar->module), msg, msg->dn, &nc_root);
+                       if (ret == LDB_ERR_NO_SUCH_OBJECT) {
+                               ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+                                                      "No suitable NC root found for %s.  "
+                                                      "We need to move this object because parent object %s "
+                                                      "is deleted, but this object is not.",
+                                                      ldb_dn_get_linearized(msg->dn),
+                                                      ldb_dn_get_linearized(parent_msg->dn));
+                               return ldb_module_done(ar->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+                       } else if (ret != LDB_SUCCESS) {
+                               ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+                                                      "Unable to find NC root for %s: %s. "
+                                                      "We need to move this object because parent object %s "
+                                                      "is deleted, but this object is not.",
+                                                      ldb_dn_get_linearized(msg->dn),
+                                                      ldb_errstring(ldb_module_get_ctx(ar->module)),
+                                                      ldb_dn_get_linearized(parent_msg->dn));
+                               return ldb_module_done(ar->req, NULL, NULL, LDB_ERR_OPERATIONS_ERROR);
+                       }
+                       
+                       ret = dsdb_wellknown_dn(ldb_module_get_ctx(ar->module), msg,
+                                               nc_root,
+                                               DS_GUID_LOSTANDFOUND_CONTAINER,
+                                               &parent_dn);
+                       if (ret != LDB_SUCCESS) {
+                               ldb_asprintf_errstring(ldb_module_get_ctx(ar->module),
+                                                      "Unable to find LostAndFound Container for %s "
+                                                      "in partition %s: %s. "
+                                                      "We need to move this object because parent object %s "
+                                                      "is deleted, but this object is not.",
+                                                      ldb_dn_get_linearized(msg->dn), ldb_dn_get_linearized(nc_root),
+                                                      ldb_errstring(ldb_module_get_ctx(ar->module)),
+                                                      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));
+               }
+               break;
+       }
+       case LDB_REPLY_REFERRAL:
+               /* we ignore referrals */
+               break;
+
+       case LDB_REPLY_DONE:
+               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);
+               }
+       }
+
+       talloc_free(ares);
+       return LDB_SUCCESS;
+}
+
+/*
+ * Look for the parent object, so we put the new object in the right
+ * place This is akin to NameObject in MS-DRSR - this routine and the
+ * callbacks find the right parent name, and correct name for this
+ * object
+ */
+
+static int replmd_replicated_apply_search_for_parent(struct replmd_replicated_request *ar)
+{
+       struct ldb_context *ldb;
+       int ret;
+       char *tmp_str;
+       char *filter;
+       struct ldb_request *search_req;
+       static const char *attrs[] = {"isDeleted", NULL};
+
+       ldb = ldb_module_get_ctx(ar->module);
+
+       if (!ar->objs->objects[ar->index_current].parent_guid_value.data) {
+               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);
+       if (!tmp_str) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+
+       filter = talloc_asprintf(ar, "(objectGUID=%s)", tmp_str);
+       if (!filter) return replmd_replicated_request_werror(ar, WERR_NOMEM);
+       talloc_free(tmp_str);
+
+       ret = ldb_build_search_req(&search_req,
+                                  ldb,
+                                  ar,
+                                  ar->objs->partition_dn,
+                                  LDB_SCOPE_SUBTREE,
+                                  filter,
+                                  attrs,
+                                  NULL,
+                                  ar,
+                                  replmd_replicated_apply_search_for_parent_callback,
+                                  ar->req);
+       LDB_REQ_SET_LOCATION(search_req);
+
+       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;
+       }
+
+       return ldb_next_request(ar->module, search_req);
+}
+
 /*
   handle renames that come in over DRS replication
  */
 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;
-       }
+       struct ldb_request *req;
+       int ret;
+       TALLOC_CTX *tmp_ctx = talloc_new(msg);
+       struct ldb_result *res;
 
-       /* 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 (!md_remote || !md_local ||
-           replmd_replPropertyMetaData1_is_newer(md_local, md_remote)) {
-               DEBUG(4,("replmd_replicated_request rename %s => %s\n",
-                        ldb_dn_get_linearized(ar->search_msg->dn),
-                        ldb_dn_get_linearized(msg->dn)));
-               /* pass rename to the next module
-                * so it doesn't appear as an originating update */
-               return dsdb_module_rename(ar->module,
-                                         ar->search_msg->dn, msg->dn,
-                                         DSDB_FLAG_NEXT_MODULE | DSDB_MODIFY_RELAX, parent);
-       }
-
-       /* we're going to keep our old object */
-       DEBUG(4,(__location__ ": Keeping object %s and rejecting older rename to %s\n",
+       DEBUG(4,("replmd_replicated_request rename %s => %s\n",
                 ldb_dn_get_linearized(ar->search_msg->dn),
                 ldb_dn_get_linearized(msg->dn)));
-       return LDB_SUCCESS;
+
+
+       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;
+       }
+
+       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);
+
+       if (ret == LDB_SUCCESS) {
+               ret = ldb_wait(req->handle, LDB_WAIT_ALL);
+       }
+
+       talloc_free(tmp_ctx);
+       return ret;
 }
 
 
@@ -3718,9 +4319,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;
@@ -3740,9 +4349,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),
@@ -3778,11 +4445,19 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                        }
 
                        if (ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PRIORITISE_INCOMING) {
-                               /* if we compare equal then do an
-                                  update. This is used when a client
-                                  asks for a FULL_SYNC, and can be
-                                  used to recover a corrupt
-                                  replica */
+                               /*
+                                * if we compare equal then do an
+                                * update. This is used when a client
+                                * asks for a FULL_SYNC, and can be
+                                * used to recover a corrupt
+                                * replica.
+                                *
+                                * This call is a bit tricky, what we
+                                * are doing it turning the 'is_newer'
+                                * call into a 'not is older' by
+                                * swapping i and j, and negating the
+                                * outcome.
+                               */
                                cmp = !replmd_replPropertyMetaData1_is_newer(&rmd->ctr.ctr1.array[i],
                                                                             &nmd.ctr.ctr1.array[j]);
                        } else {
@@ -3799,6 +4474,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;
                        }
@@ -3828,6 +4513,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++;
        }
 
@@ -3843,11 +4538,26 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
         *
         * sort the new meta data array
         */
-       ret = replmd_replPropertyMetaDataCtr1_sort(&nmd.ctr.ctr1, ar->schema, msg->dn);
+       ret = replmd_replPropertyMetaDataCtr1_sort_and_verify(ldb, &nmd.ctr.ctr1, ar->schema, msg->dn);
        if (ret != LDB_SUCCESS) {
+               ldb_asprintf_errstring(ldb, "%s: error during DRS repl merge: %s", __func__, ldb_errstring(ldb));
                return ret;
        }
 
+       /*
+        * Work out if this object is deleted, so we can prune any extra attributes.  See MS-DRSR 4.1.10.6.9
+        * UpdateObject.
+        *
+        * This also controls SD propagation below
+        */
+       if (take_remote_isDeleted) {
+               isDeleted = remote_isDeleted;
+       } else {
+               isDeleted = local_isDeleted;
+       }
+
+       ar->isDeleted = isDeleted;
+
        /*
         * check if some replicated attributes left, otherwise skip the ldb_modify() call
         */
@@ -3855,13 +4565,25 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_replicated_apply_merge[%u]: skip replace\n",
                          ar->index_current);
 
-               ar->index_current++;
-               return replmd_replicated_apply_next(ar);
+               return replmd_replicated_apply_isDeleted(ar);
        }
 
        ldb_debug(ldb, LDB_DEBUG_TRACE, "replmd_replicated_apply_merge[%u]: replace %u attributes\n",
                  ar->index_current, msg->num_elements);
 
+       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);
@@ -3892,6 +4614,14 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
        /* we want to replace the old values */
        for (i=0; i < msg->num_elements; i++) {
                msg->elements[i].flags = LDB_FLAG_MOD_REPLACE;
+               if (ldb_attr_cmp(msg->elements[i].name, "objectClass") == 0) {
+                       if (msg->elements[i].num_values == 0) {
+                               ldb_asprintf_errstring(ldb, __location__
+                                                      ": objectClass removed on %s, aborting replication\n",
+                                                      ldb_dn_get_linearized(msg->dn));
+                               return replmd_replicated_request_error(ar, LDB_ERR_OBJECT_CLASS_VIOLATION);
+                       }
+               }
        }
 
        if (DEBUGLVL(4)) {
@@ -3906,7 +4636,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);
@@ -3947,15 +4677,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);
+       {
+               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 {
-                       ret = replmd_replicated_apply_add(ar);
+                       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;
@@ -3970,7 +4776,6 @@ static int replmd_replicated_apply_next(struct replmd_replicated_request *ar)
        char *tmp_str;
        char *filter;
        struct ldb_request *search_req;
-       struct ldb_search_options_control *options;
 
        if (ar->index_current >= ar->objs->num_objects) {
                /* done with it, go to next stage */
@@ -3979,6 +4784,7 @@ static int replmd_replicated_apply_next(struct replmd_replicated_request *ar)
 
        ldb = ldb_module_get_ctx(ar->module);
        ar->search_msg = NULL;
+       ar->isDeleted = false;
 
        tmp_str = ldb_binary_encode(ar, ar->objs->objects[ar->index_current].guid_value);
        if (!tmp_str) return replmd_replicated_request_werror(ar, WERR_NOMEM);
@@ -4000,29 +4806,90 @@ static int replmd_replicated_apply_next(struct replmd_replicated_request *ar)
                                   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_SEARCH_ALL_PARTITIONS|DSDB_SEARCH_SHOW_RECYCLED);
+
        if (ret != LDB_SUCCESS) {
                return ret;
        }
 
-       /* we need to cope with cross-partition links, so search for
-          the GUID over all partitions */
-       options = talloc(search_req, struct ldb_search_options_control);
-       if (options == NULL) {
-               DEBUG(0, (__location__ ": out of memory\n"));
-               return LDB_ERR_OPERATIONS_ERROR;
-       }
-       options->search_options = LDB_SEARCH_OPTION_PHANTOM_ROOT;
+       return ldb_next_request(ar->module, search_req);
+}
 
-       ret = ldb_request_add_control(search_req,
-                                     LDB_CONTROL_SEARCH_OPTIONS_OID,
-                                     true, options);
-       if (ret != LDB_SUCCESS) {
-               return ret;
+/*
+ * This is essentially a wrapper for replmd_replicated_apply_next()
+ *
+ * This is needed to ensure that both codepaths call this handler.
+ */
+static int replmd_replicated_apply_isDeleted(struct replmd_replicated_request *ar)
+{
+       struct ldb_dn *deleted_objects_dn;
+       struct ldb_message *msg = ar->objs->objects[ar->index_current].msg;
+       int ret = dsdb_get_deleted_objects_dn(ldb_module_get_ctx(ar->module), msg, msg->dn,
+                                             &deleted_objects_dn);
+       if (ar->isDeleted && (ret != LDB_SUCCESS || ldb_dn_compare(msg->dn, deleted_objects_dn) != 0)) {
+               /*
+                * Do a delete here again, so that if there is
+                * anything local that conflicts with this
+                * object being deleted, it is removed.  This
+                * includes links.  See MS-DRSR 4.1.10.6.9
+                * UpdateObject.
+                *
+                * If the object is already deleted, and there
+                * is no more work required, it doesn't do
+                * anything.
+                */
+
+               /* This has been updated to point to the DN we eventually did the modify on */
+
+               struct ldb_request *del_req;
+               struct ldb_result *res;
+
+               TALLOC_CTX *tmp_ctx = talloc_new(ar);
+               if (!tmp_ctx) {
+                       ret = ldb_oom(ldb_module_get_ctx(ar->module));
+                       return ret;
+               }
+
+               res = talloc_zero(tmp_ctx, struct ldb_result);
+               if (!res) {
+                       ret = ldb_oom(ldb_module_get_ctx(ar->module));
+                       talloc_free(tmp_ctx);
+                       return ret;
+               }
+
+               /* Build a delete request, which hopefully will artually turn into nothing */
+               ret = ldb_build_del_req(&del_req, ldb_module_get_ctx(ar->module), tmp_ctx,
+                                       msg->dn,
+                                       NULL,
+                                       res,
+                                       ldb_modify_default_callback,
+                                       ar->req);
+               LDB_REQ_SET_LOCATION(del_req);
+               if (ret != LDB_SUCCESS) {
+                       talloc_free(tmp_ctx);
+                       return ret;
+               }
+
+               /*
+                * This is the guts of the call, call back
+                * into our delete code, but setting the
+                * re_delete flag so we delete anything that
+                * shouldn't be there on a deleted or recycled
+                * object
+                */
+               ret = replmd_delete_internals(ar->module, del_req, true);
+               if (ret == LDB_SUCCESS) {
+                       ret = ldb_wait(del_req->handle, LDB_WAIT_ALL);
+               }
+
+               talloc_free(tmp_ctx);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
        }
 
-       return ldb_next_request(ar->module, search_req);
+       ar->index_current++;
+       return replmd_replicated_apply_next(ar);
 }
 
 static int replmd_replicated_uptodate_modify_callback(struct ldb_request *req,
@@ -4126,7 +4993,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,
@@ -4141,6 +5008,10 @@ static int replmd_replicated_uptodate_modify(struct replmd_replicated_request *a
 
        /* get our invocation_id if we have one already attached to the ldb */
        our_invocation_id = samdb_ntds_invocation_id(ldb);
+       if (our_invocation_id == NULL) {
+               DEBUG(0, ("repl_meta_data: Could not find our own server's invocationID!\n"));
+               return replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);                
+       }
 
        /* merge in the source_dsa vector is available */
        for (i=0; (ruv && i < ruv->count); i++) {
@@ -4160,12 +5031,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;
                }
@@ -4177,43 +5044,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
         */
@@ -4249,7 +5079,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
@@ -4539,6 +5371,7 @@ static int replmd_process_linked_attribute(struct ldb_module *module,
        struct drsuapi_DsReplicaLinkedAttribute *la = la_entry->la;
        struct ldb_context *ldb = ldb_module_get_ctx(module);
        struct ldb_message *msg;
+       struct ldb_message *target_msg = NULL;
        TALLOC_CTX *tmp_ctx = talloc_new(la_entry);
        const struct dsdb_schema *schema = dsdb_get_schema(ldb, tmp_ctx);
        int ret;
@@ -4549,13 +5382,18 @@ static int replmd_process_linked_attribute(struct ldb_module *module,
        WERROR status;
        time_t t = time(NULL);
        struct ldb_result *res;
-       const char *attrs[2];
+       struct ldb_result *target_res;
+       const char *attrs[4];
+       const char *attrs2[] = { "isDeleted", "isRecycled", NULL };
        struct parsed_dn *pdn_list, *pdn;
        struct GUID guid = GUID_zero();
        NTSTATUS ntstatus;
        bool active = (la->flags & DRSUAPI_DS_LINKED_ATTRIBUTE_FLAG_ACTIVE)?true:false;
        const struct GUID *our_invocation_id;
 
+       enum deletion_state deletion_state = OBJECT_NOT_DELETED;
+       enum deletion_state target_deletion_state = OBJECT_NOT_DELETED;
+
 /*
 linked_attributes[0]:
      &objs->linked_attributes[i]: struct drsuapi_DsReplicaLinkedAttribute
@@ -4600,7 +5438,9 @@ linked_attributes[0]:
        }
 
        attrs[0] = attr->lDAPDisplayName;
-       attrs[1] = NULL;
+       attrs[1] = "isDeleted";
+       attrs[2] = "isRecycled";
+       attrs[3] = NULL;
 
        /* get the existing message from the db for the object with
           this GUID, returning attribute being modified. We will then
@@ -4625,7 +5465,23 @@ linked_attributes[0]:
        }
        msg = res->msgs[0];
 
-       if (msg->num_elements == 0) {
+       /*
+        * Check for deleted objects per MS-DRSR 4.1.10.6.13
+        * ProcessLinkValue, because link updates are not applied to
+        * recycled and tombstone objects.  We don't have to delete
+        * any existing link, that should have happened when the
+        * object deletion was replicated or initiated.
+        */
+
+       replmd_deletion_state(module, msg, &deletion_state, NULL);
+
+       if (deletion_state >= OBJECT_RECYCLED) {
+               talloc_free(tmp_ctx);
+               return LDB_SUCCESS;
+       }
+
+       old_el = ldb_msg_find_element(msg, attr->lDAPDisplayName);
+       if (old_el == NULL) {
                ret = ldb_msg_add_empty(msg, attr->lDAPDisplayName, LDB_FLAG_MOD_REPLACE, &old_el);
                if (ret != LDB_SUCCESS) {
                        ldb_module_oom(module);
@@ -4633,7 +5489,6 @@ linked_attributes[0]:
                        return LDB_ERR_OPERATIONS_ERROR;
                }
        } else {
-               old_el = &msg->elements[0];
                old_el->flags = LDB_FLAG_MOD_REPLACE;
        }
 
@@ -4662,29 +5517,96 @@ linked_attributes[0]:
        if (!W_ERROR_IS_OK(status)) {
                ldb_asprintf_errstring(ldb, "Failed to parsed linked attribute blob for %s on %s - %s\n",
                                       old_el->name, ldb_dn_get_linearized(msg->dn), win_errstr(status));
+               talloc_free(tmp_ctx);
                return LDB_ERR_OPERATIONS_ERROR;
        }
 
        ntstatus = dsdb_get_extended_dn_guid(dsdb_dn->dn, &guid, "GUID");
-       if (!NT_STATUS_IS_OK(ntstatus) && active) {
+       if (!NT_STATUS_IS_OK(ntstatus) && !active) {
+               /*
+                * This strange behaviour (allowing a NULL/missing
+                * GUID) originally comes from:
+                *
+                * commit e3054ce0fe0f8f62d2f5b2a77893e7a1479128bd
+                * Author: Andrew Tridgell <tridge@samba.org>
+                * Date:   Mon Dec 21 21:21:55 2009 +1100
+                *
+                *  s4-drs: cope better with NULL GUIDS from DRS
+                *
+                *  It is valid to get a NULL GUID over DRS for a deleted forward link. We
+                *  need to match by DN if possible when seeing if we should update an
+                *  existing link.
+                *
+                *  Pair-Programmed-With: Andrew Bartlett <abartlet@samba.org>
+                */
+
+               ret = dsdb_module_search_dn(module, tmp_ctx, &target_res,
+                                           dsdb_dn->dn, attrs2,
+                                           DSDB_FLAG_NEXT_MODULE |
+                                           DSDB_SEARCH_SHOW_RECYCLED |
+                                           DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
+                                           DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+                                           parent);
+       } else if (!NT_STATUS_IS_OK(ntstatus)) {
                ldb_asprintf_errstring(ldb, "Failed to find GUID in linked attribute blob for %s on %s from %s",
                                       old_el->name,
                                       ldb_dn_get_linearized(dsdb_dn->dn),
                                       ldb_dn_get_linearized(msg->dn));
+               talloc_free(tmp_ctx);
                return LDB_ERR_OPERATIONS_ERROR;
+       } else {
+               ret = dsdb_module_search(module, tmp_ctx, &target_res,
+                                        NULL, LDB_SCOPE_SUBTREE,
+                                        attrs2,
+                                        DSDB_FLAG_NEXT_MODULE |
+                                        DSDB_SEARCH_SHOW_RECYCLED |
+                                        DSDB_SEARCH_SEARCH_ALL_PARTITIONS |
+                                        DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT,
+                                        parent,
+                                        "objectGUID=%s",
+                                        GUID_string(tmp_ctx, &guid));
        }
 
-       /* re-resolve the DN by GUID, as the DRS server may give us an
-          old DN value */
-       ret = dsdb_module_dn_by_guid(module, dsdb_dn, &guid, &dsdb_dn->dn, parent);
        if (ret != LDB_SUCCESS) {
+               ldb_asprintf_errstring(ldb_module_get_ctx(module), "Failed to re-resolve GUID %s: %s\n",
+                                      GUID_string(tmp_ctx, &guid),
+                                      ldb_errstring(ldb_module_get_ctx(module)));
+               talloc_free(tmp_ctx);
+               return ret;
+       }
+
+       if (target_res->count == 0) {
                DEBUG(2,(__location__ ": WARNING: Failed to re-resolve GUID %s - using %s\n",
                         GUID_string(tmp_ctx, &guid),
                         ldb_dn_get_linearized(dsdb_dn->dn)));
+       } else if (target_res->count != 1) {
+               ldb_asprintf_errstring(ldb_module_get_ctx(module), "More than one object found matching objectGUID %s\n",
+                                      GUID_string(tmp_ctx, &guid));
+               talloc_free(tmp_ctx);
+               return LDB_ERR_OPERATIONS_ERROR;
+       } else {
+               target_msg = target_res->msgs[0];
+               dsdb_dn->dn = talloc_steal(dsdb_dn, target_msg->dn);
+       }
+
+       /*
+        * Check for deleted objects per MS-DRSR 4.1.10.6.13
+        * ProcessLinkValue, because link updates are not applied to
+        * recycled and tombstone objects.  We don't have to delete
+        * any existing link, that should have happened when the
+        * object deletion was replicated or initiated.
+        */
+       replmd_deletion_state(module, target_msg,
+                             &target_deletion_state, NULL);
+
+       if (target_deletion_state >= OBJECT_RECYCLED) {
+               talloc_free(tmp_ctx);
+               return LDB_SUCCESS;
        }
 
        /* see if this link already exists */
-       pdn = parsed_dn_find(pdn_list, old_el->num_values, &guid, dsdb_dn->dn);
+       pdn = parsed_dn_find(pdn_list, old_el->num_values, &guid, dsdb_dn->dn,
+                            dsdb_dn->extra_part);
        if (pdn != NULL) {
                /* see if this update is newer than what we have already */
                struct GUID invocation_id = GUID_zero();