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

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