TODO ... s4-dsdb: While replicating add isRecycled
[metze/samba/wip.git] / source4 / dsdb / samdb / ldb_modules / repl_meta_data.c
index 54741e6f9dd5a97c93a04ab707df0aa33910b9ac..1e039d9be539dbbbb16e52557c14df2a2aee752b 100644 (file)
@@ -50,6 +50,7 @@
 #include "lib/util/binsearch.h"
 #include "lib/util/tsort.h"
 
+static const uint64_t DELETED_OBJECT_CONTAINER_CHANGE_TIME = 253402127999L;
 struct replmd_private {
        TALLOC_CTX *la_ctx;
        struct la_entry *la_list;
@@ -86,6 +87,10 @@ struct replmd_replicated_request {
 
        uint64_t seq_num;
        bool is_urgent;
+       const struct dsdb_attribute *isRecycled;
+       const struct dsdb_attribute *lastKnownRDN;
+       const struct dsdb_attribute *isDeleted;
+       bool is_recycled_tested;
 };
 
 enum urgent_situation {
@@ -917,7 +922,32 @@ static int replmd_add(struct ldb_module *module, struct ldb_request *req)
 
                m->attid                        = sa->attributeID_id;
                m->version                      = 1;
-               m->originating_change_time      = now;
+               if (m->attid == 0x20030) {
+                       const struct ldb_val *rdn_val = ldb_dn_get_rdn_val(msg->dn);
+                       const char* rdn;
+
+                       if (rdn_val == NULL) {
+                               ldb_oom(ldb);
+                               talloc_free(ac);
+                               return LDB_ERR_OPERATIONS_ERROR;
+                       }
+
+                       rdn = (const char*)rdn_val->data;
+                       if (strcmp(rdn, "Deleted Objects") == 0) {
+                               /*
+                                * Set the originating_change_time to 29/12/9999 at 23:59:59
+                                * as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
+                                */
+                               NTTIME deleted_obj_ts;
+
+                               unix_to_nt_time(&deleted_obj_ts, DELETED_OBJECT_CONTAINER_CHANGE_TIME);
+                               m->originating_change_time      = deleted_obj_ts;
+                       } else {
+                               m->originating_change_time      = now;
+                       }
+               } else {
+                       m->originating_change_time      = now;
+               }
                m->originating_invocation_id    = *our_invocation_id;
                m->originating_usn              = ac->seq_num;
                m->local_usn                    = ac->seq_num;
@@ -1066,9 +1096,15 @@ 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     */
+       /* 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 (old_el != NULL && ldb_msg_element_compare(el, old_el) == 0) {
-               if (!ldb_request_get_control(req, LDB_CONTROL_PROVISION_OID)) {
+               if (strcmp(el->name, "interSiteTopologyGenerator") != 0 &&
+                   !ldb_request_get_control(req, LDB_CONTROL_PROVISION_OID)) {
                        /*
                         * allow this to make it possible for dbcheck
                         * to rebuild broken metadata
@@ -1118,7 +1154,31 @@ static int replmd_update_rpmd_element(struct ldb_context *ldb,
        md1 = &omd->ctr.ctr1.array[i];
        md1->version++;
        md1->attid                     = a->attributeID_id;
-       md1->originating_change_time   = now;
+       if (md1->attid == 0x20030) {
+               const struct ldb_val *rdn_val = ldb_dn_get_rdn_val(msg->dn);
+               const char* rdn;
+
+               if (rdn_val == NULL) {
+                       ldb_oom(ldb);
+                       return LDB_ERR_OPERATIONS_ERROR;
+               }
+
+               rdn = (const char*)rdn_val->data;
+               if (strcmp(rdn, "Deleted Objects") == 0) {
+                       /*
+                        * Set the originating_change_time to 29/12/9999 at 23:59:59
+                        * as specified in MS-ADTS 7.1.1.4.2 Deleted Objects Container
+                        */
+                       NTTIME deleted_obj_ts;
+
+                       unix_to_nt_time(&deleted_obj_ts, DELETED_OBJECT_CONTAINER_CHANGE_TIME);
+                       md1->originating_change_time    = deleted_obj_ts;
+               } else {
+                       md1->originating_change_time    = now;
+               }
+       } else {
+               md1->originating_change_time    = now;
+       }
        md1->originating_invocation_id = *our_invocation_id;
        md1->originating_usn           = *seq_num;
        md1->local_usn                 = *seq_num;
@@ -1151,7 +1211,7 @@ static int replmd_update_rpmd(struct ldb_module *module,
                              const char * const *rename_attrs,
                              struct ldb_message *msg, uint64_t *seq_num,
                              time_t t,
-                             bool *is_urgent)
+                             bool *is_urgent, bool *rodc)
 {
        const struct ldb_val *omd_value;
        enum ndr_err_code ndr_err;
@@ -1162,12 +1222,12 @@ static int replmd_update_rpmd(struct ldb_module *module,
        int ret;
        const char * const *attrs = NULL;
        const char * const attrs1[] = { "replPropertyMetaData", "*", NULL };
-       const char * const attrs2[] = { "uSNChanged", "objectClass", NULL };
+       const char * const attrs2[] = { "uSNChanged", "objectClass", "instanceType", NULL };
        struct ldb_result *res;
        struct ldb_context *ldb;
        struct ldb_message_element *objectclass_el;
        enum urgent_situation situation;
-       bool rodc, rmd_is_provided;
+       bool rmd_is_provided;
 
        if (rename_attrs) {
                attrs = rename_attrs;
@@ -1243,10 +1303,8 @@ static int replmd_update_rpmd(struct ldb_module *module,
                                            DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
                                            DSDB_SEARCH_REVEAL_INTERNALS, req);
 
-               if (ret != LDB_SUCCESS || res->count != 1) {
-                       DEBUG(0,(__location__ ": Object %s failed to find uSNChanged\n",
-                                ldb_dn_get_linearized(msg->dn)));
-                       return LDB_ERR_OPERATIONS_ERROR;
+               if (ret != LDB_SUCCESS) {
+                       return ret;
                }
 
                objectclass_el = ldb_msg_find_element(res->msgs[0], "objectClass");
@@ -1275,10 +1333,8 @@ static int replmd_update_rpmd(struct ldb_module *module,
                                            DSDB_SEARCH_SHOW_EXTENDED_DN |
                                            DSDB_SEARCH_SHOW_DN_IN_STORAGE_FORMAT |
                                            DSDB_SEARCH_REVEAL_INTERNALS, req);
-               if (ret != LDB_SUCCESS || res->count != 1) {
-                       DEBUG(0,(__location__ ": Object %s failed to find replPropertyMetaData\n",
-                                ldb_dn_get_linearized(msg->dn)));
-                       return LDB_ERR_OPERATIONS_ERROR;
+               if (ret != LDB_SUCCESS) {
+                       return ret;
                }
 
                objectclass_el = ldb_msg_find_element(res->msgs[0], "objectClass");
@@ -1333,13 +1389,21 @@ static int replmd_update_rpmd(struct ldb_module *module,
 
                /*if we are RODC and this is a DRSR update then its ok*/
                if (!ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
-                       ret = samdb_rodc(ldb, &rodc);
+                       unsigned instanceType;
+
+                       ret = samdb_rodc(ldb, rodc);
                        if (ret != LDB_SUCCESS) {
                                DEBUG(4, (__location__ ": unable to tell if we are an RODC\n"));
-                       } else if (rodc) {
-                               ldb_asprintf_errstring(ldb, "RODC modify is forbidden\n");
+                       } else if (*rodc) {
+                               ldb_set_errstring(ldb, "RODC modify is forbidden!");
                                return LDB_ERR_REFERRAL;
                        }
+
+                       instanceType = ldb_msg_find_attr_as_uint(res->msgs[0], "instanceType", INSTANCE_TYPE_WRITE);
+                       if (!(instanceType & INSTANCE_TYPE_WRITE)) {
+                               return ldb_error(ldb, LDB_ERR_UNWILLING_TO_PERFORM,
+                                                "cannot change replicated attribute on partial replica");
+                       }
                }
 
                md_value = talloc(msg, struct ldb_val);
@@ -2171,6 +2235,9 @@ static int replmd_modify_handle_linked_attribs(struct ldb_module *module,
                        continue;
                }
                if ((schema_attr->linkID & 1) == 1) {
+                       if (parent && ldb_request_get_control(parent, DSDB_CONTROL_DBCHECK)) {
+                               continue;
+                       }
                        /* Odd is for the target.  Illegal to modify */
                        ldb_asprintf_errstring(ldb,
                                               "attribute %s must not be modified directly, it is a linked attribute", el->name);
@@ -2238,9 +2305,7 @@ static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
        struct ldb_message *msg;
        time_t t = time(NULL);
        int ret;
-       bool is_urgent = false;
-       struct loadparm_context *lp_ctx;
-       char *referral;
+       bool is_urgent = false, rodc = false;
        unsigned int functional_level;
        const DATA_BLOB *guid_blob;
 
@@ -2267,9 +2332,6 @@ static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
 
        functional_level = dsdb_functional_level(ldb);
 
-       lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
-                                struct loadparm_context);
-
        /* we have to copy the message as the caller might have it as a const */
        msg = ldb_msg_copy_shallow(ac, req->op.mod.message);
        if (msg == NULL) {
@@ -2282,15 +2344,21 @@ static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
        ldb_msg_remove_attr(msg, "uSNChanged");
 
        ret = replmd_update_rpmd(module, ac->schema, req, NULL,
-                                msg, &ac->seq_num, t, &is_urgent);
-       if (ret == LDB_ERR_REFERRAL) {
+                                msg, &ac->seq_num, t, &is_urgent, &rodc);
+       if (rodc && (ret == LDB_ERR_REFERRAL)) {
+               struct loadparm_context *lp_ctx;
+               char *referral;
+
+               lp_ctx = talloc_get_type(ldb_get_opaque(ldb, "loadparm"),
+                                        struct loadparm_context);
+
                referral = talloc_asprintf(req,
                                           "ldap://%s/%s",
                                           lpcfg_dnsdomain(lp_ctx),
                                           ldb_dn_get_linearized(msg->dn));
                ret = ldb_module_send_referral(req, referral);
                talloc_free(ac);
-               return ldb_module_done(req, NULL, NULL, ret);
+               return ret;
        }
 
        if (ret != LDB_SUCCESS) {
@@ -2351,12 +2419,14 @@ static int replmd_modify(struct ldb_module *module, struct ldb_request *req)
                ret = add_time_element(msg, "whenChanged", t);
                if (ret != LDB_SUCCESS) {
                        talloc_free(ac);
+                       ldb_operr(ldb);
                        return ret;
                }
 
                ret = add_uint64_element(ldb, msg, "uSNChanged", ac->seq_num);
                if (ret != LDB_SUCCESS) {
                        talloc_free(ac);
+                       ldb_operr(ldb);
                        return ret;
                }
        }
@@ -2420,10 +2490,10 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are
        const struct dsdb_attribute *rdn_attr;
        const char *rdn_name;
        const struct ldb_val *rdn_val;
-       const char *attrs[4] = { NULL, };
+       const char *attrs[5] = { NULL, };
        time_t t = time(NULL);
        int ret;
-       bool is_urgent = false;
+       bool is_urgent = false, rodc = false;
 
        ac = talloc_get_type(req->context, struct replmd_replicated_request);
        ldb = ldb_module_get_ctx(ac->module);
@@ -2533,12 +2603,13 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are
         */
        attrs[0] = "replPropertyMetaData";
        attrs[1] = "objectClass";
-       attrs[2] = rdn_name;
-       attrs[3] = NULL;
+       attrs[2] = "instanceType";
+       attrs[3] = rdn_name;
+       attrs[4] = NULL;
 
        ret = replmd_update_rpmd(ac->module, ac->schema, req, attrs,
-                                msg, &ac->seq_num, t, &is_urgent);
-       if (ret == LDB_ERR_REFERRAL) {
+                                msg, &ac->seq_num, t, &is_urgent, &rodc);
+       if (rodc && (ret == LDB_ERR_REFERRAL)) {
                struct ldb_dn *olddn = ac->req->op.rename.olddn;
                struct loadparm_context *lp_ctx;
                char *referral;
@@ -2551,15 +2622,13 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are
                                           lpcfg_dnsdomain(lp_ctx),
                                           ldb_dn_get_linearized(olddn));
                ret = ldb_module_send_referral(req, referral);
-               talloc_free(ac);
+               talloc_free(ares);
                return ldb_module_done(req, NULL, NULL, ret);
        }
 
        if (ret != LDB_SUCCESS) {
                talloc_free(ares);
-               return ldb_module_done(ac->req, NULL, NULL,
-                                      ldb_error(ldb, ret,
-                                       "failed to call replmd_update_rpmd()"));
+               return ldb_module_done(ac->req, NULL, NULL, ret);
        }
 
        if (ac->seq_num == 0) {
@@ -2597,12 +2666,14 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are
        ret = add_time_element(msg, "whenChanged", t);
        if (ret != LDB_SUCCESS) {
                talloc_free(ac);
+               ldb_operr(ldb);
                return ret;
        }
 
        ret = add_uint64_element(ldb, msg, "uSNChanged", ac->seq_num);
        if (ret != LDB_SUCCESS) {
                talloc_free(ac);
+               ldb_operr(ldb);
                return ret;
        }
 
@@ -2724,6 +2795,7 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                                                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);
@@ -2740,6 +2812,8 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                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
@@ -2919,14 +2993,14 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                }
                msg->elements[el_count++].flags = LDB_FLAG_MOD_ADD;
 
-               ret = ldb_msg_add_empty(msg, "objectCategory", LDB_FLAG_MOD_DELETE, NULL);
+               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_DELETE, NULL);
+               ret = ldb_msg_add_empty(msg, "sAMAccountType", LDB_FLAG_MOD_REPLACE, NULL);
                if (ret != LDB_SUCCESS) {
                        talloc_free(tmp_ctx);
                        ldb_module_oom(module);
@@ -2938,9 +3012,15 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
        case OBJECT_RECYCLED:
        case OBJECT_TOMBSTONE:
 
-               /* we also mark it as recycled, meaning this object can't be
-                  recovered (we are stripping its attributes) */
-               if (dsdb_functional_level(ldb) >= DS_DOMAIN_FUNCTION_2008_R2) {
+               /*
+                * we also mark it as recycled, meaning this object can't be
+                * recovered (we are stripping its attributes).
+                * This is done only if we have this schema object of course ...
+                * This behavior is identical to the one of Windows 2008R2 which
+                * always set the isRecycled attribute, even if the recycle-bin is
+                * not activated and what ever the forest level is.
+                */
+               if (dsdb_attribute_by_lDAPDisplayName(schema, "isRecycled") != NULL) {
                        ret = ldb_msg_add_string(msg, "isRecycled", "TRUE");
                        if (ret != LDB_SUCCESS) {
                                DEBUG(0,(__location__ ": Failed to add isRecycled string to the msg\n"));
@@ -2964,12 +3044,24 @@ static int replmd_delete(struct ldb_module *module, struct ldb_request *req)
                                /* don't remove the rDN */
                                continue;
                        }
-                       if (sa->linkID && sa->linkID & 1) {
+                       if (sa->linkID && (sa->linkID & 1)) {
+                               /*
+                                 we have a backlink in this object
+                                 that needs to be removed. We're not
+                                 allowed to remove it directly
+                                 however, so we instead setup a
+                                 modify to delete the corresponding
+                                 forward link
+                                */
                                ret = replmd_delete_remove_link(module, schema, old_dn, el, sa, req);
                                if (ret != LDB_SUCCESS) {
                                        talloc_free(tmp_ctx);
                                        return LDB_ERR_OPERATIONS_ERROR;
                                }
+                               /* now we continue, which means we
+                                  won't remove this backlink
+                                  directly
+                               */
                                continue;
                        }
                        if (!sa->linkID && ldb_attr_in_list(preserved_attrs, el->name)) {
@@ -3262,7 +3354,7 @@ static int replmd_op_add_callback(struct ldb_request *req, struct ldb_reply *are
        const struct ldb_val *rmd_value, *omd_value;
        struct replPropertyMetaDataBlob omd, rmd;
        enum ndr_err_code ndr_err;
-       bool rename_incoming_record;
+       bool rename_incoming_record, rodc;
        struct replPropertyMetaData1 *rmd_name, *omd_name;
 
        if (ares->error != LDB_ERR_ENTRY_ALREADY_EXISTS) {
@@ -3271,7 +3363,31 @@ static int replmd_op_add_callback(struct ldb_request *req, struct ldb_reply *are
                return replmd_op_callback(req, ares);
        }
 
-       /*
+       ret = samdb_rodc(ldb_module_get_ctx(ar->module), &rodc);
+       if (ret != LDB_SUCCESS) {
+               return ret;
+       }
+       /*
+        * 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;
+
+       if (rodc) {
+               /*
+                * We are on an RODC, or were a GC for this
+                * partition, so we have to fail this until
+                * someone who owns the partition sorts it
+                * out 
+                */
+               ldb_asprintf_errstring(ldb_module_get_ctx(ar->module), 
+                                      "Conflict adding object '%s' from incoming replication as we are read only for the partition.  \n"
+                                      " - We must fail the operation until a master for this partition resolves the conflict",
+                                      ldb_dn_get_linearized(conflict_dn));
+               goto failed;
+       }
+
+       /*
         * we have a conflict, and need to decide if we will keep the
         * new record or the old record
         */
@@ -3426,6 +3542,59 @@ failed:
        return replmd_op_callback(req, ares);
 }
 
+static int replmd_add_isrecycled(struct replmd_replicated_request *ar,
+                                       struct ldb_context *ldb,
+                                       struct ldb_message *msg,
+                                       struct replPropertyMetaDataBlob *md) {
+       time_t t = time(NULL);
+       NTTIME now;
+       struct replPropertyMetaData1 *m;
+       const struct GUID *our_invocation_id;
+       const struct ldb_val* v;
+       int ret;
+
+       v = ldb_dn_get_rdn_val(msg->dn);
+       if (!v || strcmp((char*)v->data, "Deleted Objects") == 0) {
+               return LDB_SUCCESS;
+       }
+
+       our_invocation_id = samdb_ntds_invocation_id(ldb);
+       if (!our_invocation_id) {
+               ldb_debug_set(ldb, LDB_DEBUG_ERROR,
+                       "replmd_add: unable to find invocationId\n");
+               return replmd_replicated_request_error(ar, LDB_ERR_OPERATIONS_ERROR);
+       }
+
+       unix_to_nt_time(&now, t);
+
+       ret = ldb_msg_add_string(msg, "isRecycled", "TRUE");
+       if (ret != LDB_SUCCESS) {
+               return replmd_replicated_request_error(ar, ret);
+       }
+
+       md->ctr.ctr1.count++;
+       md->ctr.ctr1.array = talloc_realloc(ar, md->ctr.ctr1.array,
+                                               struct replPropertyMetaData1,
+                                               md->ctr.ctr1.count);
+       if (md->ctr.ctr1.array == NULL) {
+               return replmd_replicated_request_werror(ar, WERR_NOMEM);
+       }
+
+       /* rdn is at the end so shift it to end first */
+       m = &md->ctr.ctr1.array[md->ctr.ctr1.count - 2];
+       md->ctr.ctr1.array[md->ctr.ctr1.count - 1] = *m;
+
+       /* Allocate a new entry in the replPropertyMetadata */
+
+       m->attid                        = ar->isRecycled->attributeID_id;
+       m->version                      = 1;
+       m->originating_change_time      = now;
+       m->originating_invocation_id    = *our_invocation_id;
+       m->originating_usn              = ar->seq_num;
+       m->local_usn                    = ar->seq_num;
+
+       return LDB_SUCCESS;
+}
 /*
   this is called when a new object comes in over DRS
  */
@@ -3438,6 +3607,9 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
        struct replPropertyMetaDataBlob *md;
        struct ldb_val md_value;
        unsigned int i;
+       bool is_deleted = false;
+       bool is_recycled = false;
+       bool has_lastknownrdn = false;
        int ret;
 
        /*
@@ -3478,6 +3650,12 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
                return replmd_replicated_request_error(ar, ret);
        }
 
+       if (!ar->is_recycled_tested) {
+               ar->isRecycled = dsdb_attribute_by_lDAPDisplayName(ar->schema,
+                                                                  "isRecycled");
+               ar->is_recycled_tested = true;
+       }
+
        /* remove any message elements that have zero values */
        for (i=0; i<msg->num_elements; i++) {
                struct ldb_message_element *el = &msg->elements[i];
@@ -3490,6 +3668,51 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
                        i--;
                        continue;
                }
+
+               if (ldb_attr_cmp(el->name, "isDeleted") == 0) {
+                       struct ldb_val *v = &el->values[0];
+
+                       if (strncmp((const char*)v->data, "TRUE", 4) == 0 ) {
+                               DEBUG(11, ("Found isDeleted on %s while doing replmd_replicated_apply_add\n",
+                                       ldb_dn_get_linearized(msg->dn)));
+                               is_deleted = true;
+                       }
+                       continue;
+               }
+
+               if (ldb_attr_cmp(el->name, "isRecycled") == 0) {
+                       struct ldb_val *v = &el->values[0];
+
+                       /*
+                        * Normaly we do not store boolean equals to false, but
+                        * nothing forbids to do so, especially if you undelete
+                        * an object.
+                        */
+                       if (strncmp((const char*)v->data, "TRUE", 4) == 0 ) {
+                               is_recycled = true;
+                       }
+                       continue;
+               }
+
+               if (ldb_attr_cmp(el->name, "msDS-LastKnownRDN") == 0) {
+                       has_lastknownrdn = true;
+                       continue;
+               }
+       }
+
+       if (is_deleted && ar->isRecycled && !is_recycled && !has_lastknownrdn)  {
+               /*
+                * The object is deleted and we have the isRecycled attribute in
+                * the schema, but it is missing on the object the recycle-bin
+                * is not activated and it hasn't the msDS-LastKnownRDN so we
+                * mark the object as deleted because it means that it comes
+                * from a pre windows 2008R2 server or from a Samba DC before
+                * changes related to isRecycled.
+                */
+               ret = replmd_add_isrecycled(ar, ldb, msg, md);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
        }
 
        /*
@@ -3534,6 +3757,15 @@ static int replmd_replicated_apply_add(struct replmd_replicated_request *ar)
                                      false, NULL);
        if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
 
+       if (ar->objs->dsdb_repl_flags & DSDB_REPL_FLAG_PARTIAL_REPLICA) {
+               /* this tells the partition module to make it a
+                  partial replica if creating an NC */
+               ret = ldb_request_add_control(change_req,
+                                             DSDB_CONTROL_PARTIAL_REPLICA,
+                                             false, NULL);
+               if (ret != LDB_SUCCESS) return replmd_replicated_request_error(ar, ret);
+       }
+
        return ldb_next_request(ar->module, change_req);
 }
 
@@ -3598,6 +3830,18 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
        uint32_t j,ni=0;
        unsigned int removed_attrs = 0;
        int ret;
+       bool found_old_is_deleted = false;
+       bool found_old_is_recycled = false;
+       bool found_old_has_lastknownrdn = false;
+       bool old_is_deleted = false;
+       bool old_is_recycled = false;
+       bool old_has_lastknownrdn = false;
+       bool found_new_is_deleted = false;
+       bool found_new_is_recycled = false;
+       bool found_new_has_lastknownrdn = false;
+       bool new_is_deleted = false;
+       bool new_is_recycled = false;
+       bool new_has_lastknownrdn = false;
 
        ldb = ldb_module_get_ctx(ar->module);
        msg = ar->objs->objects[ar->index_current].msg;
@@ -3605,6 +3849,16 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
        ZERO_STRUCT(omd);
        omd.version = 1;
 
+       if (!ar->is_recycled_tested) {
+               ar->isDeleted = dsdb_attribute_by_lDAPDisplayName(ar->schema,
+                                                                  "isDeleted");
+               ar->isRecycled = dsdb_attribute_by_lDAPDisplayName(ar->schema,
+                                                                  "isRecycled");
+               ar->lastKnownRDN = dsdb_attribute_by_lDAPDisplayName(ar->schema,
+                                                                  "msDS-lastKnownRDN");
+               ar->is_recycled_tested = true;
+       }
+
        /* find existing meta data */
        omd_value = ldb_msg_find_ldb_val(ar->search_msg, "replPropertyMetaData");
        if (omd_value) {
@@ -3631,6 +3885,58 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                return replmd_replicated_request_werror(ar, WERR_DS_DRA_DB_ERROR);
        }
 
+       for (i=0; i < ar->search_msg->num_elements; i++) {
+               struct ldb_message_element *el = &ar->search_msg->elements[i];
+
+               if (ar->isDeleted && strcmp(el->name, ar->isDeleted->lDAPDisplayName) == 0) {
+                       struct ldb_val *v = NULL;
+
+                       if (el->num_values == 0) {
+                               continue;
+                       }
+
+                       found_old_is_deleted = true;
+
+                       v = &el->values[0];
+
+                       if (strncmp((const char*)v->data, "TRUE", 4) == 0) {
+                               old_is_deleted = true;
+                       }
+               }
+
+               if (ar->isRecycled && strcmp(el->name, ar->isRecycled->lDAPDisplayName) == 0) {
+                       struct ldb_val *v = NULL;
+
+                       if (el->num_values == 0) {
+                               continue;
+                       }
+
+                       found_old_is_recycled = true;
+
+                       v = &el->values[0];
+
+                       if (strncmp((const char*)v->data, "TRUE", 4) == 0) {
+                               old_is_recycled = true;
+                       }
+               }
+
+               if (ar->lastKnownRDN && strcmp(el->name, ar->lastKnownRDN->lDAPDisplayName) == 0) {
+                       struct ldb_val *v = NULL;
+
+                       if (el->num_values == 0) {
+                               continue;
+                       }
+
+                       found_old_has_lastknownrdn = true;
+
+                       v = &el->values[0];
+
+                       if (strncmp((const char*)v->data, "TRUE", 4) == 0) {
+                               old_has_lastknownrdn = true;
+                       }
+               }
+       }
+
        ZERO_STRUCT(nmd);
        nmd.version = 1;
        nmd.ctr.ctr1.count = omd.ctr.ctr1.count + rmd->ctr.ctr1.count;
@@ -3657,8 +3963,18 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
                                continue;
                        }
 
-                       cmp = replmd_replPropertyMetaData1_is_newer(&nmd.ctr.ctr1.array[j],
-                                                                   &rmd->ctr.ctr1.array[i]);
+                       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 */
+                               cmp = !replmd_replPropertyMetaData1_is_newer(&rmd->ctr.ctr1.array[i],
+                                                                            &nmd.ctr.ctr1.array[j]);
+                       } else {
+                               cmp = replmd_replPropertyMetaData1_is_newer(&nmd.ctr.ctr1.array[j],
+                                                                           &rmd->ctr.ctr1.array[i]);
+                       }
                        if (cmp) {
                                /* replace the entry */
                                nmd.ctr.ctr1.array[j] = rmd->ctr.ctr1.array[i];
@@ -3706,6 +4022,97 @@ static int replmd_replicated_apply_merge(struct replmd_replicated_request *ar)
         */
        nmd.ctr.ctr1.count = ni;
 
+       for (i=0; i < msg->num_elements; i++) {
+               struct ldb_message_element *el = &msg->elements[i];
+
+               if (ar->isDeleted && strcmp(el->name, ar->isDeleted->lDAPDisplayName) == 0) {
+                       struct ldb_val *v = NULL;
+
+                       if (el->num_values == 0) {
+                               continue;
+                       }
+
+                       found_new_is_deleted = true;
+
+                       v = &el->values[0];
+
+                       if (strncmp((const char*)v->data, "TRUE", 4) == 0) {
+                               new_is_deleted = true;
+                       }
+               }
+
+               if (ar->isRecycled && strcmp(el->name, ar->isRecycled->lDAPDisplayName) == 0) {
+                       struct ldb_val *v = NULL;
+
+                       if (el->num_values == 0) {
+                               continue;
+                       }
+
+                       found_new_is_recycled = true;
+
+                       v = &el->values[0];
+
+                       if (strncmp((const char*)v->data, "TRUE", 4) == 0) {
+                               new_is_recycled = true;
+                       }
+               }
+
+               if (ar->lastKnownRDN && strcmp(el->name, ar->lastKnownRDN->lDAPDisplayName) == 0) {
+                       struct ldb_val *v = NULL;
+
+                       if (el->num_values == 0) {
+                               continue;
+                       }
+
+                       found_new_has_lastknownrdn = true;
+
+                       v = &el->values[0];
+
+                       if (strncmp((const char*)v->data, "TRUE", 4) == 0) {
+                               new_has_lastknownrdn = true;
+                       }
+               }
+       }
+
+       is_deleted = false;
+       if (found_old_is_deleted) {
+               is_deleted = old_is_deleted;
+       }
+       if (found_new_is_deleted) {
+               is_deleted = new_is_deleted;
+       }
+
+       is_recycled = false;
+       if (found_old_is_recycled) {
+               is_recycled = old_is_recycled;
+       }
+       if (found_new_is_recycled) {
+               is_recycled = new_is_recycled;
+       }
+
+       has_lastknownrdn = false;
+       if (found_old_has_lastknownrdn) {
+               has_lastknownrdn = old_has_lastknownrdn;
+       }
+       if (found_new_has_lastknownrdn) {
+               has_lastknownrdn = new_has_lastknownrdn;
+       }
+
+       if (is_deleted && ar->isRecycled && !is_recycled && !has_lastknownrdn) {
+               /*
+                * The replicated attributes for the current object has the
+                * isDeleted attribute but not the isRecycled and no the
+                * lastKnownRDN.  It means that we knew the object before and
+                * now we are notified that is has been deleted but it's not a
+                * recycled one.  If we support the isRecycled attribute we had
+                * this attribute.
+                */
+               ret = replmd_add_isrecycled(ar, ldb, msg, &nmd);
+               if (ret != LDB_SUCCESS) {
+                       return ret;
+               }
+       }
+
        /*
         * the rdn attribute (the alias for the name attribute),
         * 'cn' for most objects is the last entry in the meta data array
@@ -3913,7 +4320,7 @@ static int replmd_replicated_uptodate_modify_callback(struct ldb_request *req,
        }
 
        if (ares->type != LDB_REPLY_DONE) {
-               ldb_set_errstring(ldb, "Invalid reply type\n!");
+               ldb_asprintf_errstring(ldb, "Invalid LDB reply type %d", ares->type);
                return ldb_module_done(ar->req, NULL, NULL,
                                        LDB_ERR_OPERATIONS_ERROR);
        }
@@ -3957,6 +4364,15 @@ static int replmd_replicated_uptodate_modify(struct replmd_replicated_request *a
 
        unix_to_nt_time(&now, t);
 
+       if (ar->search_msg == NULL) {
+               /* this happens for a REPL_OBJ call where we are
+                  creating the target object by replicating it. The
+                  subdomain join code does this for the partition DN
+               */
+               DEBUG(4,(__location__ ": Skipping UDV and repsFrom update as no target DN\n"));
+               return ldb_module_done(ar->req, NULL, NULL, LDB_SUCCESS);
+       }
+
        instanceType = ldb_msg_find_attr_as_uint(ar->search_msg, "instanceType", 0);
        if (! (instanceType & INSTANCE_TYPE_IS_NC_HEAD)) {
                DEBUG(4,(__location__ ": Skipping UDV and repsFrom update as not NC root: %s\n",
@@ -4236,11 +4652,7 @@ static int replmd_replicated_uptodate_search_callback(struct ldb_request *req,
                break;
 
        case LDB_REPLY_DONE:
-               if (ar->search_msg == NULL) {
-                       ret = replmd_replicated_request_werror(ar, WERR_DS_DRA_INTERNAL_ERROR);
-               } else {
-                       ret = replmd_replicated_uptodate_modify(ar);
-               }
+               ret = replmd_replicated_uptodate_modify(ar);
                if (ret != LDB_SUCCESS) {
                        return ldb_module_done(ar->req, NULL, NULL, ret);
                }
@@ -4295,6 +4707,7 @@ static int replmd_extended_replicated_objects(struct ldb_module *module, struct
        uint32_t i;
        struct replmd_private *replmd_private =
                talloc_get_type(ldb_module_get_private(module), struct replmd_private);
+       struct dsdb_control_replicated_update *rep_update;
 
        ldb = ldb_module_get_ctx(module);
 
@@ -4335,8 +4748,15 @@ static int replmd_extended_replicated_objects(struct ldb_module *module, struct
                if (!req->controls) return replmd_replicated_request_werror(ar, WERR_NOMEM);
        }
 
-       /* This allows layers further down to know if a change came in over replication */
-       ret = ldb_request_add_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID, false, NULL);
+       /* This allows layers further down to know if a change came in
+          over replication and what the replication flags were */
+       rep_update = talloc_zero(ar, struct dsdb_control_replicated_update);
+       if (rep_update == NULL) {
+               return ldb_module_oom(module);
+       }
+       rep_update->dsdb_repl_flags = objs->dsdb_repl_flags;
+
+       ret = ldb_request_add_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID, false, rep_update);
        if (ret != LDB_SUCCESS) {
                return ret;
        }
@@ -4535,7 +4955,7 @@ linked_attributes[0]:
           old DN value */
        ret = dsdb_module_dn_by_guid(module, dsdb_dn, &guid, &dsdb_dn->dn, parent);
        if (ret != LDB_SUCCESS) {
-               DEBUG(2,(__location__ ": WARNING: Failed to re-resolve GUID %s - using %s",
+               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)));
        }
@@ -4642,15 +5062,18 @@ linked_attributes[0]:
 
        /* we only change whenChanged and uSNChanged if the seq_num
           has changed */
-       if (add_time_element(msg, "whenChanged", t) != LDB_SUCCESS) {
+       ret = add_time_element(msg, "whenChanged", t);
+       if (ret != LDB_SUCCESS) {
                talloc_free(tmp_ctx);
-               return ldb_operr(ldb);
+               ldb_operr(ldb);
+               return ret;
        }
 
-       if (add_uint64_element(ldb, msg, "uSNChanged",
-                              seq_num) != LDB_SUCCESS) {
+       ret = add_uint64_element(ldb, msg, "uSNChanged", seq_num);
+       if (ret != LDB_SUCCESS) {
                talloc_free(tmp_ctx);
-               return ldb_operr(ldb);
+               ldb_operr(ldb);
+               return ret;
        }
 
        old_el = ldb_msg_find_element(msg, attr->lDAPDisplayName);