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

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