TODO ... s4-dsdb: While replicating add isRecycled
[metze/samba/wip.git] / source4 / dsdb / samdb / ldb_modules / repl_meta_data.c
index b3126c3e883798a8a56f493815bd2af44a742a7d..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;
@@ -1167,7 +1227,7 @@ static int replmd_update_rpmd(struct ldb_module *module,
        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");
@@ -1335,11 +1391,11 @@ static int replmd_update_rpmd(struct ldb_module *module,
                if (!ldb_request_get_control(req, DSDB_CONTROL_REPLICATED_UPDATE_OID)) {
                        unsigned instanceType;
 
-                       ret = samdb_rodc(ldb, &rodc);
+                       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;
                        }
 
@@ -2249,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;
 
@@ -2278,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) {
@@ -2293,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) {
@@ -2362,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;
                }
        }
@@ -2434,7 +2493,7 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are
        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);
@@ -2549,8 +2608,8 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are
        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;
@@ -2569,9 +2628,7 @@ static int replmd_rename_callback(struct ldb_request *req, struct ldb_reply *are
 
        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) {
@@ -2609,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;
        }
 
@@ -2953,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 (functional_level >= 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"));
@@ -3477,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
  */
@@ -3489,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;
 
        /*
@@ -3529,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];
@@ -3541,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;
+               }
        }
 
        /*
@@ -3658,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;
@@ -3665,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) {
@@ -3691,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;
@@ -3776,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
@@ -4725,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);