Blob Blame Raw
From 7ad3f7283e41f3d690626db5b38a9fb63fa8e005 Mon Sep 17 00:00:00 2001
From: Mark Reynolds <mreynolds@redhat.com>
Date: Tue, 18 Nov 2014 09:24:21 -0500
Subject: [PATCH 36/53] Ticket 47950 - Bind DN tracking unable to write to
 internalModifiersName without special permissions

Bug Description:  When a non-rootDN entry makes an update when plugin bind dn tracking
                  is enabled, it is incorrectly rejected with an error 50.

Fix Description:  Create a function to check if an attribute is one of last mod attributes,
                  including the internalModfieresname/internalModifytimestamp.  Use this
                  new function wherever we are checking for last mod attributes.

https://fedorahosted.org/389/ticket/47950

Reviewed by: rmeggins(Thanks!)

(cherry picked from commit c973e7150cf1e7fb3f61a76cf1baf3c0fa91f756)
(cherry picked from commit fa8f7dc3d1ab508c762fc2e18de0cb860ed84657)
---
 dirsrvtests/tickets/ticket47950_test.py            | 273 +++++++++++++++++++++
 ldap/servers/plugins/acl/acl.c                     |   4 +-
 ldap/servers/plugins/replication/cl5_config.c      |   9 +-
 ldap/servers/plugins/replication/repl5_agmtlist.c  |   3 +-
 .../plugins/replication/repl5_replica_config.c     |   3 +-
 ldap/servers/slapd/add.c                           |   4 +-
 ldap/servers/slapd/back-ldbm/ldbm_config.c         |   3 +-
 ldap/servers/slapd/configdse.c                     |   5 +-
 ldap/servers/slapd/opshared.c                      |  17 ++
 ldap/servers/slapd/result.c                        |   2 +
 ldap/servers/slapd/slapi-plugin.h                  |   2 +
 ldap/servers/slapd/task.c                          |   4 +-
 ldap/servers/slapd/tools/mmldif.c                  |  12 +-
 13 files changed, 309 insertions(+), 32 deletions(-)
 create mode 100644 dirsrvtests/tickets/ticket47950_test.py

diff --git a/dirsrvtests/tickets/ticket47950_test.py b/dirsrvtests/tickets/ticket47950_test.py
new file mode 100644
index 0000000..976f964
--- /dev/null
+++ b/dirsrvtests/tickets/ticket47950_test.py
@@ -0,0 +1,273 @@
+import os
+import sys
+import time
+import ldap
+import logging
+import socket
+import pytest
+from lib389 import DirSrv, Entry, tools, tasks
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from lib389.tasks import *
+from constants import *
+
+log = logging.getLogger(__name__)
+
+installation_prefix = None
+
+USER1_DN = "uid=user1,%s" % DEFAULT_SUFFIX
+USER2_DN = "uid=user2,%s" % DEFAULT_SUFFIX
+
+
+class TopologyStandalone(object):
+    def __init__(self, standalone):
+        standalone.open()
+        self.standalone = standalone
+
+
+@pytest.fixture(scope="module")
+def topology(request):
+    '''
+        This fixture is used to standalone topology for the 'module'.
+        At the beginning, It may exists a standalone instance.
+        It may also exists a backup for the standalone instance.
+
+        Principle:
+            If standalone instance exists:
+                restart it
+            If backup of standalone exists:
+                create/rebind to standalone
+
+                restore standalone instance from backup
+            else:
+                Cleanup everything
+                    remove instance
+                    remove backup
+                Create instance
+                Create backup
+    '''
+    global installation_prefix
+
+    if installation_prefix:
+        args_instance[SER_DEPLOYED_DIR] = installation_prefix
+
+    standalone = DirSrv(verbose=False)
+
+    # Args for the standalone instance
+    args_instance[SER_HOST] = HOST_STANDALONE
+    args_instance[SER_PORT] = PORT_STANDALONE
+    args_instance[SER_SERVERID_PROP] = SERVERID_STANDALONE
+    args_standalone = args_instance.copy()
+    standalone.allocate(args_standalone)
+
+    # Get the status of the backups
+    backup_standalone = standalone.checkBackupFS()
+
+    # Get the status of the instance and restart it if it exists
+    instance_standalone = standalone.exists()
+    if instance_standalone:
+        # assuming the instance is already stopped, just wait 5 sec max
+        standalone.stop(timeout=5)
+        standalone.start(timeout=10)
+
+    if backup_standalone:
+        # The backup exist, assuming it is correct
+        # we just re-init the instance with it
+        if not instance_standalone:
+            standalone.create()
+            # Used to retrieve configuration information (dbdir, confdir...)
+            standalone.open()
+
+        # restore standalone instance from backup
+        standalone.stop(timeout=10)
+        standalone.restoreFS(backup_standalone)
+        standalone.start(timeout=10)
+
+    else:
+        # We should be here only in two conditions
+        #      - This is the first time a test involve standalone instance
+        #      - Something weird happened (instance/backup destroyed)
+        #        so we discard everything and recreate all
+
+        # Remove the backup. So even if we have a specific backup file
+        # (e.g backup_standalone) we clear backup that an instance may have created
+        if backup_standalone:
+            standalone.clearBackupFS()
+
+        # Remove the instance
+        if instance_standalone:
+            standalone.delete()
+
+        # Create the instance
+        standalone.create()
+
+        # Used to retrieve configuration information (dbdir, confdir...)
+        standalone.open()
+
+        # Time to create the backups
+        standalone.stop(timeout=10)
+        standalone.backupfile = standalone.backupFS()
+        standalone.start(timeout=10)
+
+    # clear the tmp directory
+    standalone.clearTmpDir(__file__)
+
+    #
+    # Here we have standalone instance up and running
+    # Either coming from a backup recovery
+    # or from a fresh (re)init
+    # Time to return the topology
+    return TopologyStandalone(standalone)
+
+
+def test_ticket47950(topology):
+    """
+        Testing nsslapd-plugin-binddn-tracking does not cause issues around
+        access control and reconfiguring replication/repl agmt.
+    """
+
+    log.info('Testing Ticket 47950 - Testing nsslapd-plugin-binddn-tracking')
+
+    #
+    # Turn on bind dn tracking
+    #
+    try:
+        topology.standalone.modify_s("cn=config", [(ldap.MOD_REPLACE, 'nsslapd-plugin-binddn-tracking', 'on')])
+        log.info('nsslapd-plugin-binddn-tracking enabled.')
+    except ldap.LDAPError, e:
+        log.error('Failed to enable bind dn tracking: ' + e.message['desc'])
+        assert False
+
+    #
+    # Add two users
+    #
+    try:
+        topology.standalone.add_s(Entry((USER1_DN, {
+                                        'objectclass': "top person inetuser".split(),
+                                        'userpassword': "password",
+                                        'sn': "1",
+                                        'cn': "user 1"})))
+        log.info('Added test user %s' % USER1_DN)
+    except ldap.LDAPError, e:
+        log.error('Failed to add %s: %s' % (USER1_DN, e.message['desc']))
+        assert False
+
+    try:
+        topology.standalone.add_s(Entry((USER2_DN, {
+                                        'objectclass': "top person inetuser".split(),
+                                        'sn': "2",
+                                        'cn': "user 2"})))
+        log.info('Added test user %s' % USER2_DN)
+    except ldap.LDAPError, e:
+        log.error('Failed to add user1: ' + e.message['desc'])
+        assert False
+
+    #
+    # Add an aci
+    #
+    try:
+        acival = '(targetattr ="cn")(version 3.0;acl "Test bind dn tracking"' + \
+             ';allow (all) (userdn = "ldap:///%s");)' % USER1_DN
+
+        topology.standalone.modify_s(DEFAULT_SUFFIX, [(ldap.MOD_ADD, 'aci', acival)])
+        log.info('Added aci')
+    except ldap.LDAPError, e:
+        log.error('Failed to add aci: ' + e.message['desc'])
+        assert False
+
+    #
+    # Make modification as user
+    #
+    try:
+        topology.standalone.simple_bind_s(USER1_DN, "password")
+        log.info('Bind as user %s successful' % USER1_DN)
+    except ldap.LDAPError, e:
+        log.error('Failed to bind as user1: ' + e.message['desc'])
+        assert False
+
+    try:
+        topology.standalone.modify_s(USER2_DN, [(ldap.MOD_REPLACE, 'cn', 'new value')])
+        log.info('%s successfully modified user %s' % (USER1_DN, USER2_DN))
+    except ldap.LDAPError, e:
+        log.error('Failed to update user2: ' + e.message['desc'])
+        assert False
+
+    #
+    # Setup replica and create a repl agmt
+    #
+    try:
+        topology.standalone.simple_bind_s(DN_DM, PASSWORD)
+        log.info('Bind as %s successful' % DN_DM)
+    except ldap.LDAPError, e:
+        log.error('Failed to bind as rootDN: ' + e.message['desc'])
+        assert False
+
+    try:
+        topology.standalone.replica.enableReplication(suffix=DEFAULT_SUFFIX, role=REPLICAROLE_MASTER,
+                                                  replicaId=REPLICAID_MASTER)
+        log.info('Successfully enabled replication.')
+    except ValueError:
+        log.error('Failed to enable replication')
+        assert False
+
+    properties = {RA_NAME: r'test plugin internal bind dn',
+                  RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
+                  RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
+                  RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
+                  RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
+
+    try:
+        repl_agreement = topology.standalone.agreement.create(suffix=DEFAULT_SUFFIX, host="127.0.0.1",
+                                                          port="7777", properties=properties)
+        log.info('Successfully created replication agreement')
+    except InvalidArgumentError, e:
+        log.error('Failed to create replication agreement: ' + e.message['desc'])
+        assert False
+
+    #
+    # modify replica
+    #
+    try:
+        properties = {REPLICA_ID: "7"}
+        topology.standalone.replica.setProperties(DEFAULT_SUFFIX, None, None, properties)
+        log.info('Successfully modified replica')
+    except ldap.LDAPError, e:
+        log.error('Failed to update replica config: ' + e.message['desc'])
+        assert False
+
+    #
+    # modify repl agmt
+    #
+    try:
+        properties = {RA_CONSUMER_PORT: "8888"}
+        topology.standalone.agreement.setProperties(None, repl_agreement, None, properties)
+        log.info('Successfully modified replication agreement')
+    except ValueError:
+        log.error('Failed to update replica agreement: ' + repl_agreement)
+        assert False
+
+    # We passed
+    log.info("Test Passed.")
+
+
+def test_ticket47953_final(topology):
+    topology.standalone.stop(timeout=10)
+
+
+def run_isolated():
+    '''
+        run_isolated is used to run these test cases independently of a test scheduler (xunit, py.test..)
+        To run isolated without py.test, you need to
+            - edit this file and comment '@pytest.fixture' line before 'topology' function.
+            - set the installation prefix
+            - run this program
+    '''
+    global installation_prefix
+    installation_prefix = None
+
+    topo = topology(True)
+    test_ticket47950(topo)
+
+if __name__ == '__main__':
+    run_isolated()
\ No newline at end of file
diff --git a/ldap/servers/plugins/acl/acl.c b/ldap/servers/plugins/acl/acl.c
index 5416330..403c5b3 100644
--- a/ldap/servers/plugins/acl/acl.c
+++ b/ldap/servers/plugins/acl/acl.c
@@ -1450,9 +1450,7 @@ acl_check_mods(
 			    if (be != NULL)
 				slapi_pblock_get ( pb, SLAPI_BE_LASTMOD, &lastmod );
 			}
-			if (lastmod &&
-			    (strcmp (mod->mod_type, "modifiersname")== 0 ||
-			     strcmp (mod->mod_type, "modifytimestamp")== 0)) {
+			if (lastmod && slapi_attr_is_last_mod(mod->mod_type)) {
 				/* skip pseudo attr(s)  */
 				continue; 
 			}
diff --git a/ldap/servers/plugins/replication/cl5_config.c b/ldap/servers/plugins/replication/cl5_config.c
index 55727c2..a3a0fb3 100644
--- a/ldap/servers/plugins/replication/cl5_config.c
+++ b/ldap/servers/plugins/replication/cl5_config.c
@@ -357,15 +357,10 @@ changelog5_config_modify (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entr
                 config_attr = (char *) mods[i]->mod_type; 
                 config_attr_value = (char *) mods[i]->mod_bvalues[j]->bv_val;
 
-#define ATTR_MODIFIERSNAME	"modifiersname"
-#define ATTR_MODIFYTIMESTAMP	"modifytimestamp"
-
-				if ( strcasecmp ( config_attr, ATTR_MODIFIERSNAME ) == 0 ) {
-                    continue;
-                }
-				if ( strcasecmp ( config_attr, ATTR_MODIFYTIMESTAMP ) == 0 ) {
+				if ( slapi_attr_is_last_mod(config_attr)){
 					continue;
                 }
+
                 /* replace existing value */
                 if ( strcasecmp (config_attr, CONFIG_CHANGELOG_DIR_ATTRIBUTE ) == 0 )
 				{
diff --git a/ldap/servers/plugins/replication/repl5_agmtlist.c b/ldap/servers/plugins/replication/repl5_agmtlist.c
index 5eead07..4a1ff5d 100644
--- a/ldap/servers/plugins/replication/repl5_agmtlist.c
+++ b/ldap/servers/plugins/replication/repl5_agmtlist.c
@@ -524,8 +524,7 @@ agmtlist_modify_callback(Slapi_PBlock *pb, Slapi_Entry *entryBefore, Slapi_Entry
 			repl5_set_debug_timeout(val);
 			slapi_ch_free_string(&val);
 		}
-        else if (strcasecmp (mods[i]->mod_type, "modifytimestamp") == 0 ||
-                 strcasecmp (mods[i]->mod_type, "modifiersname") == 0 ||
+        else if (slapi_attr_is_last_mod(mods[i]->mod_type) ||
                  strcasecmp (mods[i]->mod_type, "description") == 0)
         {
             /* ignore modifier's name and timestamp attributes and the description. */
diff --git a/ldap/servers/plugins/replication/repl5_replica_config.c b/ldap/servers/plugins/replication/repl5_replica_config.c
index 2810b11..3bc3916 100644
--- a/ldap/servers/plugins/replication/repl5_replica_config.c
+++ b/ldap/servers/plugins/replication/repl5_replica_config.c
@@ -534,8 +534,7 @@ replica_config_modify (Slapi_PBlock *pb, Slapi_Entry* entryBefore, Slapi_Entry*
                     }
                 }
                 /* ignore modifiers attributes added by the server */
-                else if (strcasecmp (config_attr, "modifytimestamp") == 0 ||
-                         strcasecmp (config_attr, "modifiersname") == 0)
+                else if (slapi_attr_is_last_mod(config_attr))
                 {
                     *returncode = LDAP_SUCCESS;
                 }
diff --git a/ldap/servers/slapd/add.c b/ldap/servers/slapd/add.c
index 875ad22..28fae32 100644
--- a/ldap/servers/slapd/add.c
+++ b/ldap/servers/slapd/add.c
@@ -911,8 +911,8 @@ static int check_rdn_for_created_attrs(Slapi_Entry *e)
     int i, rc = 0;
     Slapi_RDN *rdn = NULL;
     char *value = NULL;
-    char *type[] = {SLAPI_ATTR_UNIQUEID, "modifytimestamp", "createtimestamp",
-                   "creatorsname", "modifiersname", 0};
+    char *type[] = {SLAPI_ATTR_UNIQUEID, "modifytimestamp", "modifiersname", "internalmodifytimestamp",
+                    "internalmodifiersname", "createtimestamp", "creatorsname", 0};
 
     if ((rdn = slapi_rdn_new())) {
         slapi_rdn_init_dn(rdn, slapi_entry_get_dn_const(e));
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_config.c b/ldap/servers/slapd/back-ldbm/ldbm_config.c
index 0f8cc76..294999c 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_config.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_config.c
@@ -1849,10 +1849,9 @@ int ldbm_config_ignored_attr(char *attr_name)
     if (!strcasecmp("objectclass", attr_name) ||
         !strcasecmp("cn", attr_name) ||
         !strcasecmp("creatorsname", attr_name) ||
-        !strcasecmp("modifiersname", attr_name) ||
         !strcasecmp("createtimestamp", attr_name) ||
         !strcasecmp(LDBM_NUMSUBORDINATES_STR, attr_name) ||
-        !strcasecmp("modifytimestamp", attr_name)) {
+        slapi_attr_is_last_mod(attr_name)){
         return 1;
     } else {
         return 0;
diff --git a/ldap/servers/slapd/configdse.c b/ldap/servers/slapd/configdse.c
index 339be42..e70e340 100644
--- a/ldap/servers/slapd/configdse.c
+++ b/ldap/servers/slapd/configdse.c
@@ -116,10 +116,9 @@ ignore_attr_type(const char *attr_type)
 		 (strcasecmp (attr_type, "aci") == 0) ||
 		 (strcasecmp (attr_type, "objectclass") == 0) ||
 		 (strcasecmp (attr_type, "numsubordinates") == 0) ||
-		 (strcasecmp (attr_type, "internalModifiersname") == 0) ||
 		 (strcasecmp (attr_type, "internalCreatorsname") == 0) ||
-		 (strcasecmp (attr_type, "modifytimestamp") == 0) ||
-		 (strcasecmp (attr_type, "modifiersname") == 0)) {
+		 slapi_attr_is_last_mod((char *)attr_type))
+	{
 		return 1;
 	}
 
diff --git a/ldap/servers/slapd/opshared.c b/ldap/servers/slapd/opshared.c
index 4e06652..ebd4fdf 100644
--- a/ldap/servers/slapd/opshared.c
+++ b/ldap/servers/slapd/opshared.c
@@ -218,6 +218,23 @@ modify_update_last_modified_attr(Slapi_PBlock *pb, Slapi_Mods *smods)
 }
 
 /*
+ * If the attribute is one of the last mod attributes return 1,
+ * otherwise return 0;
+ */
+int
+slapi_attr_is_last_mod(char *attr)
+{
+    if(strcasecmp (attr, "modifytimestamp") == 0 ||
+       strcasecmp (attr, "modifiersname") == 0 ||
+       strcasecmp (attr, "internalmodifytimestamp") == 0 ||
+       strcasecmp (attr, "internalmodifiersname") == 0)
+    {
+        return 1;
+    }
+    return 0;
+}
+
+/*
  * Returns: 0    - if the operation is successful
  *        < 0    - if operation fails. 
  * Note that an operation is considered "failed" if a result is sent 
diff --git a/ldap/servers/slapd/result.c b/ldap/servers/slapd/result.c
index 92573d5..ca2fa43 100644
--- a/ldap/servers/slapd/result.c
+++ b/ldap/servers/slapd/result.c
@@ -1059,6 +1059,8 @@ encode_attr(
 
 #define LASTMODATTR( x )	(strcasecmp( x, "modifytimestamp" ) == 0 \
 				    || strcasecmp( x, "modifiersname" ) == 0 \
+				    || strcasecmp( x, "internalmodifytimestamp" ) == 0 \
+				    || strcasecmp( x, "internalmodifiersname" ) == 0 \
 				    || strcasecmp( x, "createtimestamp" ) == 0 \
 				    || strcasecmp( x, "creatorsname" ) == 0)
 
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
index f1ecfe8..8ffc582 100644
--- a/ldap/servers/slapd/slapi-plugin.h
+++ b/ldap/servers/slapd/slapi-plugin.h
@@ -5278,6 +5278,8 @@ int slapi_filter_compare(struct slapi_filter *f1, struct slapi_filter *f2);
 Slapi_Filter *slapi_filter_dup(Slapi_Filter *f);
 int slapi_filter_changetype(Slapi_Filter *f, const char *newtype);
 
+int slapi_attr_is_last_mod(char *attr);
+
 /**
  * Normalize in-place the given filter.  Normalizes the attribute types always.
  * If norm_values is true, will also normalize the values.
diff --git a/ldap/servers/slapd/task.c b/ldap/servers/slapd/task.c
index a4d85a8..2a0cb82 100644
--- a/ldap/servers/slapd/task.c
+++ b/ldap/servers/slapd/task.c
@@ -802,8 +802,8 @@ static int task_modify(Slapi_PBlock *pb, Slapi_Entry *e,
          * stuck in by the server */
         if ((strcasecmp(mods[i]->mod_type, "ttl") != 0) &&
             (strcasecmp(mods[i]->mod_type, "nsTaskCancel") != 0) &&
-            (strcasecmp(mods[i]->mod_type, "modifiersName") != 0) &&
-            (strcasecmp(mods[i]->mod_type, "modifyTimestamp") != 0)) {
+            !slapi_attr_is_last_mod(mods[i]->mod_type))
+        {
             /* you aren't allowed to change this! */
             *returncode = LDAP_UNWILLING_TO_PERFORM;
             return SLAPI_DSE_CALLBACK_ERROR;
diff --git a/ldap/servers/slapd/tools/mmldif.c b/ldap/servers/slapd/tools/mmldif.c
index bf20945..156d892 100644
--- a/ldap/servers/slapd/tools/mmldif.c
+++ b/ldap/servers/slapd/tools/mmldif.c
@@ -1009,9 +1009,7 @@ addnew(FILE * edf3, const char *changetype, record_t * first)
     for (attnum = 1, att = &first->data;
          attnum <= first->nattrs;
          attnum++, att = attribnext(att)) {
-        if (!stricmp(attribname(att), "modifytimestamp"))
-            continue;
-        if (!stricmp(attribname(att), "modifiersname"))
+        if (slapi_attr_is_last_mod(attribname(att)))
             continue;
         if (!putvalue(edf3, NULL, attribname(att), att->namelen,
                       attribvalue(att), att->valuelen)) {
@@ -1066,15 +1064,11 @@ addmodified(FILE * edf3, attrib1_t * attrib, record_t * first)
      */
     while (a != NULL || num_b <= tot_b) {
         /* ignore operational attrs */
-        if (num_b <= tot_b &&
-            (stricmp(attribname(b), "modifytimestamp") == 0 ||
-             stricmp(attribname(b), "modifiersname") == 0)) {
+        if ( num_b <= tot_b && slapi_attr_is_last_mod(attribname(b)) ){
             b = attribnext(b); num_b++;
             continue;
         }
-        if (a != NULL &&
-            (stricmp(a->name, "modifytimestamp") == 0 ||
-             stricmp(a->name, "modifiersname") == 0)) {
+        if (a != NULL && slapi_attr_is_last_mod(a->name)) {
             a = a->next;
             continue;
         }
-- 
1.9.3