From 7ad3f7283e41f3d690626db5b38a9fb63fa8e005 Mon Sep 17 00:00:00 2001 From: Mark Reynolds 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