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

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