Blame SOURCES/0020-Ticket-49859-A-distinguished-value-can-be-missing-in.patch

5873fa
From 6cd4b1c60dbd3d7b74adb19a2434585d50553f39 Mon Sep 17 00:00:00 2001
5873fa
From: Thierry Bordaz <tbordaz@redhat.com>
5873fa
Date: Fri, 5 Jun 2020 12:14:51 +0200
5873fa
Subject: [PATCH] Ticket 49859 - A distinguished value can be missing in an
5873fa
 entry
5873fa
5873fa
Bug description:
5873fa
	According to RFC 4511 (see ticket), the values of the RDN attributes
5873fa
        should be present in an entry.
5873fa
	With a set of replicated operations, it is possible that those values
5873fa
        would be missing
5873fa
5873fa
Fix description:
5873fa
        MOD and MODRDN update checks that the RDN values are presents.
5873fa
        If they are missing they are added to the resulting entry. In addition
5873fa
        the set of modifications to add those values are also indexed.
5873fa
        The specific case of single-valued attributes, where the final and unique value
5873fa
        can not be the RDN value, the attribute nsds5ReplConflict is added.
5873fa
5873fa
https://pagure.io/389-ds-base/issue/49859
5873fa
5873fa
Reviewed by: Mark Reynolds, William Brown
5873fa
5873fa
Platforms tested: F31
5873fa
---
5873fa
 .../replication/conflict_resolve_test.py      | 174 +++++++++++++++++-
5873fa
 ldap/servers/slapd/back-ldbm/ldbm_modify.c    | 136 ++++++++++++++
5873fa
 ldap/servers/slapd/back-ldbm/ldbm_modrdn.c    |  37 +++-
5873fa
 .../servers/slapd/back-ldbm/proto-back-ldbm.h |   1 +
5873fa
 4 files changed, 343 insertions(+), 5 deletions(-)
5873fa
5873fa
diff --git a/dirsrvtests/tests/suites/replication/conflict_resolve_test.py b/dirsrvtests/tests/suites/replication/conflict_resolve_test.py
5873fa
index 99a072935..48d0067db 100644
5873fa
--- a/dirsrvtests/tests/suites/replication/conflict_resolve_test.py
5873fa
+++ b/dirsrvtests/tests/suites/replication/conflict_resolve_test.py
5873fa
@@ -10,10 +10,11 @@ import time
5873fa
 import logging
5873fa
 import ldap
5873fa
 import pytest
5873fa
+import re
5873fa
 from itertools import permutations
5873fa
 from lib389._constants import *
5873fa
 from lib389.idm.nscontainer import nsContainers
5873fa
-from lib389.idm.user import UserAccounts
5873fa
+from lib389.idm.user import UserAccounts, UserAccount
5873fa
 from lib389.idm.group import Groups
5873fa
 from lib389.idm.organizationalunit import OrganizationalUnits
5873fa
 from lib389.replica import ReplicationManager
5873fa
@@ -763,6 +764,177 @@ class TestTwoMasters:
5873fa
         user_dns_m2 = [user.dn for user in test_users_m2.list()]
5873fa
         assert set(user_dns_m1) == set(user_dns_m2)
5873fa
 
5873fa
+    def test_conflict_attribute_multi_valued(self, topology_m2, base_m2):
5873fa
+        """A RDN attribute being multi-valued, checks that after several operations
5873fa
+           MODRDN and MOD_REPL its RDN values are the same on both servers
5873fa
+
5873fa
+        :id: 225b3522-8ed7-4256-96f9-5fab9b7044a5
5873fa
+        :setup: Two master replication,
5873fa
+                audit log, error log for replica and access log for internal
5873fa
+        :steps:
5873fa
+            1. Create a test entry uid=user_test_1000,...
5873fa
+            2. Pause all replication agreements
5873fa
+            3. On M1 rename it into uid=foo1,...
5873fa
+            4. On M2 rename it into uid=foo2,...
5873fa
+            5. On M1 MOD_REPL uid:foo1
5873fa
+            6. Resume all replication agreements
5873fa
+            7. Check that entry on M1 has uid=foo1, foo2
5873fa
+            8. Check that entry on M2 has uid=foo1, foo2
5873fa
+            9. Check that entry on M1 and M2 has the same uid values
5873fa
+        :expectedresults:
5873fa
+            1. It should pass
5873fa
+            2. It should pass
5873fa
+            3. It should pass
5873fa
+            4. It should pass
5873fa
+            5. It should pass
5873fa
+            6. It should pass
5873fa
+            7. It should pass
5873fa
+            8. It should pass
5873fa
+            9. It should pass
5873fa
+        """
5873fa
+
5873fa
+        M1 = topology_m2.ms["master1"]
5873fa
+        M2 = topology_m2.ms["master2"]
5873fa
+
5873fa
+        # add a test user
5873fa
+        test_users_m1 = UserAccounts(M1, base_m2.dn, rdn=None)
5873fa
+        user_1 = test_users_m1.create_test_user(uid=1000)
5873fa
+        test_users_m2 = UserAccount(M2, user_1.dn)
5873fa
+        # Waiting fo the user to be replicated
5873fa
+        for i in range(0,4):
5873fa
+            time.sleep(1)
5873fa
+            if test_users_m2.exists():
5873fa
+                break
5873fa
+        assert(test_users_m2.exists())
5873fa
+
5873fa
+        # Stop replication agreements
5873fa
+        topology_m2.pause_all_replicas()
5873fa
+
5873fa
+        # On M1 rename test entry in uid=foo1
5873fa
+        original_dn = user_1.dn
5873fa
+        user_1.rename('uid=foo1')
5873fa
+        time.sleep(1)
5873fa
+
5873fa
+        # On M2 rename test entry in uid=foo2
5873fa
+        M2.rename_s(original_dn, 'uid=foo2')
5873fa
+        time.sleep(2)
5873fa
+
5873fa
+        # on M1 MOD_REPL uid into foo1
5873fa
+        user_1.replace('uid', 'foo1')
5873fa
+
5873fa
+        # resume replication agreements
5873fa
+        topology_m2.resume_all_replicas()
5873fa
+        time.sleep(5)
5873fa
+
5873fa
+        # check that on M1, the entry 'uid' has two values 'foo1' and 'foo2'
5873fa
+        final_dn = re.sub('^.*1000,', 'uid=foo2,', original_dn)
5873fa
+        final_user_m1 = UserAccount(M1, final_dn)
5873fa
+        for val in final_user_m1.get_attr_vals_utf8('uid'):
5873fa
+            log.info("Check %s is on M1" % val)
5873fa
+            assert(val in ['foo1', 'foo2'])
5873fa
+
5873fa
+        # check that on M2, the entry 'uid' has two values 'foo1' and 'foo2'
5873fa
+        final_user_m2 = UserAccount(M2, final_dn)
5873fa
+        for val in final_user_m2.get_attr_vals_utf8('uid'):
5873fa
+            log.info("Check %s is on M1" % val)
5873fa
+            assert(val in ['foo1', 'foo2'])
5873fa
+
5873fa
+        # check that the entry have the same uid values
5873fa
+        for val in final_user_m1.get_attr_vals_utf8('uid'):
5873fa
+            log.info("Check M1.uid %s is also on M2" % val)
5873fa
+            assert(val in final_user_m2.get_attr_vals_utf8('uid'))
5873fa
+
5873fa
+        for val in final_user_m2.get_attr_vals_utf8('uid'):
5873fa
+            log.info("Check M2.uid %s is also on M1" % val)
5873fa
+            assert(val in final_user_m1.get_attr_vals_utf8('uid'))
5873fa
+
5873fa
+    def test_conflict_attribute_single_valued(self, topology_m2, base_m2):
5873fa
+        """A RDN attribute being signle-valued, checks that after several operations
5873fa
+           MODRDN and MOD_REPL its RDN values are the same on both servers
5873fa
+
5873fa
+        :id: c38ae613-5d1e-47cf-b051-c7284e64b817
5873fa
+        :setup: Two master replication, test container for entries, enable plugin logging,
5873fa
+                audit log, error log for replica and access log for internal
5873fa
+        :steps:
5873fa
+            1. Create a test entry uid=user_test_1000,...
5873fa
+            2. Pause all replication agreements
5873fa
+            3. On M1 rename it into employeenumber=foo1,...
5873fa
+            4. On M2 rename it into employeenumber=foo2,...
5873fa
+            5. On M1 MOD_REPL employeenumber:foo1
5873fa
+            6. Resume all replication agreements
5873fa
+            7. Check that entry on M1 has employeenumber=foo1
5873fa
+            8. Check that entry on M2 has employeenumber=foo1
5873fa
+            9. Check that entry on M1 and M2 has the same employeenumber values
5873fa
+        :expectedresults:
5873fa
+            1. It should pass
5873fa
+            2. It should pass
5873fa
+            3. It should pass
5873fa
+            4. It should pass
5873fa
+            5. It should pass
5873fa
+            6. It should pass
5873fa
+            7. It should pass
5873fa
+            8. It should pass
5873fa
+            9. It should pass
5873fa
+        """
5873fa
+
5873fa
+        M1 = topology_m2.ms["master1"]
5873fa
+        M2 = topology_m2.ms["master2"]
5873fa
+
5873fa
+        # add a test user with a dummy 'uid' extra value because modrdn removes
5873fa
+        # uid that conflict with 'account' objectclass
5873fa
+        test_users_m1 = UserAccounts(M1, base_m2.dn, rdn=None)
5873fa
+        user_1 = test_users_m1.create_test_user(uid=1000)
5873fa
+        user_1.add('objectclass', 'extensibleobject')
5873fa
+        user_1.add('uid', 'dummy')
5873fa
+        test_users_m2 = UserAccount(M2, user_1.dn)
5873fa
+
5873fa
+        # Waiting fo the user to be replicated
5873fa
+        for i in range(0,4):
5873fa
+            time.sleep(1)
5873fa
+            if test_users_m2.exists():
5873fa
+                break
5873fa
+        assert(test_users_m2.exists())
5873fa
+
5873fa
+        # Stop replication agreements
5873fa
+        topology_m2.pause_all_replicas()
5873fa
+
5873fa
+        # On M1 rename test entry in employeenumber=foo1
5873fa
+        original_dn = user_1.dn
5873fa
+        user_1.rename('employeenumber=foo1')
5873fa
+        time.sleep(1)
5873fa
+
5873fa
+        # On M2 rename test entry in employeenumber=foo2
5873fa
+        M2.rename_s(original_dn, 'employeenumber=foo2')
5873fa
+        time.sleep(2)
5873fa
+
5873fa
+        # on M1 MOD_REPL uid into foo1
5873fa
+        user_1.replace('employeenumber', 'foo1')
5873fa
+
5873fa
+        # resume replication agreements
5873fa
+        topology_m2.resume_all_replicas()
5873fa
+        time.sleep(5)
5873fa
+
5873fa
+        # check that on M1, the entry 'employeenumber' has value 'foo1'
5873fa
+        final_dn = re.sub('^.*1000,', 'employeenumber=foo2,', original_dn)
5873fa
+        final_user_m1 = UserAccount(M1, final_dn)
5873fa
+        for val in final_user_m1.get_attr_vals_utf8('employeenumber'):
5873fa
+            log.info("Check %s is on M1" % val)
5873fa
+            assert(val in ['foo1'])
5873fa
+
5873fa
+        # check that on M2, the entry 'employeenumber' has values 'foo1'
5873fa
+        final_user_m2 = UserAccount(M2, final_dn)
5873fa
+        for val in final_user_m2.get_attr_vals_utf8('employeenumber'):
5873fa
+            log.info("Check %s is on M2" % val)
5873fa
+            assert(val in ['foo1'])
5873fa
+
5873fa
+        # check that the entry have the same uid values
5873fa
+        for val in final_user_m1.get_attr_vals_utf8('employeenumber'):
5873fa
+            log.info("Check M1.uid %s is also on M2" % val)
5873fa
+            assert(val in final_user_m2.get_attr_vals_utf8('employeenumber'))
5873fa
+
5873fa
+        for val in final_user_m2.get_attr_vals_utf8('employeenumber'):
5873fa
+            log.info("Check M2.uid %s is also on M1" % val)
5873fa
+            assert(val in final_user_m1.get_attr_vals_utf8('employeenumber'))
5873fa
 
5873fa
 class TestThreeMasters:
5873fa
     def test_nested_entries(self, topology_m3, base_m3):
5873fa
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modify.c b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
5873fa
index e9d7e87e3..a507f3c31 100644
5873fa
--- a/ldap/servers/slapd/back-ldbm/ldbm_modify.c
5873fa
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modify.c
5873fa
@@ -213,6 +213,112 @@ error:
5873fa
     return retval;
5873fa
 }
5873fa
 
5873fa
+int32_t
5873fa
+entry_get_rdn_mods(Slapi_PBlock *pb, Slapi_Entry *entry, CSN *csn, int repl_op, Slapi_Mods **smods_ret)
5873fa
+{
5873fa
+    unsigned long op_type = SLAPI_OPERATION_NONE;
5873fa
+    char *new_rdn = NULL;
5873fa
+    char **dns = NULL;
5873fa
+    char **rdns = NULL;
5873fa
+    Slapi_Mods *smods = NULL;
5873fa
+    char *type = NULL;
5873fa
+    struct berval *bvp[2] = {0};
5873fa
+    struct berval bv;
5873fa
+    Slapi_Attr *attr = NULL;
5873fa
+    const char *entry_dn = NULL;
5873fa
+
5873fa
+    *smods_ret = NULL;
5873fa
+    entry_dn = slapi_entry_get_dn_const(entry);
5873fa
+    /* Do not bother to check that RDN is present, no one rename RUV or change its nsuniqueid */
5873fa
+    if (strcasestr(entry_dn, RUV_STORAGE_ENTRY_UNIQUEID)) {
5873fa
+        return 0;
5873fa
+    }
5873fa
+
5873fa
+    /* First get the RDNs of the operation */
5873fa
+    slapi_pblock_get(pb, SLAPI_OPERATION_TYPE, &op_type);
5873fa
+    switch (op_type) {
5873fa
+        case SLAPI_OPERATION_MODIFY:
5873fa
+            dns = slapi_ldap_explode_dn(entry_dn, 0);
5873fa
+            if (dns == NULL) {
5873fa
+                slapi_log_err(SLAPI_LOG_ERR, "entry_get_rdn_mods",
5873fa
+                      "Fails to split DN \"%s\" into components\n", entry_dn);
5873fa
+                return -1;
5873fa
+            }
5873fa
+            rdns = slapi_ldap_explode_rdn(dns[0], 0);
5873fa
+            slapi_ldap_value_free(dns);
5873fa
+
5873fa
+            break;
5873fa
+        case SLAPI_OPERATION_MODRDN:
5873fa
+            slapi_pblock_get(pb, SLAPI_MODRDN_NEWRDN, &new_rdn);
5873fa
+            rdns = slapi_ldap_explode_rdn(new_rdn, 0);
5873fa
+            break;
5873fa
+        default:
5873fa
+            break;
5873fa
+    }
5873fa
+    if (rdns == NULL || rdns[0] == NULL) {
5873fa
+        slapi_log_err(SLAPI_LOG_ERR, "entry_get_rdn_mods",
5873fa
+                      "Fails to split RDN \"%s\" into components\n", slapi_entry_get_dn_const(entry));
5873fa
+        return -1;
5873fa
+    }
5873fa
+
5873fa
+    /* Update the entry to add RDNs values if they are missing */
5873fa
+    smods = slapi_mods_new();
5873fa
+
5873fa
+    bvp[0] = &bv;
5873fa
+    bvp[1] = NULL;
5873fa
+    for (size_t rdns_count = 0; rdns[rdns_count]; rdns_count++) {
5873fa
+        Slapi_Value *value;
5873fa
+        attr = NULL;
5873fa
+        slapi_rdn2typeval(rdns[rdns_count], &type, &bv;;
5873fa
+
5873fa
+        /* Check if the RDN value exists */
5873fa
+        if ((slapi_entry_attr_find(entry, type, &attr) != 0) ||
5873fa
+            (slapi_attr_value_find(attr, &bv))) {
5873fa
+            const CSN *csn_rdn_add;
5873fa
+            const CSN *adcsn = attr_get_deletion_csn(attr);
5873fa
+
5873fa
+            /* It is missing => adds it */
5873fa
+            if (slapi_attr_flag_is_set(attr, SLAPI_ATTR_FLAG_SINGLE)) {
5873fa
+                if (csn_compare(adcsn, csn) >= 0) {
5873fa
+                    /* this is a single valued attribute and the current value
5873fa
+                     * (that is different from RDN value) is more recent than
5873fa
+                     * the RDN value we want to apply.
5873fa
+                     * Keep the current value and add a conflict flag
5873fa
+                     */
5873fa
+
5873fa
+                    type = ATTR_NSDS5_REPLCONFLICT;
5873fa
+                    bv.bv_val = "RDN value may be missing because it is single-valued";
5873fa
+                    bv.bv_len = strlen(bv.bv_val);
5873fa
+                    slapi_entry_add_string(entry, type, bv.bv_val);
5873fa
+                    slapi_mods_add_modbvps(smods, LDAP_MOD_ADD, type, bvp);
5873fa
+                    continue;
5873fa
+                }
5873fa
+            }
5873fa
+            /* if a RDN value needs to be forced, make sure it csn is ahead */
5873fa
+            slapi_mods_add_modbvps(smods, LDAP_MOD_ADD, type, bvp);
5873fa
+            csn_rdn_add = csn_max(adcsn, csn);
5873fa
+
5873fa
+            if (entry_apply_mods_wsi(entry, smods, csn_rdn_add, repl_op)) {
5873fa
+                slapi_log_err(SLAPI_LOG_ERR, "entry_get_rdn_mods",
5873fa
+                              "Fails to set \"%s\" in  \"%s\"\n", type, slapi_entry_get_dn_const(entry));
5873fa
+                slapi_ldap_value_free(rdns);
5873fa
+                slapi_mods_free(&smods);
5873fa
+                return -1;
5873fa
+            }
5873fa
+            /* Make the RDN value a distinguished value */
5873fa
+            attr_value_find_wsi(attr, &bv, &value);
5873fa
+            value_update_csn(value, CSN_TYPE_VALUE_DISTINGUISHED, csn_rdn_add);
5873fa
+        }
5873fa
+    }
5873fa
+    slapi_ldap_value_free(rdns);
5873fa
+    if (smods->num_mods == 0) {
5873fa
+        /* smods_ret already NULL, just free the useless smods */
5873fa
+        slapi_mods_free(&smods);
5873fa
+    } else {
5873fa
+        *smods_ret = smods;
5873fa
+    }
5873fa
+    return 0;
5873fa
+}
5873fa
 /**
5873fa
    Apply the mods to the ec entry.  Check for syntax, schema problems.
5873fa
    Check for abandon.
5873fa
@@ -269,6 +375,8 @@ modify_apply_check_expand(
5873fa
         goto done;
5873fa
     }
5873fa
 
5873fa
+
5873fa
+
5873fa
     /*
5873fa
      * If the objectClass attribute type was modified in any way, expand
5873fa
      * the objectClass values to reflect the inheritance hierarchy.
5873fa
@@ -414,6 +522,7 @@ ldbm_back_modify(Slapi_PBlock *pb)
5873fa
     int result_sent = 0;
5873fa
     int32_t parent_op = 0;
5873fa
     struct timespec parent_time;
5873fa
+    Slapi_Mods *smods_add_rdn = NULL;
5873fa
 
5873fa
     slapi_pblock_get(pb, SLAPI_BACKEND, &be);
5873fa
     slapi_pblock_get(pb, SLAPI_PLUGIN_PRIVATE, &li;;
5873fa
@@ -731,6 +840,15 @@ ldbm_back_modify(Slapi_PBlock *pb)
5873fa
             }
5873fa
         } /* else if new_mod_count == mod_count then betxnpremod plugin did nothing */
5873fa
 
5873fa
+        /* time to check if applying a replicated operation removed
5873fa
+         * the RDN value from the entry. Assuming that only replicated update
5873fa
+         * can lead to that bad result
5873fa
+         */
5873fa
+        if (entry_get_rdn_mods(pb, ec->ep_entry, opcsn, repl_op, &smods_add_rdn)) {
5873fa
+            goto error_return;
5873fa
+        }
5873fa
+
5873fa
+
5873fa
         /*
5873fa
          * Update the ID to Entry index.
5873fa
          * Note that id2entry_add replaces the entry, so the Entry ID
5873fa
@@ -764,6 +882,23 @@ ldbm_back_modify(Slapi_PBlock *pb)
5873fa
             MOD_SET_ERROR(ldap_result_code, LDAP_OPERATIONS_ERROR, retry_count);
5873fa
             goto error_return;
5873fa
         }
5873fa
+
5873fa
+        if (smods_add_rdn && slapi_mods_get_num_mods(smods_add_rdn) > 0) {
5873fa
+            retval = index_add_mods(be, (LDAPMod **) slapi_mods_get_ldapmods_byref(smods_add_rdn), e, ec, &txn);
5873fa
+            if (DB_LOCK_DEADLOCK == retval) {
5873fa
+                /* Abort and re-try */
5873fa
+                slapi_mods_free(&smods_add_rdn);
5873fa
+                continue;
5873fa
+            }
5873fa
+            if (retval != 0) {
5873fa
+                slapi_log_err(SLAPI_LOG_ERR, "ldbm_back_modify",
5873fa
+                        "index_add_mods (rdn) failed, err=%d %s\n",
5873fa
+                        retval, (msg = dblayer_strerror(retval)) ? msg : "");
5873fa
+                MOD_SET_ERROR(ldap_result_code, LDAP_OPERATIONS_ERROR, retry_count);
5873fa
+                slapi_mods_free(&smods_add_rdn);
5873fa
+                goto error_return;
5873fa
+            }
5873fa
+        }
5873fa
         /*
5873fa
          * Remove the old entry from the Virtual List View indexes.
5873fa
          * Add the new entry to the Virtual List View indexes.
5873fa
@@ -978,6 +1113,7 @@ error_return:
5873fa
 
5873fa
 common_return:
5873fa
     slapi_mods_done(&smods);
5873fa
+    slapi_mods_free(&smods_add_rdn);
5873fa
 
5873fa
     if (inst) {
5873fa
         if (ec_locked || cache_is_in_cache(&inst->inst_cache, ec)) {
5873fa
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
5873fa
index fde83c99f..e97b7a5f6 100644
5873fa
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
5873fa
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
5873fa
@@ -21,7 +21,7 @@ static void moddn_unlock_and_return_entry(backend *be, struct backentry **target
5873fa
 static int moddn_newrdn_mods(Slapi_PBlock *pb, const char *olddn, struct backentry *ec, Slapi_Mods *smods_wsi, int is_repl_op);
5873fa
 static IDList *moddn_get_children(back_txn *ptxn, Slapi_PBlock *pb, backend *be, struct backentry *parententry, Slapi_DN *parentdn, struct backentry ***child_entries, struct backdn ***child_dns, int is_resurect_operation);
5873fa
 static int moddn_rename_children(back_txn *ptxn, Slapi_PBlock *pb, backend *be, IDList *children, Slapi_DN *dn_parentdn, Slapi_DN *dn_newsuperiordn, struct backentry *child_entries[]);
5873fa
-static int modrdn_rename_entry_update_indexes(back_txn *ptxn, Slapi_PBlock *pb, struct ldbminfo *li, struct backentry *e, struct backentry **ec, Slapi_Mods *smods1, Slapi_Mods *smods2, Slapi_Mods *smods3);
5873fa
+static int modrdn_rename_entry_update_indexes(back_txn *ptxn, Slapi_PBlock *pb, struct ldbminfo *li, struct backentry *e, struct backentry **ec, Slapi_Mods *smods1, Slapi_Mods *smods2, Slapi_Mods *smods3, Slapi_Mods *smods4);
5873fa
 static void mods_remove_nsuniqueid(Slapi_Mods *smods);
5873fa
 
5873fa
 #define MOD_SET_ERROR(rc, error, count)                                            \
5873fa
@@ -100,6 +100,7 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
5873fa
     Connection *pb_conn = NULL;
5873fa
     int32_t parent_op = 0;
5873fa
     struct timespec parent_time;
5873fa
+    Slapi_Mods *smods_add_rdn = NULL;
5873fa
 
5873fa
     if (slapi_pblock_get(pb, SLAPI_CONN_ID, &conn_id) < 0) {
5873fa
         conn_id = 0; /* connection is NULL */
5873fa
@@ -842,6 +843,15 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
5873fa
                     goto error_return;
5873fa
                 }
5873fa
             }
5873fa
+
5873fa
+            /* time to check if applying a replicated operation removed
5873fa
+             * the RDN value from the entry. Assuming that only replicated update
5873fa
+             * can lead to that bad result
5873fa
+             */
5873fa
+            if (entry_get_rdn_mods(pb, ec->ep_entry, opcsn, is_replicated_operation, &smods_add_rdn)) {
5873fa
+                goto error_return;
5873fa
+            }
5873fa
+
5873fa
             /* check that the entry still obeys the schema */
5873fa
             if (slapi_entry_schema_check(pb, ec->ep_entry) != 0) {
5873fa
                 ldap_result_code = LDAP_OBJECT_CLASS_VIOLATION;
5873fa
@@ -1003,7 +1013,7 @@ ldbm_back_modrdn(Slapi_PBlock *pb)
5873fa
         /*
5873fa
          * Update the indexes for the entry.
5873fa
          */
5873fa
-        retval = modrdn_rename_entry_update_indexes(&txn, pb, li, e, &ec, &smods_generated, &smods_generated_wsi, &smods_operation_wsi);
5873fa
+        retval = modrdn_rename_entry_update_indexes(&txn, pb, li, e, &ec, &smods_generated, &smods_generated_wsi, &smods_operation_wsi, smods_add_rdn);
5873fa
         if (DB_LOCK_DEADLOCK == retval) {
5873fa
             /* Retry txn */
5873fa
             continue;
5873fa
@@ -1497,6 +1507,7 @@ common_return:
5873fa
     slapi_mods_done(&smods_operation_wsi);
5873fa
     slapi_mods_done(&smods_generated);
5873fa
     slapi_mods_done(&smods_generated_wsi);
5873fa
+    slapi_mods_free(&smods_add_rdn);
5873fa
     slapi_ch_free((void **)&child_entries);
5873fa
     slapi_ch_free((void **)&child_dns);
5873fa
     if (ldap_result_matcheddn && 0 != strcmp(ldap_result_matcheddn, "NULL"))
5873fa
@@ -1778,7 +1789,7 @@ mods_remove_nsuniqueid(Slapi_Mods *smods)
5873fa
  * mods contains the list of attribute change made.
5873fa
  */
5873fa
 static int
5873fa
-modrdn_rename_entry_update_indexes(back_txn *ptxn, Slapi_PBlock *pb, struct ldbminfo *li __attribute__((unused)), struct backentry *e, struct backentry **ec, Slapi_Mods *smods1, Slapi_Mods *smods2, Slapi_Mods *smods3)
5873fa
+modrdn_rename_entry_update_indexes(back_txn *ptxn, Slapi_PBlock *pb, struct ldbminfo *li __attribute__((unused)), struct backentry *e, struct backentry **ec, Slapi_Mods *smods1, Slapi_Mods *smods2, Slapi_Mods *smods3, Slapi_Mods *smods4)
5873fa
 {
5873fa
     backend *be;
5873fa
     ldbm_instance *inst;
5873fa
@@ -1874,6 +1885,24 @@ modrdn_rename_entry_update_indexes(back_txn *ptxn, Slapi_PBlock *pb, struct ldbm
5873fa
             goto error_return;
5873fa
         }
5873fa
     }
5873fa
+    if (smods4 != NULL && slapi_mods_get_num_mods(smods4) > 0) {
5873fa
+        /*
5873fa
+         * update the indexes: lastmod, rdn, etc.
5873fa
+         */
5873fa
+        retval = index_add_mods(be, slapi_mods_get_ldapmods_byref(smods4), e, *ec, ptxn);
5873fa
+        if (DB_LOCK_DEADLOCK == retval) {
5873fa
+            /* Retry txn */
5873fa
+            slapi_log_err(SLAPI_LOG_BACKLDBM, "modrdn_rename_entry_update_indexes",
5873fa
+                          "index_add_mods4 deadlock\n");
5873fa
+            goto error_return;
5873fa
+        }
5873fa
+        if (retval != 0) {
5873fa
+            slapi_log_err(SLAPI_LOG_TRACE, "modrdn_rename_entry_update_indexes",
5873fa
+                          "index_add_mods 4 failed, err=%d %s\n",
5873fa
+                          retval, (msg = dblayer_strerror(retval)) ? msg : "");
5873fa
+            goto error_return;
5873fa
+        }
5873fa
+    }
5873fa
     /*
5873fa
      * Remove the old entry from the Virtual List View indexes.
5873fa
      * Add the new entry to the Virtual List View indexes.
5873fa
@@ -1991,7 +2020,7 @@ moddn_rename_child_entry(
5873fa
          * Update all the indexes.
5873fa
          */
5873fa
         retval = modrdn_rename_entry_update_indexes(ptxn, pb, li, e, ec,
5873fa
-                                                    smodsp, NULL, NULL);
5873fa
+                                                    smodsp, NULL, NULL, NULL);
5873fa
         /* JCMREPL - Should the children get updated modifiersname and lastmodifiedtime? */
5873fa
         slapi_mods_done(&smods);
5873fa
     }
5873fa
diff --git a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
5873fa
index 4d2524fd9..e2f1100ed 100644
5873fa
--- a/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
5873fa
+++ b/ldap/servers/slapd/back-ldbm/proto-back-ldbm.h
5873fa
@@ -324,6 +324,7 @@ int get_parent_rdn(DB *db, ID parentid, Slapi_RDN *srdn);
5873fa
 /*
5873fa
  * modify.c
5873fa
  */
5873fa
+int32_t entry_get_rdn_mods(Slapi_PBlock *pb, Slapi_Entry *entry, CSN *csn, int repl_op, Slapi_Mods **smods_ret);
5873fa
 int modify_update_all(backend *be, Slapi_PBlock *pb, modify_context *mc, back_txn *txn);
5873fa
 void modify_init(modify_context *mc, struct backentry *old_entry);
5873fa
 int modify_apply_mods(modify_context *mc, Slapi_Mods *smods);
5873fa
-- 
5873fa
2.26.2
5873fa