Blame SOURCES/0018-Ticket-50020-during-MODRDN-referential-integrity-can.patch

fc2009
From 70fd6e1fa6667734f39146cef53de6e3ff22d765 Mon Sep 17 00:00:00 2001
fc2009
From: Thierry Bordaz <tbordaz@redhat.com>
fc2009
Date: Fri, 9 Nov 2018 17:07:11 +0100
fc2009
Subject: [PATCH] Ticket 50020 - during MODRDN referential integrity can fail
fc2009
 erronously while updating large groups
fc2009
fc2009
Bug Description:
fc2009
	During a MODRDN of a group member, referential integrity will update the groups containing this member.
fc2009
	Under specific conditions, the MODRDN can fail (err=1).
fc2009
fc2009
	on MODRDN Referential integrity checks if the original DN of the target MODRDN entry is
fc2009
	member of a given group. If it is then it updates the group.
fc2009
	The returned code of the group update is using the variable 'rc'.
fc2009
	It does a normalized DN comparison to compare original DN with members DN, to determine if
fc2009
	a group needs to be updated.
fc2009
	If the group does not need to be updated, 'rc' is not set.
fc2009
	The bug is that it uses 'rc' to normalize the DN and if the group is not updated
fc2009
	the returned code reflects the normalization returned code rather that the group update.
fc2009
fc2009
	The bug is hit in specific conditions
fc2009
fc2009
	    One of the evaluated group contains more than 128 members
fc2009
	    the last member (last value) of the group is not the moved entry
fc2009
	    the last member (last value) of the group is a DN value that contains escaped chars
fc2009
fc2009
Fix Description:
fc2009
	Use a local variable to check the result of the DN normalization
fc2009
fc2009
https://pagure.io/389-ds-base/issue/50020
fc2009
fc2009
Reviewed by: Simon Pichugin, Mark Reynolds (thanks)
fc2009
fc2009
Platforms tested: F27
fc2009
fc2009
Flag Day: no
fc2009
---
fc2009
 .../tests/suites/plugins/referint_test.py     | 103 ++++++++++++++++++
fc2009
 ldap/servers/plugins/referint/referint.c      |  18 +--
fc2009
 2 files changed, 113 insertions(+), 8 deletions(-)
fc2009
 create mode 100644 dirsrvtests/tests/suites/plugins/referint_test.py
fc2009
fc2009
diff --git a/dirsrvtests/tests/suites/plugins/referint_test.py b/dirsrvtests/tests/suites/plugins/referint_test.py
fc2009
new file mode 100644
fc2009
index 000000000..67a11de9e
fc2009
--- /dev/null
fc2009
+++ b/dirsrvtests/tests/suites/plugins/referint_test.py
fc2009
@@ -0,0 +1,103 @@
fc2009
+# --- BEGIN COPYRIGHT BLOCK ---
fc2009
+# Copyright (C) 2016 Red Hat, Inc.
fc2009
+# All rights reserved.
fc2009
+#
fc2009
+# License: GPL (version 3 or any later version).
fc2009
+# See LICENSE for details.
fc2009
+# --- END COPYRIGHT BLOCK ---
fc2009
+#
fc2009
+'''
fc2009
+Created on Dec 12, 2019
fc2009
+
fc2009
+@author: tbordaz
fc2009
+'''
fc2009
+import logging
fc2009
+import subprocess
fc2009
+import pytest
fc2009
+from lib389 import Entry
fc2009
+from lib389.utils import *
fc2009
+from lib389.plugins import *
fc2009
+from lib389._constants import *
fc2009
+from lib389.idm.user import UserAccounts, UserAccount
fc2009
+from lib389.idm.group import Groups
fc2009
+from lib389.topologies import topology_st as topo
fc2009
+
fc2009
+log = logging.getLogger(__name__)
fc2009
+
fc2009
+ESCAPED_RDN_BASE = "foo\,oo"
fc2009
+def _user_get_dn(no):
fc2009
+    uid = '%s%d' % (ESCAPED_RDN_BASE, no)
fc2009
+    dn = 'uid=%s,%s' % (uid, SUFFIX)
fc2009
+    return (uid, dn)
fc2009
+
fc2009
+def add_escaped_user(server, no):
fc2009
+    (uid, dn) = _user_get_dn(no)
fc2009
+    log.fatal('Adding user (%s): ' % dn)
fc2009
+    server.add_s(Entry((dn, {'objectclass': ['top', 'person', 'organizationalPerson', 'inetOrgPerson'],
fc2009
+                             'uid': [uid],
fc2009
+                             'sn' : [uid],
fc2009
+                             'cn' : [uid]})))
fc2009
+    return dn
fc2009
+
fc2009
+@pytest.mark.ds50020
fc2009
+def test_referential_false_failure(topo):
fc2009
+    """On MODRDN referential integrity can erronously fail
fc2009
+
fc2009
+    :id: f77aeb80-c4c4-471b-8c1b-4733b714778b
fc2009
+    :setup: Standalone Instance
fc2009
+    :steps:
fc2009
+        1. Configure the plugin
fc2009
+        2. Create a group
fc2009
+            - 1rst member the one that will be move
fc2009
+            - more than 128 members
fc2009
+            - last member is a DN containing escaped char
fc2009
+        3. Rename the 1rst member
fc2009
+    :expectedresults:
fc2009
+        1. should succeed
fc2009
+        2. should succeed
fc2009
+        3. should succeed
fc2009
+    """
fc2009
+
fc2009
+    inst = topo[0]
fc2009
+
fc2009
+    # stop the plugin, and start it
fc2009
+    plugin = ReferentialIntegrityPlugin(inst)
fc2009
+    plugin.disable()
fc2009
+    plugin.enable()
fc2009
+
fc2009
+    ############################################################################
fc2009
+    # Configure plugin
fc2009
+    ############################################################################
fc2009
+    GROUP_CONTAINER = "ou=groups,%s" % DEFAULT_SUFFIX
fc2009
+    plugin.replace('referint-membership-attr', 'member')
fc2009
+    plugin.replace('nsslapd-plugincontainerscope', GROUP_CONTAINER)
fc2009
+
fc2009
+    ############################################################################
fc2009
+    # Creates a group with members having escaped DN
fc2009
+    ############################################################################
fc2009
+    # Add some users and a group
fc2009
+    users = UserAccounts(inst, DEFAULT_SUFFIX, None)
fc2009
+    user1 = users.create_test_user(uid=1001)
fc2009
+    user2 = users.create_test_user(uid=1002)
fc2009
+
fc2009
+    groups = Groups(inst, GROUP_CONTAINER, None)
fc2009
+    group = groups.create(properties={'cn': 'group'})
fc2009
+    group.add('member', user2.dn)
fc2009
+    group.add('member', user1.dn)
fc2009
+
fc2009
+    # Add more than 128 members so that referint follows the buggy path
fc2009
+    for i in range(130):
fc2009
+        escaped_user = add_escaped_user(inst, i)
fc2009
+        group.add('member', escaped_user)
fc2009
+
fc2009
+    ############################################################################
fc2009
+    # Check that the MODRDN succeeds
fc2009
+    ###########################################################################
fc2009
+    # Here we need to restart so that member values are taken in the right order
fc2009
+    # the last value is the escaped one
fc2009
+    inst.restart()
fc2009
+
fc2009
+    # Here if the bug is fixed, referential is able to update the member value
fc2009
+    inst.rename_s(user1.dn, 'uid=new_test_user_1001', newsuperior=SUFFIX, delold=0)
fc2009
+
fc2009
+
fc2009
diff --git a/ldap/servers/plugins/referint/referint.c b/ldap/servers/plugins/referint/referint.c
fc2009
index f6d1c27a2..9e4e680d3 100644
fc2009
--- a/ldap/servers/plugins/referint/referint.c
fc2009
+++ b/ldap/servers/plugins/referint/referint.c
fc2009
@@ -824,20 +824,21 @@ _update_one_per_mod(Slapi_DN *entrySDN, /* DN of the searched entry */
fc2009
          */
fc2009
         for (nval = slapi_attr_first_value(attr, &v); nval != -1;
fc2009
              nval = slapi_attr_next_value(attr, nval, &v)) {
fc2009
+            int normalize_rc;
fc2009
             p = NULL;
fc2009
             dnlen = 0;
fc2009
 
fc2009
             /* DN syntax, which should be a string */
fc2009
             sval = slapi_ch_strdup(slapi_value_get_string(v));
fc2009
-            rc = slapi_dn_normalize_case_ext(sval, 0, &p, &dnlen);
fc2009
-            if (rc == 0) { /* sval is passed in; not terminated */
fc2009
+            normalize_rc = slapi_dn_normalize_case_ext(sval, 0, &p, &dnlen);
fc2009
+            if (normalize_rc == 0) { /* sval is passed in; not terminated */
fc2009
                 *(p + dnlen) = '\0';
fc2009
                 sval = p;
fc2009
-            } else if (rc > 0) {
fc2009
+            } else if (normalize_rc > 0) {
fc2009
                 slapi_ch_free_string(&sval);
fc2009
                 sval = p;
fc2009
             }
fc2009
-            /* else: (rc < 0) Ignore the DN normalization error for now. */
fc2009
+            /* else: (normalize_rc < 0) Ignore the DN normalization error for now. */
fc2009
 
fc2009
             p = PL_strstr(sval, slapi_sdn_get_ndn(origDN));
fc2009
             if (p == sval) {
fc2009
@@ -1013,20 +1014,21 @@ _update_all_per_mod(Slapi_DN *entrySDN, /* DN of the searched entry */
fc2009
         for (nval = slapi_attr_first_value(attr, &v);
fc2009
              nval != -1;
fc2009
              nval = slapi_attr_next_value(attr, nval, &v)) {
fc2009
+            int normalize_rc;
fc2009
             p = NULL;
fc2009
             dnlen = 0;
fc2009
 
fc2009
             /* DN syntax, which should be a string */
fc2009
             sval = slapi_ch_strdup(slapi_value_get_string(v));
fc2009
-            rc = slapi_dn_normalize_case_ext(sval, 0, &p, &dnlen);
fc2009
-            if (rc == 0) { /* sval is passed in; not terminated */
fc2009
+            normalize_rc = slapi_dn_normalize_case_ext(sval, 0, &p, &dnlen);
fc2009
+            if (normalize_rc == 0) { /* sval is passed in; not terminated */
fc2009
                 *(p + dnlen) = '\0';
fc2009
                 sval = p;
fc2009
-            } else if (rc > 0) {
fc2009
+            } else if (normalize_rc > 0) {
fc2009
                 slapi_ch_free_string(&sval);
fc2009
                 sval = p;
fc2009
             }
fc2009
-            /* else: (rc < 0) Ignore the DN normalization error for now. */
fc2009
+            /* else: normalize_rc < 0) Ignore the DN normalization error for now. */
fc2009
 
fc2009
             p = PL_strstr(sval, slapi_sdn_get_ndn(origDN));
fc2009
             if (p == sval) {
fc2009
-- 
fc2009
2.17.2
fc2009