dirsrvtests/tickets/ticket47553_single_aci_test.py | 1187 +++++++++++++++++++++
ldap/schema/01core389.ldif | 1
ldap/servers/plugins/acl/acl.c | 250 +++-
ldap/servers/plugins/acl/acl.h | 47
ldap/servers/plugins/acl/aclanom.c | 40
ldap/servers/plugins/acl/acleffectiverights.c | 7
ldap/servers/plugins/acl/aclparse.c | 117 +-
ldap/servers/plugins/acl/aclplugin.c | 2
ldap/servers/plugins/acl/aclutil.c | 5
ldap/servers/slapd/back-ldbm/ldbm_modrdn.c | 59 -
ldap/servers/slapd/config.c | 17
ldap/servers/slapd/libglobs.c | 33
ldap/servers/slapd/proto-slap.h | 2
ldap/servers/slapd/slap.h | 2
ldap/servers/slapd/slapi-plugin.h | 7
15 files changed, 1678 insertions(+), 98 deletions(-)
New commits:
commit dc17d63368fed59048ea7730dadb89ea623e7bab
Author: Thierry bordaz (tbordaz) <tbordaz(a)redhat.com>
Date: Fri Feb 28 14:24:53 2014 +0100
Ticket 47553: Enhance ACIs to have more control over MODRDN operations
Bug Description:
This is enhancement of the access control related to the MODDN/MODRDN operation.
We need the ability to specify a 'source tree' and a 'destination
tree' where MODDN/MODRDN
are allowed/denied.
It is a requirement that 'source' and 'destination' targets can be
specified in the same ACI.
Fix Description:
Please refer to
http://directory.fedoraproject.org/wiki/Access_control_on_trees_specified....
Mainly:
- parse new aci syntax
- support of new 'moddn' right in aci
- Check 'moddn' right in ldbm_back_modrdn
- filter source/destination when looking for matching ressources
- support of 'moddn' rights in GER control
- new nsslapd-moddn-aci compatibility config flag
- aci logging summary (display source/destination)
- Skip readonly backend
https://fedorahosted.org/389/ticket/47553
Reviewed by: Rich Megginson (Big thanks Rich !!)
Platforms tested: F17
Flag Day: no
Doc impact: yes
diff --git a/dirsrvtests/tickets/ticket47553_single_aci_test.py
b/dirsrvtests/tickets/ticket47553_single_aci_test.py
new file mode 100644
index 0000000..87f8bb3
--- /dev/null
+++ b/dirsrvtests/tickets/ticket47553_single_aci_test.py
@@ -0,0 +1,1187 @@
+'''
+Created on Nov 7, 2013
+
+@author: tbordaz
+'''
+import os
+import sys
+import time
+import ldap
+import logging
+import socket
+import time
+import logging
+import pytest
+import re
+from lib389 import DirSrv, Entry, tools
+from lib389.tools import DirSrvTools
+from lib389._constants import *
+from lib389.properties import *
+from constants import *
+from lib389._constants import REPLICAROLE_MASTER
+
+logging.getLogger(__name__).setLevel(logging.DEBUG)
+log = logging.getLogger(__name__)
+
+#
+# important part. We can deploy Master1 and Master2 on different versions
+#
+installation1_prefix = None
+installation2_prefix = None
+
+TEST_REPL_DN = "cn=test_repl, %s" % SUFFIX
+
+STAGING_CN = "staged user"
+PRODUCTION_CN = "accounts"
+EXCEPT_CN = "excepts"
+
+STAGING_DN = "cn=%s,%s" % (STAGING_CN, SUFFIX)
+PRODUCTION_DN = "cn=%s,%s" % (PRODUCTION_CN, SUFFIX)
+PROD_EXCEPT_DN = "cn=%s,%s" % (EXCEPT_CN, PRODUCTION_DN)
+
+STAGING_PATTERN = "cn=%s*,%s" % (STAGING_CN[:2], SUFFIX)
+PRODUCTION_PATTERN = "cn=%s*,%s" % (PRODUCTION_CN[:2], SUFFIX)
+BAD_STAGING_PATTERN = "cn=bad*,%s" % (SUFFIX)
+BAD_PRODUCTION_PATTERN = "cn=bad*,%s" % (SUFFIX)
+
+BIND_CN = "bind_entry"
+BIND_DN = "cn=%s,%s" % (BIND_CN, SUFFIX)
+BIND_PW = "password"
+
+NEW_ACCOUNT = "new_account"
+MAX_ACCOUNTS = 20
+
+CONFIG_MODDN_ACI_ATTR = "nsslapd-moddn-aci"
+
+class TopologyMaster1Master2(object):
+ def __init__(self, master1, master2):
+ master1.open()
+ self.master1 = master1
+
+ master2.open()
+ self.master2 = master2
+
+
+#(a)pytest.fixture(scope="module")
+def topology(request):
+ '''
+ This fixture is used to create a replicated topology for the 'module'.
+ The replicated topology is MASTER1 <-> Master2.
+ At the beginning, It may exists a master2 instance and/or a master2 instance.
+ It may also exists a backup for the master1 and/or the master2.
+
+ Principle:
+ If master1 instance exists:
+ restart it
+ If master2 instance exists:
+ restart it
+ If backup of master1 AND backup of master2 exists:
+ create or rebind to master1
+ create or rebind to master2
+
+ restore master1 from backup
+ restore master2 from backup
+ else:
+ Cleanup everything
+ remove instances
+ remove backups
+ Create instances
+ Initialize replication
+ Create backups
+ '''
+ global installation1_prefix
+ global installation2_prefix
+
+ # allocate master1 on a given deployement
+ master1 = DirSrv(verbose=False)
+ if installation1_prefix:
+ args_instance[SER_DEPLOYED_DIR] = installation1_prefix
+
+ # Args for the master1 instance
+ args_instance[SER_HOST] = HOST_MASTER_1
+ args_instance[SER_PORT] = PORT_MASTER_1
+ args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_1
+ args_master = args_instance.copy()
+ master1.allocate(args_master)
+
+ # allocate master1 on a given deployement
+ master2 = DirSrv(verbose=False)
+ if installation2_prefix:
+ args_instance[SER_DEPLOYED_DIR] = installation2_prefix
+
+ # Args for the consumer instance
+ args_instance[SER_HOST] = HOST_MASTER_2
+ args_instance[SER_PORT] = PORT_MASTER_2
+ args_instance[SER_SERVERID_PROP] = SERVERID_MASTER_2
+ args_master = args_instance.copy()
+ master2.allocate(args_master)
+
+
+ # Get the status of the backups
+ backup_master1 = master1.checkBackupFS()
+ backup_master2 = master2.checkBackupFS()
+
+ # Get the status of the instance and restart it if it exists
+ instance_master1 = master1.exists()
+ if instance_master1:
+ master1.stop(timeout=10)
+ master1.start(timeout=10)
+
+ instance_master2 = master2.exists()
+ if instance_master2:
+ master2.stop(timeout=10)
+ master2.start(timeout=10)
+
+ if backup_master1 and backup_master2:
+ # The backups exist, assuming they are correct
+ # we just re-init the instances with them
+ if not instance_master1:
+ master1.create()
+ # Used to retrieve configuration information (dbdir, confdir...)
+ master1.open()
+
+ if not instance_master2:
+ master2.create()
+ # Used to retrieve configuration information (dbdir, confdir...)
+ master2.open()
+
+ # restore master1 from backup
+ master1.stop(timeout=10)
+ master1.restoreFS(backup_master1)
+ master1.start(timeout=10)
+
+ # restore master2 from backup
+ master2.stop(timeout=10)
+ master2.restoreFS(backup_master2)
+ master2.start(timeout=10)
+ else:
+ # We should be here only in two conditions
+ # - This is the first time a test involve master-consumer
+ # so we need to create everything
+ # - Something weird happened (instance/backup destroyed)
+ # so we discard everything and recreate all
+
+ # Remove all the backups. So even if we have a specific backup file
+ # (e.g backup_master) we clear all backups that an instance my have created
+ if backup_master1:
+ master1.clearBackupFS()
+ if backup_master2:
+ master2.clearBackupFS()
+
+ # Remove all the instances
+ if instance_master1:
+ master1.delete()
+ if instance_master2:
+ master2.delete()
+
+ # Create the instances
+ master1.create()
+ master1.open()
+ master2.create()
+ master2.open()
+
+ #
+ # Now prepare the Master-Consumer topology
+ #
+ # First Enable replication
+ master1.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER,
replicaId=REPLICAID_MASTER_1)
+ master2.replica.enableReplication(suffix=SUFFIX, role=REPLICAROLE_MASTER,
replicaId=REPLICAID_MASTER_2)
+
+ # Initialize the supplier->consumer
+
+ properties = {RA_NAME: r'meTo_$host:$port',
+ RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
+ RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
+ RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
+ RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
+ repl_agreement = master1.agreement.create(suffix=SUFFIX, host=master2.host,
port=master2.port, properties=properties)
+
+ if not repl_agreement:
+ log.fatal("Fail to create a replica agreement")
+ sys.exit(1)
+
+ log.debug("%s created" % repl_agreement)
+
+ properties = {RA_NAME: r'meTo_$host:$port',
+ RA_BINDDN: defaultProperties[REPLICATION_BIND_DN],
+ RA_BINDPW: defaultProperties[REPLICATION_BIND_PW],
+ RA_METHOD: defaultProperties[REPLICATION_BIND_METHOD],
+ RA_TRANSPORT_PROT: defaultProperties[REPLICATION_TRANSPORT]}
+ master2.agreement.create(suffix=SUFFIX, host=master1.host, port=master1.port,
properties=properties)
+
+ master1.agreement.init(SUFFIX, HOST_MASTER_2, PORT_MASTER_2)
+ master1.waitForReplInit(repl_agreement)
+
+ # Check replication is working fine
+ master1.add_s(Entry((TEST_REPL_DN, {
+ 'objectclass': "top
person".split(),
+ 'sn': 'test_repl',
+ 'cn': 'test_repl'})))
+ loop = 0
+ while loop <= 10:
+ try:
+ ent = master2.getEntry(TEST_REPL_DN, ldap.SCOPE_BASE,
"(objectclass=*)")
+ break
+ except ldap.NO_SUCH_OBJECT:
+ time.sleep(1)
+ loop += 1
+
+ # Time to create the backups
+ master1.stop(timeout=10)
+ master1.backupfile = master1.backupFS()
+ master1.start(timeout=10)
+
+ master2.stop(timeout=10)
+ master2.backupfile = master2.backupFS()
+ master2.start(timeout=10)
+
+ #
+ # Here we have two instances master and consumer
+ # with replication working. Either coming from a backup recovery
+ # or from a fresh (re)init
+ # Time to return the topology
+ return TopologyMaster1Master2(master1, master2)
+
+
+
+def _bind_manager(topology):
+ topology.master1.log.info("Bind as %s " % DN_DM)
+ topology.master1.simple_bind_s(DN_DM, PASSWORD)
+
+def _bind_normal(topology):
+ # bind as bind_entry
+ topology.master1.log.info("Bind as %s" % BIND_DN)
+ topology.master1.simple_bind_s(BIND_DN, BIND_PW)
+
+def _moddn_aci_deny_tree(topology, mod_type=None, target_from=STAGING_DN,
target_to=PROD_EXCEPT_DN):
+ '''
+ It denies the access moddn_to in cn=except,cn=accounts,SUFFIX
+ '''
+ assert mod_type != None
+
+ ACI_TARGET_FROM = ""
+ ACI_TARGET_TO = ""
+ if target_from:
+ ACI_TARGET_FROM = "(target_from = \"ldap:///%s\")" %
(target_from)
+ if target_to:
+ ACI_TARGET_TO = "(target_to = \"ldap:///%s\")" %
(target_to)
+
+ ACI_ALLOW = "(version 3.0; acl \"Deny MODDN to prod_except\";
deny (moddn)"
+ ACI_SUBJECT = " userdn = \"ldap:///%s\";)" % BIND_DN
+ ACI_BODY = ACI_TARGET_TO + ACI_TARGET_FROM + ACI_ALLOW + ACI_SUBJECT
+ mod = [(mod_type, 'aci', ACI_BODY)]
+ #topology.master1.modify_s(SUFFIX, mod)
+ topology.master1.log.info("Add a DENY aci under %s " % PROD_EXCEPT_DN)
+ topology.master1.modify_s(PROD_EXCEPT_DN, mod)
+
+def _moddn_aci_staging_to_production(topology, mod_type=None, target_from=STAGING_DN,
target_to=PRODUCTION_DN):
+ assert mod_type != None
+
+
+ ACI_TARGET_FROM = ""
+ ACI_TARGET_TO = ""
+ if target_from:
+ ACI_TARGET_FROM = "(target_from = \"ldap:///%s\")" %
(target_from)
+ if target_to:
+ ACI_TARGET_TO = "(target_to = \"ldap:///%s\")" %
(target_to)
+
+ ACI_ALLOW = "(version 3.0; acl \"MODDN from staging to
production\"; allow (moddn)"
+ ACI_SUBJECT = " userdn = \"ldap:///%s\";)" % BIND_DN
+ ACI_BODY = ACI_TARGET_FROM + ACI_TARGET_TO + ACI_ALLOW + ACI_SUBJECT
+ mod = [(mod_type, 'aci', ACI_BODY)]
+ topology.master1.modify_s(SUFFIX, mod)
+
+def _moddn_aci_from_production_to_staging(topology, mod_type=None):
+ assert mod_type != None
+
+ ACI_TARGET = "(target_from = \"ldap:///%s\") (target_to =
\"ldap:///%s\")" % (PRODUCTION_DN, STAGING_DN)
+ ACI_ALLOW = "(version 3.0; acl \"MODDN from production to
staging\"; allow (moddn)"
+ ACI_SUBJECT = " userdn = \"ldap:///%s\";)" % BIND_DN
+ ACI_BODY = ACI_TARGET + ACI_ALLOW + ACI_SUBJECT
+ mod = [(mod_type, 'aci', ACI_BODY)]
+ topology.master1.modify_s(SUFFIX, mod)
+
+
+def test_ticket47553_init(topology):
+ """
+ Creates
+ - a staging DIT
+ - a production DIT
+ - add accounts in staging DIT
+ - enable ACL logging (commented for performance reason)
+
+ """
+
+ topology.master1.log.info("\n\n######################### INITIALIZATION
######################\n")
+
+ # entry used to bind with
+ topology.master1.log.info("Add %s" % BIND_DN)
+ topology.master1.add_s(Entry((BIND_DN, {
+ 'objectclass': "top
person".split(),
+ 'sn': BIND_CN,
+ 'cn': BIND_CN,
+ 'userpassword': BIND_PW})))
+
+ # DIT for staging
+ topology.master1.log.info("Add %s" % STAGING_DN)
+ topology.master1.add_s(Entry((STAGING_DN, {
+ 'objectclass': "top
organizationalRole".split(),
+ 'cn': STAGING_CN,
+ 'description': "staging
DIT"})))
+
+ # DIT for production
+ topology.master1.log.info("Add %s" % PRODUCTION_DN)
+ topology.master1.add_s(Entry((PRODUCTION_DN, {
+ 'objectclass': "top
organizationalRole".split(),
+ 'cn': PRODUCTION_CN,
+ 'description': "production
DIT"})))
+
+ # DIT for production/except
+ topology.master1.log.info("Add %s" % PROD_EXCEPT_DN)
+ topology.master1.add_s(Entry((PROD_EXCEPT_DN, {
+ 'objectclass': "top
organizationalRole".split(),
+ 'cn': EXCEPT_CN,
+ 'description': "production
except DIT"})))
+
+ # enable acl error logging
+ #mod = [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', '128')]
+ #topology.master1.modify_s(DN_CONFIG, mod)
+ #topology.master2.modify_s(DN_CONFIG, mod)
+
+
+
+
+
+ # add dummy entries in the staging DIT
+ for cpt in range(MAX_ACCOUNTS):
+ name = "%s%d" % (NEW_ACCOUNT, cpt)
+ topology.master1.add_s(Entry(("cn=%s,%s" % (name, STAGING_DN), {
+ 'objectclass': "top
person".split(),
+ 'sn': name,
+ 'cn': name})))
+
+
+def test_ticket47553_add(topology):
+ '''
+ This test case checks that the ADD operation fails (no ADD aci on production)
+ '''
+
+ topology.master1.log.info("\n\n######################### ADD (should fail)
######################\n")
+
+ _bind_normal(topology)
+
+ #
+ # First try to add an entry in production => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to add %s" % PRODUCTION_DN)
+ name = "%s%d" % (NEW_ACCOUNT, 0)
+ topology.master1.add_s(Entry(("cn=%s,%s" % (name, PRODUCTION_DN), {
+ 'objectclass': "top
person".split(),
+ 'sn': name,
+ 'cn': name})))
+ assert 0 # this is an error, we should not be allowed to add an entry in
production
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+def test_ticket47553_delete(topology):
+ '''
+ This test case checks that the DEL operation fails (no 'delete' aci on
production)
+ '''
+
+ topology.master1.log.info("\n\n######################### DELETE (should fail)
######################\n")
+
+ _bind_normal(topology)
+ #
+ # Second try to delete an entry in staging => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to delete %s" % STAGING_DN)
+ name = "%s%d" % (NEW_ACCOUNT, 0)
+ topology.master1.delete_s("cn=%s,%s" % (name, STAGING_DN))
+ assert 0 # this is an error, we should not be allowed to add an entry in
production
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+
+def test_ticket47553_moddn_staging_prod_0(topology):
+ '''
+ This test case MOVE entry NEW_ACCOUNT0 from staging to prod
+ target_to/target_from: equality filter
+ '''
+
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(0) ######################\n")
+ _bind_normal(topology)
+
+ old_rdn = "cn=%s0" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+
+ # successfull MOD with the ACI
+ topology.master1.log.info("\n\n######################### MOVE to and from
equality filter ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD,
target_from=STAGING_DN, target_to=PRODUCTION_DN)
+ _bind_normal(topology)
+
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn, new_rdn,
new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+
+ # successfull MOD with the both ACI
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=STAGING_DN, target_to=PRODUCTION_DN)
+ _bind_normal(topology)
+
+
+def test_ticket47553_moddn_staging_prod_1(topology):
+ '''
+ This test case MOVE entry NEW_ACCOUNT1 from staging to prod
+ target_to/target_from: substring/equality filter
+ '''
+
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(1) ######################\n")
+ _bind_normal(topology)
+
+ old_rdn = "cn=%s1" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+
+ # successfull MOD with the ACI
+ topology.master1.log.info("\n\n######################### MOVE to substring/ from
equality filter ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD,
target_from=STAGING_DN, target_to=PRODUCTION_PATTERN)
+ _bind_normal(topology)
+
+
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn, new_rdn,
new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+
+ # successfull MOD with the both ACI
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=STAGING_DN, target_to=PRODUCTION_PATTERN)
+ _bind_normal(topology)
+
+def test_ticket47553_moddn_staging_prod_2(topology):
+ '''
+ This test case fails to MOVE entry NEW_ACCOUNT2 from staging to prod
+ because of bad pattern
+ '''
+
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(2) ######################\n")
+ _bind_normal(topology)
+
+ old_rdn = "cn=%s2" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+
+ # successfull MOD with the ACI
+ topology.master1.log.info("\n\n######################### MOVE to substring
(BAD)/ from equality filter ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD,
target_from=STAGING_DN, target_to=BAD_PRODUCTION_PATTERN)
+ _bind_normal(topology)
+
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+ # successfull MOD with the both ACI
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=STAGING_DN, target_to=BAD_PRODUCTION_PATTERN)
+ _bind_normal(topology)
+
+def test_ticket47553_moddn_staging_prod_3(topology):
+ '''
+ This test case MOVE entry NEW_ACCOUNT3 from staging to prod
+ target_to/target_from: equality/substring filter
+ '''
+
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(3) ######################\n")
+ _bind_normal(topology)
+
+ old_rdn = "cn=%s3" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+
+ # successfull MOD with the ACI
+ topology.master1.log.info("\n\n######################### MOVE to:equality filter
/ from substring filter ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD,
target_from=STAGING_PATTERN, target_to=PRODUCTION_DN)
+ _bind_normal(topology)
+
+
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn, new_rdn,
new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+
+ # successfull MOD with the both ACI
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=STAGING_PATTERN, target_to=PRODUCTION_DN)
+ _bind_normal(topology)
+
+def test_ticket47553_moddn_staging_prod_4(topology):
+ '''
+ This test case fails to MOVE entry NEW_ACCOUNT4 from staging to prod
+ because of bad pattern
+ '''
+
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(4) ######################\n")
+ _bind_normal(topology)
+
+ old_rdn = "cn=%s4" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+
+ # successfull MOD with the ACI
+ topology.master1.log.info("\n\n######################### MOVE to: equality
filter/ from: substring (BAD) ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD,
target_from=BAD_STAGING_PATTERN, target_to=PRODUCTION_DN)
+ _bind_normal(topology)
+
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+ # successfull MOD with the both ACI
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=BAD_STAGING_PATTERN, target_to=PRODUCTION_DN)
+ _bind_normal(topology)
+
+def test_ticket47553_moddn_staging_prod_5(topology):
+ '''
+ This test case MOVE entry NEW_ACCOUNT5 from staging to prod
+ target_to/target_from: substring/substring filter
+ '''
+
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(5) ######################\n")
+ _bind_normal(topology)
+
+ old_rdn = "cn=%s5" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+
+ # successfull MOD with the ACI
+ topology.master1.log.info("\n\n######################### MOVE to:substring
filter / from: substring filter ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD,
target_from=STAGING_PATTERN, target_to=PRODUCTION_PATTERN)
+ _bind_normal(topology)
+
+
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn, new_rdn,
new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+
+ # successfull MOD with the both ACI
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=STAGING_PATTERN, target_to=PRODUCTION_PATTERN)
+ _bind_normal(topology)
+
+def test_ticket47553_moddn_staging_prod_6(topology):
+ '''
+ This test case MOVE entry NEW_ACCOUNT6 from staging to prod
+ target_to/target_from: substring/<enmpty> filter
+ '''
+
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(6) ######################\n")
+ _bind_normal(topology)
+
+ old_rdn = "cn=%s6" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+
+ # successfull MOD with the ACI
+ topology.master1.log.info("\n\n######################### MOVE to:substring
filter / from: empty ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD, target_from=None,
target_to=PRODUCTION_PATTERN)
+ _bind_normal(topology)
+
+
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn, new_rdn,
new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+
+ # successfull MOD with the both ACI
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=None, target_to=PRODUCTION_PATTERN)
+ _bind_normal(topology)
+
+def test_ticket47553_moddn_staging_prod_7(topology):
+ '''
+ This test case MOVE entry NEW_ACCOUNT7 from staging to prod
+ target_to/target_from: <empty>/substring filter
+ '''
+
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(7) ######################\n")
+ _bind_normal(topology)
+
+ old_rdn = "cn=%s7" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+
+ # successfull MOD with the ACI
+ topology.master1.log.info("\n\n######################### MOVE to: empty/ from:
substring filter ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD,
target_from=STAGING_PATTERN, target_to=None)
+ _bind_normal(topology)
+
+
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn, new_rdn,
new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+
+ # successfull MOD with the both ACI
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=STAGING_PATTERN, target_to=None)
+ _bind_normal(topology)
+
+
+def test_ticket47553_moddn_staging_prod_8(topology):
+ '''
+ This test case MOVE entry NEW_ACCOUNT8 from staging to prod
+ target_to/target_from: <empty>/<empty> filter
+ '''
+
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(8) ######################\n")
+ _bind_normal(topology)
+
+ old_rdn = "cn=%s8" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+
+ # successfull MOD with the ACI
+ topology.master1.log.info("\n\n######################### MOVE to: empty/ from:
empty ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD, target_from=None,
target_to=None)
+ _bind_normal(topology)
+
+
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn, new_rdn,
new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+
+ # successfull MOD with the both ACI
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=None, target_to=None)
+ _bind_normal(topology)
+
+def test_ticket47553_moddn_staging_prod_9(topology):
+ '''
+ This test case disable the 'moddn' right so a MODDN requires a
'add' right
+ to be successfull.
+ It fails to MOVE entry NEW_ACCOUNT9 from staging to prod.
+ Add a 'add' right to prod.
+ Then it succeeds to MOVE NEW_ACCOUNT9 from staging to prod.
+
+ Then enable the 'moddn' right so a MODDN requires a 'moddn'
right
+ It fails to MOVE entry NEW_ACCOUNT10 from staging to prod.
+ Add a 'moddn' right to prod.
+ Then it succeeds to MOVE NEW_ACCOUNT10 from staging to prod.
+ '''
+
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(9) ######################\n")
+
+ _bind_normal(topology)
+ old_rdn = "cn=%s9" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+ ############################################
+ # Now do tests with no support of moddn aci
+ ############################################
+ topology.master1.log.info("Disable the moddn right" )
+ _bind_manager(topology)
+ mod = [(ldap.MOD_REPLACE, CONFIG_MODDN_ACI_ATTR, 'off')]
+ topology.master1.modify_s(DN_CONFIG, mod)
+
+ # Add the moddn aci that will not be evaluated because of the config flag
+ topology.master1.log.info("\n\n######################### MOVE to and from
equality filter ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD,
target_from=STAGING_DN, target_to=PRODUCTION_DN)
+ _bind_normal(topology)
+
+ # It will fail because it will test the ADD right
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+ # remove the moddn aci
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=STAGING_DN, target_to=PRODUCTION_DN)
+ _bind_normal(topology)
+
+ #
+ # add the 'add' right to the production DN
+ # Then do a successfull moddn
+ #
+ ACI_ALLOW = "(version 3.0; acl \"ADD rights to allow moddn\";
allow (add)"
+ ACI_SUBJECT = " userdn = \"ldap:///%s\";)" % BIND_DN
+ ACI_BODY = ACI_ALLOW + ACI_SUBJECT
+
+ _bind_manager(topology)
+ mod = [(ldap.MOD_ADD, 'aci', ACI_BODY)]
+ topology.master1.modify_s(PRODUCTION_DN, mod)
+ _bind_normal(topology)
+
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn, new_rdn,
new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+
+ _bind_manager(topology)
+ mod = [(ldap.MOD_DELETE, 'aci', ACI_BODY)]
+ topology.master1.modify_s(PRODUCTION_DN, mod)
+ _bind_normal(topology)
+
+
+ ############################################
+ # Now do tests with support of moddn aci
+ ############################################
+ topology.master1.log.info("Enable the moddn right" )
+ _bind_manager(topology)
+ mod = [(ldap.MOD_REPLACE, CONFIG_MODDN_ACI_ATTR, 'on')]
+ topology.master1.modify_s(DN_CONFIG, mod)
+
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(10) ######################\n")
+
+ _bind_normal(topology)
+ old_rdn = "cn=%s10" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+ #
+ # add the 'add' right to the production DN
+ # Then do a failing moddn
+ #
+ ACI_ALLOW = "(version 3.0; acl \"ADD rights to allow moddn\";
allow (add)"
+ ACI_SUBJECT = " userdn = \"ldap:///%s\";)" % BIND_DN
+ ACI_BODY = ACI_ALLOW + ACI_SUBJECT
+
+ _bind_manager(topology)
+ mod = [(ldap.MOD_ADD, 'aci', ACI_BODY)]
+ topology.master1.modify_s(PRODUCTION_DN, mod)
+ _bind_normal(topology)
+
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+ _bind_manager(topology)
+ mod = [(ldap.MOD_DELETE, 'aci', ACI_BODY)]
+ topology.master1.modify_s(PRODUCTION_DN, mod)
+ _bind_normal(topology)
+
+ # Add the moddn aci that will be evaluated because of the config flag
+ topology.master1.log.info("\n\n######################### MOVE to and from
equality filter ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD,
target_from=STAGING_DN, target_to=PRODUCTION_DN)
+ _bind_normal(topology)
+
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn, new_rdn,
new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+
+ # remove the moddn aci
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=STAGING_DN, target_to=PRODUCTION_DN)
+ _bind_normal(topology)
+
+def test_ticket47553_moddn_prod_staging(topology):
+ '''
+ This test checks that we can move ACCOUNT11 from staging to prod
+ but not move back ACCOUNT11 from prod to staging
+ '''
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(11) ######################\n")
+
+ _bind_normal(topology)
+
+ old_rdn = "cn=%s11" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+
+ # successfull MOD with the ACI
+ topology.master1.log.info("\n\n######################### MOVE to and from
equality filter ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD,
target_from=STAGING_DN, target_to=PRODUCTION_DN)
+ _bind_normal(topology)
+
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn, new_rdn,
new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+
+
+ #
+ # Now check we can not move back the entry to staging
+ old_rdn = "cn=%s11" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, PRODUCTION_DN)
+ new_rdn = old_rdn
+ new_superior = STAGING_DN
+
+ try:
+ topology.master1.log.info("Try to move back MODDN %s -> %s,%s" %
(old_dn, new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+ # successfull MOD with the both ACI
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=STAGING_DN, target_to=PRODUCTION_DN)
+ _bind_normal(topology)
+
+
+def test_ticket47553_check_repl_M2_to_M1(topology):
+ '''
+ Checks that replication is still working M2->M1, using ACCOUNT12
+ '''
+
+ topology.master1.log.info("Bind as %s (M2)" % DN_DM)
+ topology.master2.simple_bind_s(DN_DM, PASSWORD)
+
+ rdn = "cn=%s12" % NEW_ACCOUNT
+ dn = "%s,%s" % (rdn, STAGING_DN)
+
+ # First wait for the ACCOUNT19 entry being replicated on M2
+ loop = 0
+ while loop <= 10:
+ try:
+ ent = topology.master2.getEntry(dn, ldap.SCOPE_BASE,
"(objectclass=*)")
+ break
+ except ldap.NO_SUCH_OBJECT:
+ time.sleep(1)
+ loop += 1
+ assert loop <= 10
+
+
+ attribute = 'description'
+ tested_value = 'Hello world'
+ mod = [(ldap.MOD_ADD, attribute, tested_value)]
+ topology.master1.log.info("Update (M2) %s (%s)" % (dn, attribute))
+ topology.master2.modify_s(dn, mod)
+
+ loop = 0
+ while loop <= 10:
+ ent = topology.master1.getEntry(dn, ldap.SCOPE_BASE,
"(objectclass=*)")
+ assert ent != None
+ if ent.hasAttr(attribute) and (ent.getValue(attribute) == tested_value):
+ break
+
+ time.sleep(1)
+ loop += 1
+ assert loop < 10
+ topology.master1.log.info("Update %s (%s) replicated on M1" % (dn,
attribute))
+
+def test_ticket47553_moddn_staging_prod_except(topology):
+ '''
+ This test case MOVE entry NEW_ACCOUNT13 from staging to prod
+ but fails to move entry NEW_ACCOUNT14 from staging to prod_except
+ '''
+
+ topology.master1.log.info("\n\n######################### MOVE staging -> Prod
(13) ######################\n")
+ _bind_normal(topology)
+
+ old_rdn = "cn=%s13" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PRODUCTION_DN
+
+ #
+ # Try to rename without the apropriate ACI => INSUFFICIENT_ACCESS
+ #
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+
+ # successfull MOD with the ACI
+ topology.master1.log.info("\n\n######################### MOVE to and from
equality filter ######################\n")
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_ADD,
target_from=STAGING_DN, target_to=PRODUCTION_DN)
+ _moddn_aci_deny_tree(topology, mod_type=ldap.MOD_ADD)
+ _bind_normal(topology)
+
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn, new_rdn,
new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+
+ #
+ # Now try to move an entry under except
+ #
+ topology.master1.log.info("\n\n######################### MOVE staging ->
Prod/Except (14) ######################\n")
+ old_rdn = "cn=%s14" % NEW_ACCOUNT
+ old_dn = "%s,%s" % (old_rdn, STAGING_DN)
+ new_rdn = old_rdn
+ new_superior = PROD_EXCEPT_DN
+ try:
+ topology.master1.log.info("Try to MODDN %s -> %s,%s" % (old_dn,
new_rdn, new_superior))
+ topology.master1.rename_s(old_dn, new_rdn, newsuperior=new_superior)
+ assert 0
+ except AssertionError:
+ topology.master1.log.info("Exception (not really expected exception but that
is fine as it fails to rename)")
+ except Exception as e:
+ topology.master1.log.info("Exception (expected): %s" %
type(e).__name__)
+ assert isinstance(e, ldap.INSUFFICIENT_ACCESS)
+
+ # successfull MOD with the both ACI
+ _bind_manager(topology)
+ _moddn_aci_staging_to_production(topology, mod_type=ldap.MOD_DELETE,
target_from=STAGING_DN, target_to=PRODUCTION_DN)
+ _moddn_aci_deny_tree(topology, mod_type=ldap.MOD_DELETE)
+ _bind_normal(topology)
+
+def test_ticket47553_final(topology):
+ topology.master1.stop(timeout=10)
+ topology.master2.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 '(a)pytest.fixture' line before
'topology' function.
+ - set the installation prefix
+ - run this program
+ '''
+ global installation1_prefix
+ global installation2_prefix
+ installation1_prefix = '/home/tbordaz/install'
+ installation2_prefix = '/home/tbordaz/install'
+
+ topo = topology(True)
+ topo.master1.log.info("\n\n######################### Ticket 47553
######################\n")
+ test_ticket47553_init(topo)
+
+
+ # Check that without appropriate aci we are not allowed to add/delete
+ test_ticket47553_add(topo)
+ test_ticket47553_delete(topo)
+
+ # tests the ACI as equality/substring filter
+ test_ticket47553_moddn_staging_prod_0(topo)
+ test_ticket47553_moddn_staging_prod_1(topo)
+ test_ticket47553_moddn_staging_prod_2(topo)
+ test_ticket47553_moddn_staging_prod_3(topo)
+ test_ticket47553_moddn_staging_prod_4(topo)
+ test_ticket47553_moddn_staging_prod_5(topo)
+
+ # tests the ACI with undefined 'target_to'/'target_from'
+ test_ticket47553_moddn_staging_prod_6(topo)
+ test_ticket47553_moddn_staging_prod_7(topo)
+ test_ticket47553_moddn_staging_prod_8(topo)
+
+ # Check we can control the behavior with nsslapd-moddn-aci
+ test_ticket47553_moddn_staging_prod_9(topo)
+
+ # Check we can move entry 'from' -> 'to' but not 'to'
-> 'from'
+ test_ticket47553_moddn_prod_staging(topo)
+
+ # check replication is still working
+ test_ticket47553_check_repl_M2_to_M1(topo)
+
+ # check DENY rule is working
+ test_ticket47553_moddn_staging_prod_except(topo)
+
+ test_ticket47553_final(topo)
+
+
+
+
+if __name__ == '__main__':
+ run_isolated()
+
diff --git a/ldap/schema/01core389.ldif b/ldap/schema/01core389.ldif
index 5779d30..04043c1 100644
--- a/ldap/schema/01core389.ldif
+++ b/ldap/schema/01core389.ldif
@@ -295,6 +295,7 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2300 NAME
'nsslapd-connection-nocanon' D
attributeTypes: ( 2.16.840.1.113730.3.1.2301 NAME 'nsslapd-plugin-logging' DESC
'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
attributeTypes: ( 2.16.840.1.113730.3.1.2302 NAME 'nsslapd-listen-backlog-size'
DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
attributeTypes: ( 2.16.840.1.113730.3.1.2303 NAME 'nsslapd-ignore-time-skew' DESC
'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
+attributeTypes: ( 2.16.840.1.113730.3.1.2305 NAME 'nsslapd-moddn-aci' DESC
'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE X-ORIGIN 'Netscape Directory Server' )
#
# objectclasses
#
diff --git a/ldap/servers/plugins/acl/acl.c b/ldap/servers/plugins/acl/acl.c
index 3c1db36..f8b854c 100644
--- a/ldap/servers/plugins/acl/acl.c
+++ b/ldap/servers/plugins/acl/acl.c
@@ -282,7 +282,7 @@ acl_access_allowed(
if ( access & (ACLPB_SLAPI_ACL_WRITE_ADD | ACLPB_SLAPI_ACL_WRITE_DEL) ) {
access |= SLAPI_ACL_WRITE;
}
-
+
n_edn = slapi_entry_get_ndn ( e );
e_sdn = slapi_entry_get_sdn ( e );
@@ -290,7 +290,7 @@ acl_access_allowed(
/* No one, even the rootdn should be allowed to write to the database */
/* jcm: ReadOnly only applies to the public backends, the private ones */
/* (the DSEs) should still be writable for configuration. */
- if ( access & ( SLAPI_ACL_WRITE | SLAPI_ACL_ADD | SLAPI_ACL_DELETE )) {
+ if ( access & ( SLAPI_ACL_WRITE | SLAPI_ACL_ADD | SLAPI_ACL_DELETE |
SLAPI_ACL_MODDN)) {
int be_readonly, privateBackend;
Slapi_Backend *be;
@@ -351,6 +351,20 @@ acl_access_allowed(
TNF_PROBE_0_DEBUG(acl_aclpbinit_start,"ACL","");
acl_init_aclpb ( pb, aclpb, clientDn, 0 );
TNF_PROBE_0_DEBUG(acl_aclpbinit_end,"ACL","");
+
+ if ( access & SLAPI_ACL_MODDN) {
+ /* with MODDN, the entry 'e' is the destination entry.
+ * This destination entry will be checked against a possible
'target_to'
+ * The source entry will be checked against a possible
'target_from'.
+ * 'e' will be given down to acl_resource_match_aci, so no pb on
this side.
+ * For the source entry we need to keep its dn into the aclpb.
+ */
+ entry_address *old_addr;
+ slapi_pblock_get(pb, SLAPI_TARGET_ADDRESS, &old_addr);
+ aclpb->aclpb_moddn_source_sdn = old_addr->sdn;
+
+ }
+
/* Here we mean if "I am trying to add/delete "myself" to a group,
etc." We
* basically just want to see if the value matches the DN of the user that
@@ -767,48 +781,95 @@ print_access_control_summary( char *source, int ret_val, char
*clientDn,
if ( aclpb->aclpb_authorization_sdn != NULL ) {
- proxy_user =
- (char *)(slapi_sdn_get_ndn(aclpb->aclpb_authorization_sdn)?
- slapi_sdn_get_ndn(aclpb->aclpb_authorization_sdn):
- null_user);
-
- slapi_log_error(loglevel, plugin_name,
- "conn=%" NSPRIu64 " op=%d (%s): %s %s on entry(%s).attr(%s) to proxy
(%s)"
- ": %s\n",
- (long long unsigned int)op->o_connid, op->o_opid,
- source,
- access_status,
- right,
- edn,
- attr ? attr: "NULL",
- proxy_user,
- acl_info[0] ? acl_info : access_reason);
+ proxy_user =
+ (char
*)(slapi_sdn_get_ndn(aclpb->aclpb_authorization_sdn)?
+ slapi_sdn_get_ndn(aclpb->aclpb_authorization_sdn):
+ null_user);
+ if (strcasecmp(right, access_str_moddn) == 0) {
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%" NSPRIu64 " op=%d (%s): %s
%s on entry(%s).attr(%s) [from %s] to proxy (%s)"
+ ": %s\n",
+ (long long unsigned int)op->o_connid,
op->o_opid,
+ source,
+ access_status,
+ right,
+ edn,
+ attr ? attr: "NULL",
+ aclpb->aclpb_moddn_source_sdn ?
slapi_sdn_get_dn(aclpb->aclpb_moddn_source_sdn) : "NULL", /* from entry -
stored in pblock */
+ proxy_user,
+ acl_info[0] ? acl_info : access_reason);
+
+ } else {
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%" NSPRIu64 " op=%d (%s): %s
%s on entry(%s).attr(%s) to proxy (%s)"
+ ": %s\n",
+ (long long unsigned int)op->o_connid,
op->o_opid,
+ source,
+ access_status,
+ right,
+ edn,
+ attr ? attr: "NULL",
+ proxy_user,
+ acl_info[0] ? acl_info : access_reason);
+ }
} else {
- proxy_user = null_user;
- slapi_log_error(loglevel, plugin_name,
- "conn=%" NSPRIu64 " op=%d (%s): %s %s on entry(%s).attr(%s) to proxy
(%s)"
- ": %s\n",
- (long long unsigned int)op->o_connid, op->o_opid,
- source,
- access_status,
- right,
- edn,
- attr ? attr: "NULL",
- proxy_user,
- acl_info[0] ? acl_info : access_reason);
+ proxy_user = null_user;
+ if (strcasecmp(right, access_str_moddn) == 0) {
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%" NSPRIu64 " op=%d (%s): %s
%s on entry(%s).attr(%s) [from %s] to proxy (%s)"
+ ": %s\n",
+ (long long unsigned int)op->o_connid,
op->o_opid,
+ source,
+ access_status,
+ right,
+ edn,
+ attr ? attr: "NULL",
+ aclpb->aclpb_moddn_source_sdn ?
slapi_sdn_get_dn(aclpb->aclpb_moddn_source_sdn) : "NULL", /* from entry -
stored in pblock */
+ proxy_user,
+ acl_info[0] ? acl_info : access_reason);
+
+ } else {
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%" NSPRIu64 " op=%d (%s): %s
%s on entry(%s).attr(%s) to proxy (%s)"
+ ": %s\n",
+ (long long unsigned int)op->o_connid,
op->o_opid,
+ source,
+ access_status,
+ right,
+ edn,
+ attr ? attr: "NULL",
+ proxy_user,
+ acl_info[0] ? acl_info : access_reason);
+ }
}
- } else{
- slapi_log_error(loglevel, plugin_name,
- "conn=%" NSPRIu64 " op=%d (%s): %s %s on entry(%s).attr(%s) to
%s"
- ": %s\n",
- (long long unsigned int)op->o_connid, op->o_opid,
- source,
- access_status,
- right,
- edn,
- attr ? attr: "NULL",
- real_user,
- acl_info[0] ? acl_info : access_reason);
+ } else {
+ if (strcasecmp(right, access_str_moddn) == 0) {
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%" NSPRIu64 " op=%d (%s): %s %s on
entry(%s).attr(%s) [from %s] to %s"
+ ": %s\n",
+ (long long unsigned int)op->o_connid, op->o_opid,
+ source,
+ access_status,
+ right,
+ edn,
+ attr ? attr: "NULL",
+ aclpb->aclpb_moddn_source_sdn ?
slapi_sdn_get_dn(aclpb->aclpb_moddn_source_sdn) : "NULL", /* from entry -
stored in pblock */
+ real_user,
+ acl_info[0] ? acl_info : access_reason);
+
+ } else {
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%" NSPRIu64 " op=%d (%s): %s %s on
entry(%s).attr(%s) to %s"
+ ": %s\n",
+ (long long unsigned int)op->o_connid, op->o_opid,
+ source,
+ access_status,
+ right,
+ edn,
+ attr ? attr: "NULL",
+ real_user,
+ acl_info[0] ? acl_info : access_reason);
+ }
}
@@ -2006,7 +2067,7 @@ acl__scan_for_acis(Acl_PBlock *aclpb, int *err)
* None.
*
**************************************************************************/
-#define ACL_RIGHTS_TARGETATTR_NOT_NEEDED ( SLAPI_ACL_ADD | SLAPI_ACL_DELETE |
SLAPI_ACL_PROXY)
+#define ACL_RIGHTS_TARGETATTR_NOT_NEEDED ( SLAPI_ACL_ADD | SLAPI_ACL_DELETE |
SLAPI_ACL_PROXY | SLAPI_ACL_MODDN)
static int
acl__resource_match_aci( Acl_PBlock *aclpb, aci_t *aci, int skip_attrEval, int
*a_matched)
{
@@ -2093,7 +2154,108 @@ acl__resource_match_aci( Acl_PBlock *aclpb, aci_t *aci, int
skip_attrEval, int *
matches = (dn_matched ? ACL_TRUE: ACL_FALSE);
}
}
-
+
+ /* No need to look further */
+ if (matches == ACL_FALSE) {
+ goto acl__resource_match_aci_EXIT;
+ }
+
+ if ((aci->aci_type & ACI_TARGET_MODDN) && (res_right &
SLAPI_ACL_MODDN) && (aclpb->aclpb_curr_entry_sdn)) {
+ char *avaType;
+ struct berval *avaValue;
+ char logbuf[1024];
+
+ /* We are evaluating the moddn permission.
+ * The aci contains target_to and target_from
+ *
+ * target_to filter must be checked against the resource ndn that was
stored in
+ * aclpb->aclpb_curr_entry_sdn
+ *
+ * target_from filter must be check against the entry ndn that is in
aclpb->aclpb_moddn_source_sdn
+ * (sdn was stored in the pblock)
+ */
+ if (aci->target_to) {
+ f = aci->target_to;
+ dn_matched = ACL_TRUE;
+
+ /* Now check if the filter is a simple or substring filter */
+ if (aci->aci_type & ACI_TARGET_MODDN_TO_PATTERN) {
+ /* This is a filter with substring
+ * e.g. ldap:///uid=*,cn=accounts,dc=example,dc=com
+ */
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "moddn
target_to substring: %s\n",
+ slapi_filter_to_string(f, logbuf,
sizeof(logbuf)));
+ if ((rv = acl_match_substring(f, (char *) res_ndn, 0 /*
match suffix */)) != ACL_TRUE) {
+ dn_matched = ACL_FALSE;
+ if (rv == ACL_ERR) {
+ slapi_log_error(SLAPI_LOG_ACL,
plugin_name,
+
"acl__resource_match_aci:pattern err\n");
+ matches = ACL_FALSE;
+ goto acl__resource_match_aci_EXIT;
+ }
+ }
+ } else {
+ /* This is a filter without substring
+ * e.g. ldap:///cn=accounts,dc=example,dc=com
+ */
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "moddn
target_to: %s\n",
+ slapi_filter_to_string(f, logbuf,
sizeof(logbuf)));
+ slapi_filter_get_ava(f, &avaType, &avaValue);
+
+ if (!slapi_dn_issuffix(res_ndn, avaValue->bv_val)) {
+ dn_matched = ACL_FALSE;
+ }
+
+ }
+ if (aci->aci_type & ACI_TARGET_NOT) {
+ matches = (dn_matched ? ACL_FALSE : ACL_TRUE);
+ } else {
+ matches = (dn_matched ? ACL_TRUE : ACL_FALSE);
+ }
+ } /* target_to */
+
+ if ((matches == ACL_TRUE) && (aci->target_from) &&
aclpb->aclpb_moddn_source_sdn) {
+ f = aci->target_from;
+ dn_matched = ACL_TRUE;
+ slapi_filter_get_ava ( f, &avaType, &avaValue );
+
+ /* Now check if the filter is a simple or substring filter */
+ if (aci->aci_type & ACI_TARGET_MODDN_FROM_PATTERN) {
+ /* This is a filter with substring
+ * e.g. ldap:///uid=*,cn=accounts,dc=example,dc=com
+ */
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "moddn
target_from substring: %s\n",
+ slapi_filter_to_string(f, logbuf,
sizeof(logbuf)));
+ if ((rv = acl_match_substring(f, (char *)
slapi_sdn_get_dn(aclpb->aclpb_moddn_source_sdn), 0 /* match suffix */)) != ACL_TRUE) {
+ dn_matched = ACL_FALSE;
+ if (rv == ACL_ERR) {
+ slapi_log_error(SLAPI_LOG_ACL,
plugin_name,
+
"acl__resource_match_aci:pattern err\n");
+ matches = ACL_FALSE;
+ goto acl__resource_match_aci_EXIT;
+ }
+ }
+
+ } else {
+ /* This is a filter without substring
+ * e.g. ldap:///cn=accounts,dc=example,dc=com
+ */
+ slapi_log_error( SLAPI_LOG_ACL, plugin_name, "moddn
target_from: %s\n",
+ slapi_filter_to_string(f, logbuf,
sizeof(logbuf)));
+ if
(!slapi_dn_issuffix(slapi_sdn_get_dn(aclpb->aclpb_moddn_source_sdn),
avaValue->bv_val)) {
+ dn_matched = ACL_FALSE;
+ }
+
+ }
+
+ if (aci->aci_type & ACI_TARGET_NOT) {
+ matches = (dn_matched ? ACL_FALSE : ACL_TRUE);
+ } else {
+ matches = (dn_matched ? ACL_TRUE: ACL_FALSE);
+ }
+ }
+ }
+
/* No need to look further */
if (matches == ACL_FALSE) {
goto acl__resource_match_aci_EXIT;
diff --git a/ldap/servers/plugins/acl/acl.h b/ldap/servers/plugins/acl/acl.h
index 1a05e1f..2631ee9 100644
--- a/ldap/servers/plugins/acl/acl.h
+++ b/ldap/servers/plugins/acl/acl.h
@@ -117,6 +117,8 @@ static char* const aci_targetdn = "target";
static char* const aci_targetattr = "targetattr";
static char* const aci_targetattrfilters = "targattrfilters";
static char* const aci_targetfilter = "targetfilter";
+static char* const aci_target_to = "target_to";
+static char* const aci_target_from = "target_from";
static char* const LDAP_URL_prefix_core = "ldap://";
static char* const LDAPS_URL_prefix_core = "ldaps://";
@@ -132,6 +134,7 @@ static char* const access_str_delete = "delete";
static char* const access_str_add = "add";
static char* const access_str_selfwrite = "selfwrite";
static char* const access_str_proxy = "proxy";
+static char* const access_str_moddn = "moddn";
#define ACL_INIT_ATTR_ARRAY 5
@@ -256,24 +259,27 @@ typedef struct aci {
**
*/
-#define ACI_TARGET_MACRO_DN (int)0x000001
-#define ACI_TARGET_FILTER_MACRO_DN (int)0x000002
-#define ACI_TARGET_DN (int)0x000100 /* target has DN */
-#define ACI_TARGET_ATTR (int)0x000200 /* target is an attr */
-#define ACI_TARGET_PATTERN (int)0x000400 /* target has some patt */
-#define ACI_TARGET_FILTER (int)0x000800 /* target has a filter */
-#define ACI_ACLTXT (int)0x001000 /* ACI has text only */
-#define ACI_TARGET_NOT (int)0x002000 /* it's a != */
-#define ACI_TARGET_ATTR_NOT (int)0x004000 /* It's a != manager */
-#define ACI_TARGET_FILTER_NOT (int)0x008000 /* It's a != filter */
-#define ACI_UNUSED2 (int)0x010000 /* Unused */
-#define ACI_HAS_ALLOW_RULE (int)0x020000 /* allow (...) */
-#define ACI_HAS_DENY_RULE (int)0x040000 /* deny (...) */
-#define ACI_CONTAIN_NOT_USERDN (int)0x080000 /* userdn != blah */
-#define ACI_TARGET_ATTR_ADD_FILTERS (int)0x100000
-#define ACI_TARGET_ATTR_DEL_FILTERS (int)0x200000
-#define ACI_CONTAIN_NOT_GROUPDN (int)0x400000 /* groupdn != blah */
-#define ACI_CONTAIN_NOT_ROLEDN (int)0x800000
+#define ACI_TARGET_MACRO_DN (int)0x0000001
+#define ACI_TARGET_FILTER_MACRO_DN (int)0x0000002
+#define ACI_TARGET_DN (int)0x0000100 /* target has DN */
+#define ACI_TARGET_ATTR (int)0x0000200 /* target is an attr */
+#define ACI_TARGET_PATTERN (int)0x0000400 /* target has some patt */
+#define ACI_TARGET_FILTER (int)0x0000800 /* target has a filter */
+#define ACI_ACLTXT (int)0x0001000 /* ACI has text only */
+#define ACI_TARGET_NOT (int)0x0002000 /* it's a != */
+#define ACI_TARGET_ATTR_NOT (int)0x0004000 /* It's a != manager */
+#define ACI_TARGET_FILTER_NOT (int)0x0008000 /* It's a != filter */
+#define ACI_UNUSED2 (int)0x0010000 /* Unused */
+#define ACI_HAS_ALLOW_RULE (int)0x0020000 /* allow (...) */
+#define ACI_HAS_DENY_RULE (int)0x0040000 /* deny (...) */
+#define ACI_CONTAIN_NOT_USERDN (int)0x0080000 /* userdn != blah */
+#define ACI_TARGET_ATTR_ADD_FILTERS (int)0x0100000
+#define ACI_TARGET_ATTR_DEL_FILTERS (int)0x0200000
+#define ACI_CONTAIN_NOT_GROUPDN (int)0x0400000 /* groupdn != blah */
+#define ACI_CONTAIN_NOT_ROLEDN (int)0x0800000
+#define ACI_TARGET_MODDN (int)0x1000000
+#define ACI_TARGET_MODDN_FROM_PATTERN (int)0x2000000
+#define ACI_TARGET_MODDN_TO_PATTERN (int)0x4000000
int aci_access;
@@ -320,6 +326,8 @@ typedef struct aci {
struct slapi_filter *targetFilter; /* Target has a filter */
Targetattrfilter **targetAttrAddFilters;
Targetattrfilter **targetAttrDelFilters;
+ Slapi_Filter *target_to; /* Target is the
destination DN of moddn */
+ Slapi_Filter *target_from; /* Target is the source
DN of moddn */
char *aclName; /* ACL name */
struct ACLListHandle *aci_handle; /*handle of the ACL */
aciMacro *aci_macro;
@@ -520,6 +528,9 @@ struct acl_pblock {
Slapi_Entry *aclpb_filter_test_entry; /* Scratch entry */
aci_t *aclpb_curr_aci;
char *aclpb_Evalattr; /* The last attr evaluated */
+
+ /* Source entry (MODDN) */
+ Slapi_DN *aclpb_moddn_source_sdn; /* This is a
pointer into the pb, do not free it */
/* Plist and eval info */
ACLEvalHandle_t *aclpb_acleval; /* acleval handle for evaluation */
diff --git a/ldap/servers/plugins/acl/aclanom.c b/ldap/servers/plugins/acl/aclanom.c
index 1e68f22..d46b498 100644
--- a/ldap/servers/plugins/acl/aclanom.c
+++ b/ldap/servers/plugins/acl/aclanom.c
@@ -544,18 +544,36 @@ aclanom_match_profile (Slapi_PBlock *pb, struct acl_pblock *aclpb,
Slapi_Entry *
if ( result == LDAP_SUCCESS) {
const char *aci_ndn;
aci_ndn = slapi_sdn_get_ndn (acl_anom_profile->anom_targetinfo[i].anom_target);
-
- slapi_log_error(loglevel, plugin_name,
- "conn=%" NSPRIu64 " op=%d: Allow access on entry(%s).attr(%s) to
anonymous: acidn=\"%s\"\n",
- (long long unsigned int)op->o_connid, op->o_opid,
- ndn,
- attr ? attr:"NULL",
- aci_ndn);
+ if (access & SLAPI_ACL_MODDN) {
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%" NSPRIu64 " op=%d: Allow
access on entry(%s).attr(%s) (from %s) to anonymous: acidn=\"%s\"\n",
+ (long long unsigned int)op->o_connid,
op->o_opid,
+ ndn,
+ attr ? attr:"NULL",
+ aclpb->aclpb_moddn_source_sdn ?
slapi_sdn_get_dn(aclpb->aclpb_moddn_source_sdn) : "NULL",
+ aci_ndn);
+
+ } else {
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%" NSPRIu64 " op=%d: Allow
access on entry(%s).attr(%s) to anonymous: acidn=\"%s\"\n",
+ (long long unsigned int)op->o_connid,
op->o_opid,
+ ndn,
+ attr ? attr:"NULL",
+ aci_ndn);
+ }
} else {
- slapi_log_error(loglevel, plugin_name,
- "conn=%" NSPRIu64 " op=%d: Deny access on entry(%s).attr(%s) to
anonymous\n",
- (long long unsigned int)op->o_connid, op->o_opid,
- ndn, attr ? attr:"NULL" );
+ if (access & SLAPI_ACL_MODDN) {
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%" NSPRIu64 " op=%d: Deny
access on entry(%s).attr(%s) (from %s) to anonymous\n",
+ (long long unsigned int)op->o_connid,
op->o_opid,
+ ndn, attr ? attr:"NULL" ,
+ aclpb->aclpb_moddn_source_sdn ?
slapi_sdn_get_dn(aclpb->aclpb_moddn_source_sdn) : "NULL");
+ } else {
+ slapi_log_error(loglevel, plugin_name,
+ "conn=%" NSPRIu64 " op=%d: Deny
access on entry(%s).attr(%s) to anonymous\n",
+ (long long unsigned int)op->o_connid,
op->o_opid,
+ ndn, attr ? attr:"NULL" );
+ }
}
}
diff --git a/ldap/servers/plugins/acl/acleffectiverights.c
b/ldap/servers/plugins/acl/acleffectiverights.c
index dc22451..b9e2055 100644
--- a/ldap/servers/plugins/acl/acleffectiverights.c
+++ b/ldap/servers/plugins/acl/acleffectiverights.c
@@ -481,6 +481,13 @@ _ger_get_entry_rights (
}
slapi_rdn_free ( &rdn );
+ if (acl_access_allowed(gerpb, e, NULL, NULL, SLAPI_ACL_MODDN) == LDAP_SUCCESS)
{
+ slapi_log_error (SLAPI_LOG_ACL, plugin_name,
+ "_ger_get_entry_rights: SLAPI_ACL_MODDN %s\n", slapi_entry_get_ndn (e) );
+ /* n - rename e */
+ entryrights |= SLAPI_ACL_MODDN;
+ _append_gerstr(gerstr, gerstrsize, gerstrcap, "n", NULL);
+ }
if ( entryrights == 0 )
{
_append_gerstr(gerstr, gerstrsize, gerstrcap, "none", NULL);
diff --git a/ldap/servers/plugins/acl/aclparse.c b/ldap/servers/plugins/acl/aclparse.c
index 9c26fc7..ae4f2d5 100644
--- a/ldap/servers/plugins/acl/aclparse.c
+++ b/ldap/servers/plugins/acl/aclparse.c
@@ -254,6 +254,9 @@ __aclp__parse_aci(char *str, aci_t *aci_item, char **errbuf)
int targetdnlen = strlen (aci_targetdn);
int tfilterlen = strlen(aci_targetfilter);
int targetattrfilterslen = strlen(aci_targetattrfilters);
+ int target_to_len = strlen(aci_target_to);
+ int target_from_len = strlen(aci_target_from);
+ PRBool is_target_to;
__acl_strip_leading_space( &str );
@@ -348,7 +351,72 @@ __aclp__parse_aci(char *str, aci_t *aci_item, char **errbuf)
/* save the filter string */
aci_item->targetFilterStr = tmpstr;
- } else if (strncmp(str, aci_targetdn, targetdnlen) == 0) {
+ } else if ((strncmp(str, aci_target_to, target_to_len) == 0) || (strncmp(str,
aci_target_from, target_from_len) == 0)) {
+ /* This is important to make this test before aci_targetdn
+ * because aci_targetdn also match aci_target_to/aci_target_from
+ * */
+ char *tstr = NULL;
+ size_t LDAP_URL_prefix_len = 0;
+ size_t tmplen = 0;
+
+ /* Keep a copy of the target attr */
+ type = ACI_TARGET_MODDN;
+ if (strncmp(str, aci_target_to, target_to_len) == 0) {
+ if (aci_item->target_to) {
+ return (ACL_SYNTAX_ERR);
+ }
+ is_target_to = PR_TRUE;
+ } else {
+ if (aci_item->target_from) {
+ return (ACL_SYNTAX_ERR);
+ }
+ is_target_to = PR_FALSE;
+ }
+ if ( (s = strstr( str, "!=" )) != NULL ) {
+ type |= ACI_TARGET_NOT;
+ strncpy(s, " ", 1);
+ }
+ if ( (s = strchr( str, '=' )) != NULL ) {
+ value = s + 1;
+ __acl_strip_leading_space(&value);
+ __acl_strip_trailing_space(value);
+ len = strlen ( value );
+ /* strip double quotes */
+ if (*value == '"' && value[len-1] == '"') {
+ value[len-1] = '\0';
+ value++;
+ }
+ __acl_strip_leading_space(&value);
+ } else {
+ return ( ACL_SYNTAX_ERR );
+ }
+ if (0 ==
+ strncasecmp(value, LDAP_URL_prefix, strlen(LDAP_URL_prefix))) {
+ LDAP_URL_prefix_len = strlen(LDAP_URL_prefix);
+ } else if (0 == strncasecmp(value, LDAPS_URL_prefix,
+ strlen(LDAPS_URL_prefix))) {
+ LDAP_URL_prefix_len = strlen(LDAPS_URL_prefix);
+ } else {
+ return ( ACL_SYNTAX_ERR );
+ }
+
+ value += LDAP_URL_prefix_len;
+ rv = slapi_dn_normalize_case_ext(value, 0, &tmpstr, &tmplen);
+ if (rv < 0) {
+ return ACL_SYNTAX_ERR;
+ } else if (rv == 0) { /* value passed in; not null terminated */
+ *(tmpstr + tmplen) = '\0';
+ }
+
+ /* Now prepare the filter */
+ if (strncmp(str, aci_target_to, target_to_len) == 0) {
+ tstr = slapi_ch_smprintf("(%s=%s)",
aci_target_to, tmpstr);
+ } else {
+ tstr = slapi_ch_smprintf("(%s=%s)",
aci_target_from, tmpstr);
+ }
+ f = slapi_str2filter ( tstr );
+ slapi_ch_free_string ( &tstr );
+ } else if (strncmp(str, aci_targetdn, targetdnlen) == 0) {
char *tstr = NULL;
size_t LDAP_URL_prefix_len = 0;
size_t tmplen = 0;
@@ -407,7 +475,7 @@ __aclp__parse_aci(char *str, aci_t *aci_item, char **errbuf)
f = slapi_str2filter ( tstr );
}
slapi_ch_free_string ( &tstr );
- } else {
+ } else {
/* did start with a 't' but was not a recognsied keyword */
return(ACL_SYNTAX_ERR);
}
@@ -419,7 +487,7 @@ __aclp__parse_aci(char *str, aci_t *aci_item, char **errbuf)
*/
if (f == NULL) {
/* The following types require a filter to have been created */
- if (type & ACI_TARGET_DN)
+ if (type & (ACI_TARGET_DN | ACI_TARGET_MODDN))
return ACL_TARGET_FILTER_ERR;
else if (type & ACI_TARGET_FILTER)
return ACL_TARGETFILTER_ERR;
@@ -427,16 +495,23 @@ __aclp__parse_aci(char *str, aci_t *aci_item, char **errbuf)
int filterChoice;
filterChoice = slapi_filter_get_choice ( f );
- if ( (type & ACI_TARGET_DN) &&
+ if ( (type & (ACI_TARGET_DN | ACI_TARGET_MODDN)) &&
( filterChoice == LDAP_FILTER_PRESENT)) {
slapi_log_error(SLAPI_LOG_ACL, plugin_name,
"acl__parse_aci: Unsupported filter type:%d\n", filterChoice);
return(ACL_SYNTAX_ERR);
} else if (( filterChoice == LDAP_FILTER_SUBSTRINGS) &&
(type & ACI_TARGET_DN)) {
- type &= ~ACI_TARGET_DN;
+ type &= ~ACI_TARGET_DN;
type |= ACI_TARGET_PATTERN;
- }
+ } else if (( filterChoice == LDAP_FILTER_SUBSTRINGS) &&
+ (type & ACI_TARGET_MODDN)) {
+ if (is_target_to) {
+ type |= ACI_TARGET_MODDN_TO_PATTERN;
+ } else {
+ type |= ACI_TARGET_MODDN_FROM_PATTERN;
+ }
+ }
}
if ((type & ACI_TARGET_DN) ||
@@ -459,7 +534,29 @@ __aclp__parse_aci(char *str, aci_t *aci_item, char **errbuf)
} else {
aci_item->targetFilter = f;
}
- }
+ } else if (type & ACI_TARGET_MODDN) {
+ if (is_target_to) {
+ if (aci_item->target_to) {
+ /* There is something already. ERROR */
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Multiple targets (target_to) in the
ACL syntax\n");
+ slapi_filter_free(f, 1);
+ return(ACL_SYNTAX_ERR);
+ } else {
+ aci_item->target_to = f;
+ }
+ } else {
+ if (aci_item->target_from) {
+ /* There is something already. ERROR */
+ slapi_log_error(SLAPI_LOG_ACL, plugin_name,
+ "Multiple targets (target_from) in the ACL syntax\n");
+ slapi_filter_free(f, 1);
+ return(ACL_SYNTAX_ERR);
+ } else {
+ aci_item->target_from = f;
+ }
+ }
+ }
break; /* 't' */
default:
/* Here the keyword did not start with 'v' ot 't' so error */
@@ -1346,6 +1443,8 @@ static int get_acl_rights_as_int( char * strValue)
return (SLAPI_ACL_SELF | SLAPI_ACL_WRITE);
else if (strcasecmp (strValue, "all") == 0 )
return SLAPI_ACL_ALL;
+ else if (strcasecmp (strValue, "moddn") == 0)
+ return SLAPI_ACL_MODDN;
else
return -1; /* error */
}
@@ -1389,7 +1488,9 @@ acl_access2str(int access)
return access_str_write;
} else if (access & SLAPI_ACL_PROXY ) {
return access_str_proxy;
- }
+ } else if (access & SLAPI_ACL_MODDN) {
+ return access_str_moddn;
+ }
return NULL;
}
diff --git a/ldap/servers/plugins/acl/aclplugin.c b/ldap/servers/plugins/acl/aclplugin.c
index a5ce149..ce87327 100644
--- a/ldap/servers/plugins/acl/aclplugin.c
+++ b/ldap/servers/plugins/acl/aclplugin.c
@@ -393,7 +393,7 @@ acl_access_allowed_main ( Slapi_PBlock *pb, Slapi_Entry *e, char
**attrs,
/* generate the appropriate error message */
if ( ( rc != LDAP_SUCCESS ) && errbuf &&
( ACLPLUGIN_ACCESS_GET_EFFECTIVE_RIGHTS != flags ) &&
- ( access & ( SLAPI_ACL_WRITE | SLAPI_ACL_ADD | SLAPI_ACL_DELETE ))) {
+ ( access & ( SLAPI_ACL_WRITE | SLAPI_ACL_ADD | SLAPI_ACL_DELETE | SLAPI_ACL_MODDN
))) {
char *edn = slapi_entry_get_dn ( e );
diff --git a/ldap/servers/plugins/acl/aclutil.c b/ldap/servers/plugins/acl/aclutil.c
index 1b8bc12..e865a95 100644
--- a/ldap/servers/plugins/acl/aclutil.c
+++ b/ldap/servers/plugins/acl/aclutil.c
@@ -475,7 +475,10 @@ acl_gen_err_msg(int access, char *edn, char *attr, char **errbuf)
} else if ( access & SLAPI_ACL_DELETE ) {
line = PR_smprintf(
"Insufficient 'delete' privilege to delete the entry
'%s'.\n",edn);
- }
+ } else if ( access & SLAPI_ACL_MODDN ) {
+ line = PR_smprintf(
+ "Insufficient 'moddn' privilege to move an entry to
'%s'.\n",edn);
+ }
aclutil_str_append(errbuf, line );
if (line) {
diff --git a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
index 09f1cea..f528b07 100644
--- a/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
+++ b/ldap/servers/slapd/back-ldbm/ldbm_modrdn.c
@@ -121,6 +121,7 @@ ldbm_back_modrdn( Slapi_PBlock *pb )
int opreturn = 0;
int free_modrdn_existing_entry = 0;
int not_an_error = 0;
+ int support_moddn_aci;
/* sdn & parentsdn need to be initialized before "goto *_return" */
slapi_sdn_init(&dn_newdn);
@@ -143,6 +144,11 @@ ldbm_back_modrdn( Slapi_PBlock *pb )
return( -1 );
}
+ /* In case of support of 'moddn' permission in aci, this permission will
+ * be tested rather than the SLAPI_ACL_ADD
+ */
+ support_moddn_aci = config_get_moddn_aci();
+
/* dblayer_txn_init needs to be called before "goto error_return" */
dblayer_txn_init(li,&txn);
/* the calls to perform searches require the parent txn if any
@@ -594,12 +600,22 @@ ldbm_back_modrdn( Slapi_PBlock *pb )
}
else
{
- ldap_result_code= plugin_call_acl_plugin (pb,
newparententry->ep_entry, NULL, NULL, SLAPI_ACL_ADD, ACLPLUGIN_ACCESS_DEFAULT,
&errbuf );
- if ( ldap_result_code != LDAP_SUCCESS )
- {
- ldap_result_message= errbuf;
- LDAPDebug( LDAP_DEBUG_TRACE, "No access to new
superior.\n", 0, 0, 0 );
- goto error_return;
+ if (support_moddn_aci) {
+ /* aci permission requires 'moddn' right to allow a MODDN */
+ ldap_result_code = plugin_call_acl_plugin(pb,
newparententry->ep_entry, NULL, NULL, SLAPI_ACL_MODDN, ACLPLUGIN_ACCESS_DEFAULT,
&errbuf);
+ if (ldap_result_code != LDAP_SUCCESS) {
+ ldap_result_message = errbuf;
+ LDAPDebug(LDAP_DEBUG_TRACE, "No 'moddn' access
to new superior.\n", 0, 0, 0);
+ goto error_return;
+ }
+ } else {
+ /* aci permission requires 'add' right to allow a MODDN (old
style) */
+ ldap_result_code = plugin_call_acl_plugin (pb,
newparententry->ep_entry, NULL, NULL, SLAPI_ACL_ADD, ACLPLUGIN_ACCESS_DEFAULT,
&errbuf );
+ if (ldap_result_code != LDAP_SUCCESS) {
+ ldap_result_message = errbuf;
+ LDAPDebug(LDAP_DEBUG_TRACE, "No 'add' access
to new superior.\n", 0, 0, 0);
+ goto error_return;
+ }
}
}
@@ -641,16 +657,33 @@ ldbm_back_modrdn( Slapi_PBlock *pb )
ldap_result_code= LDAP_OPERATIONS_ERROR;
goto error_return;
}
-
+
/* JCMACL - Should be performed before the child check. */
/* JCMACL - Why is the check performed against the copy, rather than the
existing entry? */
- ldap_result_code = plugin_call_acl_plugin (pb, ec->ep_entry,
- NULL /*attr*/, NULL /*value*/, SLAPI_ACL_WRITE,
- ACLPLUGIN_ACCESS_MODRDN, &errbuf );
- if ( ldap_result_code != LDAP_SUCCESS )
{
- goto error_return;
- }
+ Slapi_RDN *new_rdn;
+ Slapi_RDN *old_rdn;
+
+ /* Taken from the entry */
+ old_rdn = slapi_entry_get_srdn(ec->ep_entry);
+
+ /* Taken from the request */
+ new_rdn = slapi_rdn_new();
+ slapi_sdn_get_rdn(&dn_newrdn, new_rdn);
+
+ /* Only if we change the RDN value, we need the write access to the entry
*/
+ if (slapi_rdn_compare(old_rdn, new_rdn)) {
+ ldap_result_code = plugin_call_acl_plugin(pb, ec->ep_entry,
+ NULL /*attr*/, NULL /*value*/, SLAPI_ACL_WRITE,
+ ACLPLUGIN_ACCESS_MODRDN, &errbuf);
+ }
+
+ slapi_rdn_free(&new_rdn);
+
+ if (ldap_result_code != LDAP_SUCCESS) {
+ goto error_return;
+ }
+ }
/* Set the new dn to the copy of the entry */
slapi_entry_set_sdn( ec->ep_entry, &dn_newdn );
diff --git a/ldap/servers/slapd/config.c b/ldap/servers/slapd/config.c
index 3067fdf..deecc66 100644
--- a/ldap/servers/slapd/config.c
+++ b/ldap/servers/slapd/config.c
@@ -225,12 +225,13 @@ slapd_bootstrap_config(const char *configdir)
char syntaxlogging[BUFSIZ];
char plugintracking[BUFSIZ];
char dn_validate_strict[BUFSIZ];
+ char moddn_aci[BUFSIZ];
Slapi_DN plug_dn;
workpath[0] = loglevel[0] = maxdescriptors[0] = '\0';
val[0] = logenabled[0] = schemacheck[0] = syntaxcheck[0] = '\0';
syntaxlogging[0] = _localuser[0] = '\0';
- plugintracking [0] = dn_validate_strict[0] = '\0';
+ plugintracking [0] = dn_validate_strict[0] = moddn_aci[0] ='\0';
/* Convert LDIF to entry structures */
slapi_sdn_init_ndn_byref(&plug_dn, PLUGIN_BASE_DN);
@@ -460,6 +461,20 @@ slapd_bootstrap_config(const char *configdir)
CONFIG_PLUGIN_BINDDN_TRACKING_ATTRIBUTE, errorbuf);
}
}
+
+ /* see if we allow moddn aci */
+ if (!moddn_aci[0] &&
+ entry_has_attr_and_value(e, CONFIG_MODDN_ACI_ATTRIBUTE,
+ moddn_aci, sizeof(moddn_aci)))
+ {
+ if (config_set_moddn_aci(CONFIG_MODDN_ACI_ATTRIBUTE,
+ moddn_aci, errorbuf, CONFIG_APPLY)
+ != LDAP_SUCCESS)
+ {
+ LDAPDebug(LDAP_DEBUG_ANY, "%s: %s: %s\n", configfile,
+ CONFIG_MODDN_ACI_ATTRIBUTE, errorbuf);
+ }
+ }
/* see if we need to enable syntax checking */
if (!syntaxcheck[0] &&
diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
index 7abc3b0..06bfc92 100644
--- a/ldap/servers/slapd/libglobs.c
+++ b/ldap/servers/slapd/libglobs.c
@@ -243,6 +243,7 @@ slapi_onoff_t init_attrname_exceptions;
slapi_onoff_t init_return_exact_case;
slapi_onoff_t init_result_tweak;
slapi_onoff_t init_plugin_track;
+slapi_onoff_t init_moddn_aci;
slapi_onoff_t init_lastmod;
slapi_onoff_t init_readonly;
slapi_onoff_t init_accesscontrol;
@@ -841,6 +842,11 @@ static struct config_get_and_set {
NULL, 0,
(void**)&global_slapdFrontendConfig.plugin_track,
CONFIG_ON_OFF, NULL, &init_plugin_track},
+ {CONFIG_MODDN_ACI_ATTRIBUTE, config_set_moddn_aci,
+ NULL, 0,
+ (void**)&global_slapdFrontendConfig.moddn_aci,
+ CONFIG_ON_OFF, (ConfigGetFunc)config_get_moddn_aci,
+ &init_moddn_aci},
{CONFIG_ATTRIBUTE_NAME_EXCEPTION_ATTRIBUTE, config_set_attrname_exceptions,
NULL, 0,
(void**)&global_slapdFrontendConfig.attrname_exceptions,
@@ -1424,6 +1430,7 @@ FrontendConfig_init () {
init_schemamod = cfg->schemamod = LDAP_ON;
init_syntaxcheck = cfg->syntaxcheck = LDAP_OFF;
init_plugin_track = cfg->plugin_track = LDAP_OFF;
+ init_moddn_aci = cfg->moddn_aci = LDAP_ON;
init_syntaxlogging = cfg->syntaxlogging = LDAP_OFF;
init_dn_validate_strict = cfg->dn_validate_strict = LDAP_OFF;
init_ds4_compatible_schema = cfg->ds4_compatible_schema = LDAP_OFF;
@@ -3250,6 +3257,20 @@ config_set_plugin_tracking( const char *attrname, char *value, char
*errorbuf, i
}
int
+config_set_moddn_aci( const char *attrname, char *value, char *errorbuf, int apply ) {
+ int retVal = LDAP_SUCCESS;
+ slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
+
+ retVal = config_set_onoff ( attrname,
+ value,
+ &(slapdFrontendConfig->moddn_aci),
+ errorbuf,
+ apply);
+
+ return retVal;
+}
+
+int
config_set_security( const char *attrname, char *value, char *errorbuf, int apply ) {
int retVal = LDAP_SUCCESS;
slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
@@ -5062,6 +5083,18 @@ config_get_result_tweak() {
}
int
+config_get_moddn_aci() {
+ slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
+ int retVal;
+
+ CFG_ONOFF_LOCK_READ(slapdFrontendConfig);
+ retVal = (int)slapdFrontendConfig->moddn_aci;
+ CFG_ONOFF_UNLOCK_READ(slapdFrontendConfig);
+
+ return retVal;
+}
+
+int
config_get_security() {
slapdFrontendConfig_t *slapdFrontendConfig = getFrontendConfig();
int retVal;
diff --git a/ldap/servers/slapd/proto-slap.h b/ldap/servers/slapd/proto-slap.h
index 3aa2170..447e1d0 100644
--- a/ldap/servers/slapd/proto-slap.h
+++ b/ldap/servers/slapd/proto-slap.h
@@ -289,6 +289,7 @@ int config_set_pagedsizelimit( const char *attrname, char *value, char
*errorbuf
int config_set_lastmod( const char *attrname, char *value, char *errorbuf, int apply );
int config_set_nagle( const char *attrname, char *value, char *errorbuf, int apply );
int config_set_accesscontrol( const char *attrname, char *value, char *errorbuf, int
apply );
+int config_set_moddn_aci( const char *attrname, char *value, char *errorbuf, int apply
);
int config_set_security( const char *attrname, char *value, char *errorbuf, int apply );
int config_set_readonly( const char *attrname, char *value, char *errorbuf, int apply
);
int config_set_schemacheck( const char *attrname, char *value, char *errorbuf, int apply
);
@@ -470,6 +471,7 @@ int config_get_nagle();
int config_get_accesscontrol();
int config_get_return_exact_case();
int config_get_result_tweak();
+int config_get_moddn_aci();
int config_get_security();
int config_get_schemacheck();
int config_get_syntaxcheck();
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
index 6834181..f2ab9b3 100644
--- a/ldap/servers/slapd/slap.h
+++ b/ldap/servers/slapd/slap.h
@@ -2102,6 +2102,7 @@ typedef struct _slapdEntryPoints {
#define CONFIG_AUDITLOG_LIST_ATTRIBUTE "nsslapd-auditlog-list"
#define CONFIG_REWRITE_RFC1274_ATTRIBUTE "nsslapd-rewrite-rfc1274"
#define CONFIG_PLUGIN_BINDDN_TRACKING_ATTRIBUTE
"nsslapd-plugin-binddn-tracking"
+#define CONFIG_MODDN_ACI_ATTRIBUTE "nsslapd-moddn-aci"
#define CONFIG_CONFIG_ATTRIBUTE "nsslapd-config"
#define CONFIG_INSTDIR_ATTRIBUTE "nsslapd-instancedir"
@@ -2257,6 +2258,7 @@ typedef struct _slapdFrontendConfig {
char **include;
char **plugin;
slapi_onoff_t plugin_track;
+ slapi_onoff_t moddn_aci;
struct pw_scheme *pw_storagescheme;
slapi_onoff_t pwpolicy_local;
diff --git a/ldap/servers/slapd/slapi-plugin.h b/ldap/servers/slapd/slapi-plugin.h
index 2f24251..63baf21 100644
--- a/ldap/servers/slapd/slapi-plugin.h
+++ b/ldap/servers/slapd/slapi-plugin.h
@@ -237,7 +237,12 @@ NSPR_API(PRUint32) PR_fprintf(struct PRFileDesc* fd, const char *fmt,
...)
#define SLAPI_ACL_SELF 0x40
#define SLAPI_ACL_PROXY 0x80
#define SLAPI_ACL_ALL 0x7f
-
+/* Values 0x200 and 0x400 are booked (acl.h) by
+ * ACLPB_SLAPI_ACL_WRITE_ADD
+ * ACLPB_SLAPI_ACL_WRITE_DEL
+ */
+#define SLAPI_ACL_MODDN 0x0800
+
/*
* filter types