dirsrvtests/tickets/ticket47676_test.py | 485 ++++++++++++++++++++++++++
ldap/schema/01core389.ldif | 5
ldap/servers/plugins/replication/repl5_init.c | 98 +++++
ldap/servers/slapd/schema.c | 304 +++++++++++++++-
ldap/servers/slapd/slap.h | 36 +
ldap/servers/slapd/slapi-private.h | 3
6 files changed, 922 insertions(+), 9 deletions(-)
New commits:
commit c44ea92408afda6cca0ed6fcdc4968d222944b07
Author: Thierry bordaz (tbordaz) <tbordaz(a)redhat.com>
Date: Tue Feb 4 11:50:45 2014 +0100
Ticket 47676 : Replication of the schema fails 'master branch' -> 1.2.11 or
1.3.1
Bug Description:
Since
https://fedorahosted.org/389/ticket/47490 and
https://fedorahosted.org/389/ticket/47541 a supplier schema is
pushed to the consumer at the condition that the consumer schema is not a superset of
the supplier schema.
With
https://fedorahosted.org/389/ticket/47647, the objectclass 'print-uri'
has been removed from master.
Starting in 1.3.1, unhashed#user#password pseudo attribute as been removed from the
schema.
A consequence is that replication of the schema fails: master->1.2.11 and
master->1.3.1
Fix Description:
Replication plugin initialization (multimaster_start) creates the three following
entries
dn: cn=replSchema,cn=config
objectClass: top
objectClass: nsSchemaPolicy
cn: replSchema
dn: cn=consumerUpdatePolicy,cn=replSchema,cn=config
objectClass: top
objectClass: nsSchemaPolicy
cn: consumerUpdatePolicy
schemaUpdateObjectclassAccept: printer-uri-oid
schemaUpdateAttributeAccept: 2.16.840.1.113730.3.1.2110
schemaUpdateObjectclassReject: dummy-objectclass-name
schemaUpdateAttributeReject: dummy-attribute-name
dn: cn=supplierUpdatePolicy,cn=replSchema,cn=config
objectClass: top
objectClass: nsSchemaPolicy
cn: supplierUpdatePolicy
schemaUpdateObjectclassAccept: printer-uri-oid
schemaUpdateAttributeAccept: 2.16.840.1.113730.3.1.2110
schemaUpdateObjectclassReject: dummy-objectclass-name
schemaUpdateAttributeReject: dummy-attribute-name
schemaUpdateObjectclassAccept, schemaUpdateAttributeAccept,
schemaUpdateObjectclassReject and schemaUpdateAttributeReject
are optional multi-valued attribute.
The values are strings representing objectclass/attribute name or OID.
During a replication session, if the consumer schema needs to be updated (because
nsSchemaCSN differs) the checkings are:
On supplier side:
OBJECTCLASSES
For each objectclass OC in the consumer schema:
if it exists in 'cn=supplierUpdatePolicy,cn=replSchema,cn=config'
schemaUpdateObjectclassAccept: <oc_name or oc_oid>
then this OC is "accepted" without checking that
- OC exists in supplier schema
- supplier's OC >= consumer's OC
schemaUpdateObjectclassReject: <oc_name or oc_oid>
then the supplier schema is not pushed (rejected)
If none of these values exists, then it does the "normal" processing to
determine if the schema can be updated:
(if Supplier's OC < consumer's OC then schema is not pushed)
ATTRIBUTES
It does the same processing as above for each attribute, looking for values
schemaUpdateAttributeAccept and schemaUpdateAttributeReject
On consumer side:
OBJECTCLASSES
It does the same processing as above for each OC in the supplier schema, checking
against
entry 'cn=consumerUpdatePolicy,cn=replSchema,cn=config'
ATTRIBUTES
It does the same processing as above for each AT in the supplier schema, checking
against
entry 'cn=consumerUpdatePolicy,cn=replSchema,cn=config'
https://fedorahosted.org/389/ticket/47676
Reviewed by: Rich Megginson (Thanks Rich!)
Platforms tested: F17/F19(jenkins)
Flag Day: no
Doc impact: no
diff --git a/dirsrvtests/tickets/ticket47676_test.py
b/dirsrvtests/tickets/ticket47676_test.py
new file mode 100644
index 0000000..8ba5956
--- /dev/null
+++ b/dirsrvtests/tickets/ticket47676_test.py
@@ -0,0 +1,485 @@
+'''
+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
+
+SCHEMA_DN = "cn=schema"
+TEST_REPL_DN = "cn=test_repl, %s" % SUFFIX
+OC_NAME = 'OCticket47676'
+OC_OID_EXT = 2
+MUST = "(postalAddress $ postalCode)"
+MAY = "(member $ street)"
+
+OC2_NAME = 'OC2ticket47676'
+OC2_OID_EXT = 3
+MUST_2 = "(postalAddress $ postalCode)"
+MAY_2 = "(member $ street)"
+
+REPL_SCHEMA_POLICY_CONSUMER =
"cn=consumerUpdatePolicy,cn=replSchema,cn=config"
+REPL_SCHEMA_POLICY_SUPPLIER =
"cn=supplierUpdatePolicy,cn=replSchema,cn=config"
+
+OTHER_NAME = 'other_entry'
+MAX_OTHERS = 10
+
+BIND_NAME = 'bind_entry'
+BIND_DN = 'cn=%s, %s' % (BIND_NAME, SUFFIX)
+BIND_PW = 'password'
+
+ENTRY_NAME = 'test_entry'
+ENTRY_DN = 'cn=%s, %s' % (ENTRY_NAME, SUFFIX)
+ENTRY_OC = "top person %s" % OC_NAME
+
+BASE_OID = "1.2.3.4.5.6.7.8.9.10"
+
+def _oc_definition(oid_ext, name, must=None, may=None):
+ oid = "%s.%d" % (BASE_OID, oid_ext)
+ desc = 'To test ticket 47490'
+ sup = 'person'
+ if not must:
+ must = MUST
+ if not may:
+ may = MAY
+
+ new_oc = "( %s NAME '%s' DESC '%s' SUP %s AUXILIARY MUST %s MAY
%s )" % (oid, name, desc, sup, must, may)
+ return new_oc
+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 test_ticket47676_init(topology):
+ """
+ It adds
+ - Objectclass with MAY 'member'
+ - an entry ('bind_entry') with which we bind to test the
'SELFDN' operation
+ It deletes the anonymous aci
+
+ """
+
+
+ topology.master1.log.info("Add %s that allows 'member' attribute" %
OC_NAME)
+ new_oc = _oc_definition(OC_OID_EXT, OC_NAME, must = MUST, may = MAY)
+ topology.master1.addSchema('objectClasses', new_oc)
+
+
+ # 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_NAME,
+ 'cn': BIND_NAME,
+ 'userpassword': BIND_PW})))
+
+ # enable acl error logging
+ mod = [(ldap.MOD_REPLACE, 'nsslapd-errorlog-level', str(128+8192))] # ACL +
REPL
+ topology.master1.modify_s(DN_CONFIG, mod)
+ topology.master2.modify_s(DN_CONFIG, mod)
+
+ # add dummy entries
+ for cpt in range(MAX_OTHERS):
+ name = "%s%d" % (OTHER_NAME, cpt)
+ topology.master1.add_s(Entry(("cn=%s,%s" % (name, SUFFIX), {
+ 'objectclass': "top
person".split(),
+ 'sn': name,
+ 'cn': name})))
+
+def test_ticket47676_skip_oc_at(topology):
+ '''
+ This test ADD an entry on MASTER1 where 47676 is fixed. Then it checks that entry
is replicated
+ on MASTER2 (even if on MASTER2 47676 is NOT fixed). Then update on MASTER2.
+ If the schema has successfully been pushed, updating Master2 should succeed
+ '''
+ topology.master1.log.info("\n\n######################### ADD
######################\n")
+
+ # bind as 'cn=Directory manager'
+ topology.master1.log.info("Bind as %s and add the add the entry with specific
oc" % DN_DM)
+ topology.master1.simple_bind_s(DN_DM, PASSWORD)
+
+ # Prepare the entry with multivalued members
+ entry = Entry(ENTRY_DN)
+ entry.setValues('objectclass', 'top', 'person',
'OCticket47676')
+ entry.setValues('sn', ENTRY_NAME)
+ entry.setValues('cn', ENTRY_NAME)
+ entry.setValues('postalAddress', 'here')
+ entry.setValues('postalCode', '1234')
+ members = []
+ for cpt in range(MAX_OTHERS):
+ name = "%s%d" % (OTHER_NAME, cpt)
+ members.append("cn=%s,%s" % (name, SUFFIX))
+ members.append(BIND_DN)
+ entry.setValues('member', members)
+
+ topology.master1.log.info("Try to add Add %s should be successful" %
ENTRY_DN)
+ topology.master1.add_s(entry)
+
+ #
+ # Now check the entry as been replicated
+ #
+ topology.master2.simple_bind_s(DN_DM, PASSWORD)
+ topology.master1.log.info("Try to retrieve %s from Master2" % ENTRY_DN)
+ loop = 0
+ while loop <= 10:
+ try:
+ ent = topology.master2.getEntry(ENTRY_DN, ldap.SCOPE_BASE,
"(objectclass=*)")
+ break
+ except ldap.NO_SUCH_OBJECT:
+ time.sleep(2)
+ loop += 1
+ assert loop <= 10
+
+ # Now update the entry on Master2 (as DM because 47676 is possibly not fixed on M2)
+ topology.master1.log.info("Update %s on M2" % ENTRY_DN)
+ mod = [(ldap.MOD_REPLACE, 'description', 'test_add')]
+ topology.master2.modify_s(ENTRY_DN, mod)
+
+ topology.master1.simple_bind_s(DN_DM, PASSWORD)
+ loop = 0
+ while loop <= 10:
+ ent = topology.master1.getEntry(ENTRY_DN, ldap.SCOPE_BASE,
"(objectclass=*)")
+ if ent.hasAttr('description') and (ent.getValue('description') ==
'test_add'):
+ break
+ time.sleep(1)
+ loop += 1
+
+ assert ent.getValue('description') == 'test_add'
+
+def test_ticket47676_reject_action(topology):
+
+ topology.master1.log.info("\n\n######################### REJECT ACTION
######################\n")
+
+ topology.master1.simple_bind_s(DN_DM, PASSWORD)
+ topology.master2.simple_bind_s(DN_DM, PASSWORD)
+
+ # make master1 to refuse to push the schema if OC_NAME is present in consumer schema
+ mod = [(ldap.MOD_ADD, 'schemaUpdateObjectclassReject', '%s' %
(OC_NAME) )] # ACL + REPL
+ topology.master1.modify_s(REPL_SCHEMA_POLICY_SUPPLIER, mod)
+
+ # Restart is required to take into account that policy
+ topology.master1.stop(timeout=10)
+ topology.master1.start(timeout=10)
+
+ # Add a new OC on M1 so that schema CSN will change and M1 will try to push the
schema
+ topology.master1.log.info("Add %s on M1" % OC2_NAME)
+ new_oc = _oc_definition(OC2_OID_EXT, OC2_NAME, must = MUST, may = MAY)
+ topology.master1.addSchema('objectClasses', new_oc)
+
+ # Safety checking that the schema has been updated on M1
+ topology.master1.log.info("Check %s is in M1" % OC2_NAME)
+ ent = topology.master1.getEntry(SCHEMA_DN, ldap.SCOPE_BASE,
"(objectclass=*)", ["objectclasses"])
+ assert ent.hasAttr('objectclasses')
+ found = False
+ for objectclass in ent.getValues('objectclasses'):
+ if str(objectclass).find(OC2_NAME) >= 0:
+ found = True
+ break
+ assert found
+
+ # Do an update of M1 so that M1 will try to push the schema
+ topology.master1.log.info("Update %s on M1" % ENTRY_DN)
+ mod = [(ldap.MOD_REPLACE, 'description', 'test_reject')]
+ topology.master1.modify_s(ENTRY_DN, mod)
+
+ # Check the replication occured and so also M1 attempted to push the schema
+ topology.master1.log.info("Check updated %s on M2" % ENTRY_DN)
+ loop = 0
+ while loop <= 10:
+ ent = topology.master2.getEntry(ENTRY_DN, ldap.SCOPE_BASE,
"(objectclass=*)", ['description'])
+ if ent.hasAttr('description') and ent.getValue('description') ==
'test_reject':
+ # update was replicated
+ break
+ time.sleep(2)
+ loop += 1
+ assert loop <= 10
+
+ # Check that the schema has not been pushed
+ topology.master1.log.info("Check %s is not in M2" % OC2_NAME)
+ ent = topology.master2.getEntry(SCHEMA_DN, ldap.SCOPE_BASE,
"(objectclass=*)", ["objectclasses"])
+ assert ent.hasAttr('objectclasses')
+ found = False
+ for objectclass in ent.getValues('objectclasses'):
+ if str(objectclass).find(OC2_NAME) >= 0:
+ found = True
+ break
+ assert not found
+
+ topology.master1.log.info("\n\n######################### NO MORE REJECT ACTION
######################\n")
+
+ # make master1 to do no specific action on OC_NAME
+ mod = [(ldap.MOD_DELETE, 'schemaUpdateObjectclassReject', '%s' %
(OC_NAME) )] # ACL + REPL
+ topology.master1.modify_s(REPL_SCHEMA_POLICY_SUPPLIER, mod)
+
+ # Restart is required to take into account that policy
+ topology.master1.stop(timeout=10)
+ topology.master1.start(timeout=10)
+
+ # Do an update of M1 so that M1 will try to push the schema
+ topology.master1.log.info("Update %s on M1" % ENTRY_DN)
+ mod = [(ldap.MOD_REPLACE, 'description', 'test_no_more_reject')]
+ topology.master1.modify_s(ENTRY_DN, mod)
+
+ # Check the replication occured and so also M1 attempted to push the schema
+ topology.master1.log.info("Check updated %s on M2" % ENTRY_DN)
+ loop = 0
+ while loop <= 10:
+ ent = topology.master2.getEntry(ENTRY_DN, ldap.SCOPE_BASE,
"(objectclass=*)", ['description'])
+ if ent.hasAttr('description') and ent.getValue('description') ==
'test_no_more_reject':
+ # update was replicated
+ break
+ time.sleep(2)
+ loop += 1
+ assert loop <= 10
+
+ # Check that the schema has been pushed
+ topology.master1.log.info("Check %s is in M2" % OC2_NAME)
+ ent = topology.master2.getEntry(SCHEMA_DN, ldap.SCOPE_BASE,
"(objectclass=*)", ["objectclasses"])
+ assert ent.hasAttr('objectclasses')
+ found = False
+ for objectclass in ent.getValues('objectclasses'):
+ if str(objectclass).find(OC2_NAME) >= 0:
+ found = True
+ break
+ assert found
+
+def test_ticket47676_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 = None
+ installation2_prefix = None
+
+ topo = topology(True)
+ topo.master1.log.info("\n\n######################### Ticket 47676
######################\n")
+ test_ticket47676_init(topo)
+
+ test_ticket47676_skip_oc_at(topo)
+ test_ticket47676_reject_action(topo)
+
+ test_ticket47676_final(topo)
+
+
+
+
+if __name__ == '__main__':
+ run_isolated()
+
diff --git a/ldap/schema/01core389.ldif b/ldap/schema/01core389.ldif
index 7b27685..df8afc6 100644
--- a/ldap/schema/01core389.ldif
+++ b/ldap/schema/01core389.ldif
@@ -156,6 +156,10 @@ attributeTypes: ( 2.16.840.1.113730.3.1.2152 NAME
'nsds5ReplicaProtocolTimeout'
attributeTypes: ( 2.16.840.1.113730.3.1.2154 NAME 'nsds5ReplicaBackoffMin' 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.2155 NAME 'nsds5ReplicaBackoffMax' 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.2156 NAME 'nsslapd-sasl-max-buffer-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.2165 NAME 'schemaUpdateObjectclassAccept'
DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
X-ORIGIN 'Netscape Directory Server' )
+attributeTypes: ( 2.16.840.1.113730.3.1.2166 NAME 'schemaUpdateObjectclassReject'
DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
X-ORIGIN 'Netscape Directory Server' )
+attributeTypes: ( 2.16.840.1.113730.3.1.2167 NAME 'schemaUpdateAttributeAccept'
DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
X-ORIGIN 'Netscape Directory Server' )
+attributeTypes: ( 2.16.840.1.113730.3.1.2168 NAME 'schemaUpdateAttributeReject'
DESC 'Netscape defined attribute type' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
X-ORIGIN 'Netscape Directory Server' )
#
# objectclasses
#
@@ -167,6 +171,7 @@ objectClasses: ( 2.16.840.1.113730.3.2.110 NAME
'nsMappingTree' DESC 'Netscape d
objectClasses: ( 2.16.840.1.113730.3.2.104 NAME 'nsContainer' DESC 'Netscape
defined objectclass' SUP top MUST ( CN ) X-ORIGIN 'Netscape Directory Server'
)
objectClasses: ( 2.16.840.1.113730.3.2.108 NAME 'nsDS5Replica' DESC 'Netscape
defined objectclass' SUP top MUST ( nsDS5ReplicaRoot $ nsDS5ReplicaId ) MAY (cn $
nsds5ReplicaCleanRUV $ nsds5ReplicaAbortCleanRUV $ nsDS5ReplicaType $ nsDS5ReplicaBindDN $
nsState $ nsDS5ReplicaName $ nsDS5Flags $ nsDS5Task $ nsDS5ReplicaReferral $
nsDS5ReplicaAutoReferral $ nsds5ReplicaPurgeDelay $ nsds5ReplicaTombstonePurgeInterval $
nsds5ReplicaChangeCount $ nsds5ReplicaLegacyConsumer $ nsds5ReplicaProtocolTimeout $
nsds5ReplicaBackoffMin $ nsds5ReplicaBackoffMax ) X-ORIGIN 'Netscape Directory
Server' )
objectClasses: ( 2.16.840.1.113730.3.2.113 NAME 'nsTombstone' DESC 'Netscape
defined objectclass' SUP top MAY ( nsParentUniqueId $ nscpEntryDN ) X-ORIGIN
'Netscape Directory Server' )
+objectClasses: ( 2.16.840.1.113730.3.2.115 NAME 'nsSchemaPolicy' DESC
'Netscape defined objectclass' SUP top MAY ( cn $ schemaUpdateObjectclassAccept $
schemaUpdateObjectclassReject $ schemaUpdateAttributeAccept $ schemaUpdateAttributeReject)
X-ORIGIN 'Netscape Directory Server' )
objectClasses: ( 2.16.840.1.113730.3.2.103 NAME 'nsDS5ReplicationAgreement' DESC
'Netscape defined objectclass' SUP top MUST ( cn ) MAY (
nsds5ReplicaCleanRUVNotified $ nsDS5ReplicaHost $ nsDS5ReplicaPort $
nsDS5ReplicaTransportInfo $ nsDS5ReplicaBindDN $ nsDS5ReplicaCredentials $
nsDS5ReplicaBindMethod $ nsDS5ReplicaRoot $ nsDS5ReplicatedAttributeList $
nsDS5ReplicatedAttributeListTotal $ nsDS5ReplicaUpdateSchedule $ nsds5BeginReplicaRefresh
$ description $ nsds50ruv $ nsruvReplicaLastModified $ nsds5ReplicaTimeout $
nsds5replicaChangesSentSinceStartup $ nsds5replicaLastUpdateEnd $
nsds5replicaLastUpdateStart $ nsds5replicaLastUpdateStatus $ nsds5replicaUpdateInProgress
$ nsds5replicaLastInitEnd $ nsds5ReplicaEnabled $ nsds5replicaLastInitStart $
nsds5replicaLastInitStatus $ nsds5debugreplicatimeout $ nsds5replicaBusyWaitTime $
nsds5ReplicaStripAttrs $ nsds5replicaSessionPauseTime $ nsds5ReplicaProtocolTimeout )
X-ORIGIN 'Netscape Directory Server' )
objectClasses: ( 2.16.840.1.113730.3.2.39 NAME 'nsslapdConfig' DESC 'Netscape
defined objectclass' SUP top MAY ( cn ) X-ORIGIN 'Netscape Directory Server'
)
objectClasses: ( 2.16.840.1.113730.3.2.317 NAME 'nsSaslMapping' DESC
'Netscape defined objectclass' SUP top MUST ( cn $ nsSaslMapRegexString $
nsSaslMapBaseDNTemplate $ nsSaslMapFilterTemplate ) MAY ( nsSaslMapPriority ) X-ORIGIN
'Netscape Directory Server' )
diff --git a/ldap/servers/plugins/replication/repl5_init.c
b/ldap/servers/plugins/replication/repl5_init.c
index ee923c9..56f01a1 100644
--- a/ldap/servers/plugins/replication/repl5_init.c
+++ b/ldap/servers/plugins/replication/repl5_init.c
@@ -644,7 +644,100 @@ check_for_ldif_dump(Slapi_PBlock *pb)
}
return return_value;
}
-
+/*
+ * If the entries do not exist, it create the entries of the schema replication policies
+ * returns 0 if success
+ */
+static int
+create_repl_schema_policy()
+{
+ /* DN part of this entry_string: no need to be optimized. */
+ char entry_string[1024];
+ Slapi_PBlock *pb;
+ Slapi_Entry *e ;
+ int return_value;
+ char *repl_schema_top, *repl_schema_supplier, *repl_schema_consumer;
+ char *default_supplier_policy = NULL;
+ char *default_consumer_policy = NULL;
+ int rc = 0;
+
+ slapi_schema_get_repl_entries(&repl_schema_top, &repl_schema_supplier,
&repl_schema_consumer, &default_supplier_policy, &default_consumer_policy);
+
+ /* Create cn=replSchema,cn=config */
+ PR_snprintf(entry_string, sizeof(entry_string), "dn: %s\nobjectclass:
top\nobjectclass: nsSchemaPolicy\ncn: replSchema\n", repl_schema_top);
+ e = slapi_str2entry(entry_string, 0);
+ pb = slapi_pblock_new();
+ slapi_add_entry_internal_set_pb(pb, e, NULL, /* controls */
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0 /* flags
*/);
+ slapi_add_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value);
+ if (return_value != LDAP_SUCCESS && return_value != LDAP_ALREADY_EXISTS)
{
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: unable
to "
+ "create configuration entry %s: %s\n",
repl_schema_top,
+ ldap_err2string(return_value));
+ rc = -1;
+ slapi_entry_free (e); /* The entry was not consumed */
+ goto done;
+ }
+ slapi_pblock_destroy(pb);
+
+ /* Create cn=supplierUpdatePolicy,cn=replSchema,cn=config */
+ PR_snprintf(entry_string, sizeof(entry_string), "dn: %s\nobjectclass:
top\nobjectclass: nsSchemaPolicy\ncn: supplierUpdatePolicy\n%s",
+ repl_schema_supplier,
+ default_supplier_policy ? default_supplier_policy : "");
+ e = slapi_str2entry(entry_string, 0);
+ pb = slapi_pblock_new();
+ slapi_add_entry_internal_set_pb(pb, e, NULL, /* controls */
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0 /* flags */);
+ slapi_add_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value);
+ if (return_value != LDAP_SUCCESS && return_value != LDAP_ALREADY_EXISTS)
{
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: unable
to "
+ "create configuration entry %s: %s\n",
repl_schema_supplier,
+ ldap_err2string(return_value));
+ rc = -1;
+ slapi_entry_free(e); /* The entry was not consumed */
+ goto done;
+ }
+ slapi_pblock_destroy(pb);
+
+ /* Create cn=consumerUpdatePolicy,cn=replSchema,cn=config */
+ PR_snprintf(entry_string, sizeof(entry_string), "dn: %s\nobjectclass:
top\nobjectclass: nsSchemaPolicy\ncn: consumerUpdatePolicy\n%s",
+ repl_schema_consumer,
+ default_consumer_policy ? default_consumer_policy : "");
+ e = slapi_str2entry(entry_string, 0);
+ pb = slapi_pblock_new();
+ slapi_add_entry_internal_set_pb(pb, e, NULL, /* controls */
+ repl_get_plugin_identity(PLUGIN_MULTIMASTER_REPLICATION), 0 /* flags */);
+ slapi_add_internal_pb(pb);
+ slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, &return_value);
+ if (return_value != LDAP_SUCCESS && return_value != LDAP_ALREADY_EXISTS)
{
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: unable
to "
+ "create configuration entry %s: %s\n",
repl_schema_consumer,
+ ldap_err2string(return_value));
+ rc = -1;
+ slapi_entry_free(e); /* The entry was not consumed */
+ goto done;
+ }
+ slapi_pblock_destroy(pb);
+ pb = NULL;
+
+
+
+ /* Load the policies of the schema replication */
+ if (slapi_schema_load_repl_policies()) {
+ slapi_log_error(SLAPI_LOG_FATAL, repl_plugin_name, "Warning: unable
to "
+ "load the schema replication policies\n");
+ rc = -1;
+ goto done;
+ }
+done:
+ if (pb) {
+ slapi_pblock_destroy(pb);
+ pb = NULL;
+ }
+ return rc;
+}
static PRBool is_ldif_dump = PR_FALSE;
@@ -715,6 +808,9 @@ multimaster_start( Slapi_PBlock *pb )
if (rc != 0)
goto out;
}
+ rc = create_repl_schema_policy();
+ if (rc != 0)
+ goto out;
/* check if the replica's data was reloaded offline and we need
to reinitialize replica's changelog. This should be done
diff --git a/ldap/servers/slapd/schema.c b/ldap/servers/slapd/schema.c
index 3ec76b4..f7170bf 100644
--- a/ldap/servers/slapd/schema.c
+++ b/ldap/servers/slapd/schema.c
@@ -86,6 +86,55 @@ static char *schema_user_defined_origin[] = {
NULL
};
+/* The policies for the replication of the schema are
+ * - base policy
+ * - extended policies
+ * Those policies are enforced when the server is acting as a supplier and
+ * when it is acting as a consumer
+ *
+ * Base policy:
+ * Supplier: before pushing the schema, the supplier checks that each
objectclass/attribute of
+ * the consumer schema is a subset of the objectclass/attribute of the
supplier schema
+ * Consumer: before accepting a schema (from replication), the consumer checks that
+ * each objectclass/attribute of the consumer schema is a subset of the
objectclass/attribute
+ * of the supplier schema
+ * Extended policies:
+ * They are stored in repl_schema_policy_t and specifies an "action" to be
taken
+ * for specific objectclass/attribute.
+ * Supplier: extended policies are stored in entry
"cn=supplierUpdatePolicy,cn=replSchema,cn=config"
+ * and uploaded in static variable: supplier_policy
+ * Before pushing the schema, for each objectclass/attribute defined in
supplier_policy:
+ * if its "action" is REPL_SCHEMA_UPDATE_ACCEPT_VALUE, it
is not checked that the
+ * attribute/objectclass of the consumer is a subset of the
attribute/objectclass
+ * of the supplier schema.
+ *
+ * if its "action" is REPL_SCHEMA_UPDATE_REJECT_VALUE and
the consumer schema contains
+ * attribute/objectclass, then schema is not pushed
+ *
+ * Consumer: extended policies are stored in entry
"cn=consumerUpdatePolicy,cn=replSchema,cn=config"
+ * and uploaded in static variable: consumer_policy
+ * before accepting a schema (from replication), for each
objectclass/attribute defined in
+ * consumer_policy:
+ * if its "action" is REPL_SCHEMA_UPDATE_ACCEPT_VALUE, it
is not checked that the
+ * attribute/objectclass of the consumer is a subset of the
attribute/objectclass
+ * of the supplier schema.
+ *
+ * if its "action" is REPL_SCHEMA_UPDATE_REJECT_VALUE and
the consumer schema contains
+ * attribute/objectclass, then schema is not accepted
+ *
+ */
+
+typedef struct schema_item {
+ int action; /* REPL_SCHEMA_UPDATE_ACCEPT_VALUE or REPL_SCHEMA_UPDATE_REJECT_VALUE
*/
+ char *name_or_oid;
+ struct schema_item *next;
+} schema_item_t;
+
+typedef struct repl_schema_policy {
+ schema_item_t *objectclasses;
+ schema_item_t *attributes;
+} repl_schema_policy_t;
+
/*
* pschemadse is based on the general implementation in dse
*/
@@ -145,7 +194,7 @@ static void schema_create_errormsg( char *errorbuf, size_t
errorbufsize,
#else
;
#endif
-static int schema_at_superset_check(struct asyntaxinfo *at_list1, struct asyntaxinfo
*at_list2, char *message);
+static int schema_at_superset_check(struct asyntaxinfo *at_list1, struct asyntaxinfo
*at_list2, char *message, int replica_role);
static int schema_at_superset_check_syntax_oids(char *oid1, char *oid2);
static int schema_at_superset_check_mr(struct asyntaxinfo *a1, struct asyntaxinfo *a2,
char *info);
static int parse_at_str(const char *input, struct asyntaxinfo **asipp, char *errorbuf,
size_t errorbufsize,
@@ -200,6 +249,13 @@ static const char *schema_errprefix_oc = "object class %s:
";
static const char *schema_errprefix_at = "attribute type %s: ";
static const char *schema_errprefix_generic = "%s: ";
+/* Defined the policies for the replication of the schema */
+static repl_schema_policy_t supplier_policy = {0};
+static repl_schema_policy_t consumer_policy = {0};
+static Slapi_RWLock *schema_policy_lock = NULL;
+static int schema_check_policy(int replica_role, int schema_item, char *name, char
*oid);
+static void schema_load_repl_policy(const char *dn, repl_schema_policy_t *replica);
+
/*
* A "cached" copy of the "ignore trailing spaces" config. setting.
@@ -264,6 +320,191 @@ schema_destroy_dse_lock()
}
}
+void
+slapi_schema_get_repl_entries(char **repl_schema_top, char ** repl_schema_supplier, char
**repl_schema_consumer, char **default_supplier_policy, char **default_consumer_policy)
+{
+ *repl_schema_top = ENTRY_REPL_SCHEMA_TOP;
+ *repl_schema_supplier = ENTRY_REPL_SCHEMA_SUPPLIER;
+ *repl_schema_consumer = ENTRY_REPL_SCHEMA_CONSUMER;
+ *default_supplier_policy = DEFAULT_SUPPLIER_POLICY;
+ *default_consumer_policy = DEFAULT_CONSUMER_POLICY;
+}
+
+/* It gets the attributes (see attrName)values in the entry, and add
+ * the policies in the provided list
+ *
+ * Entry: Slapi_entry with DN being ENTRY_REPL_SCHEMA_SUPPLIER or
ENTRY_REPL_SCHEMA_CONSUMER
+ * attrName: name defining the policy object (objectclass/attribute) and the action
+ * ATTR_SCHEMA_UPDATE_OBJECTCLASS_ACCEPT
+ * ATTR_SCHEMA_UPDATE_OBJECTCLASS_REJECT
+ * ATTR_SCHEMA_UPDATE_ATTRIBUTE_ACCEPT
+ * ATTR_SCHEMA_UPDATE_ATTRIBUTE_REJECT
+ * *list: is the list of schema_item_t containing the policies (it can be list of
objectclasses or attributes)
+ *
+ */
+static
+void schema_policy_add_action(Slapi_Entry *entry, char *attrName, schema_item_t **list)
+{
+ Slapi_Attr *attr = NULL;
+ schema_item_t *schema_item;
+ char *value;
+ int action;
+
+ /* Retrieve the expected action from the attribute name */
+ if ((strcasecmp(attrName, ATTR_SCHEMA_UPDATE_OBJECTCLASS_ACCEPT) == 0) ||
+ (strcasecmp(attrName, ATTR_SCHEMA_UPDATE_ATTRIBUTE_ACCEPT) == 0)) {
+ action = REPL_SCHEMA_UPDATE_ACCEPT_VALUE;
+ } else {
+ action = REPL_SCHEMA_UPDATE_REJECT_VALUE;
+ }
+
+ /* Retrieve the given attribute from the entry */
+ slapi_entry_attr_find(entry, attrName, &attr);
+ if (attr != NULL) {
+ Slapi_Value *sval = NULL;
+ const struct berval *attrVal = NULL;
+ int k = slapi_attr_first_value(attr, &sval);
+
+ /* For each value adds the policy in the list */
+ while (k != -1) {
+ attrVal = slapi_value_get_berval(sval);
+
+ schema_item = (schema_item_t *) slapi_ch_calloc(1,
sizeof(schema_item_t));
+
+ /* Get the schema name_or_oid */
+ value = (char *) slapi_ch_malloc(attrVal->bv_len + 1);
+ memcpy(value, attrVal->bv_val, attrVal->bv_len);
+ value[attrVal->bv_len] = '\0';
+ schema_item->name_or_oid = value;
+
+ /* Set the action on that item */
+ schema_item->action = action;
+
+ /* Add it on the head of the list */
+ schema_item->next = *list;
+ *list = schema_item;
+
+ /* Get the next name_or_oid */
+ k = slapi_attr_next_value(attr, k, &sval);
+ }
+ }
+}
+
+/* Caller must hold schema_policy_lock in write */
+static void
+schema_load_repl_policy(const char *dn, repl_schema_policy_t *replica)
+{
+ Slapi_DN sdn;
+ Slapi_Entry *entry = NULL;
+ schema_item_t *schema_item, *next;
+
+ if (replica == NULL) {
+ return;
+ }
+
+ /* Start to free the previous policy */
+ /* first the objectclasses policies */
+ for (schema_item = replica->objectclasses; schema_item; ) {
+ slapi_ch_free((void **) &schema_item->name_or_oid);
+ next = schema_item->next;
+ slapi_ch_free((void **) &schema_item);
+ schema_item = next;
+ }
+ replica->objectclasses = NULL;
+ /* second the attributes policies */
+ for (schema_item = replica->attributes; schema_item; ) {
+ slapi_ch_free((void **) &schema_item->name_or_oid);
+ next = schema_item->next;
+ slapi_ch_free((void **) &schema_item);
+ schema_item = next;
+ }
+ replica->attributes = NULL;
+
+ /* Load the replication policy of the schema */
+ slapi_sdn_init_dn_byref( &sdn, dn );
+ if (slapi_search_internal_get_entry(&sdn, NULL, &entry,
plugin_get_default_component_id()) == LDAP_SUCCESS) {
+
+ /* fill the policies (accept/reject) regarding objectclass */
+ schema_policy_add_action(entry, ATTR_SCHEMA_UPDATE_OBJECTCLASS_ACCEPT,
&replica->objectclasses);
+ schema_policy_add_action(entry, ATTR_SCHEMA_UPDATE_OBJECTCLASS_REJECT,
&replica->objectclasses);
+
+ /* fill the policies (accept/reject) regarding attribute */
+ schema_policy_add_action(entry, ATTR_SCHEMA_UPDATE_ATTRIBUTE_ACCEPT,
&replica->attributes);
+ schema_policy_add_action(entry, ATTR_SCHEMA_UPDATE_ATTRIBUTE_REJECT,
&replica->attributes);
+
+ slapi_entry_free( entry );
+ }
+ slapi_sdn_done(&sdn);
+}
+
+/* It load the policies (if they are defined) regarding the replication of the schema
+ * depending if the instance behaves as a consumer or a supplier
+ * It returns 0 if success
+ */
+int
+slapi_schema_load_repl_policies()
+{
+ if (schema_policy_lock == NULL) {
+ if (NULL == (schema_policy_lock = slapi_new_rwlock())) {
+ slapi_log_error(SLAPI_LOG_FATAL,
"slapi_schema_load_repl_policies",
+ "slapi_new_rwlock() for schema replication policy
lock failed\n");
+ return -1;
+ }
+ }
+ slapi_rwlock_wrlock( schema_policy_lock );
+
+ schema_load_repl_policy((const char *) ENTRY_REPL_SCHEMA_SUPPLIER,
&supplier_policy);
+ schema_load_repl_policy((const char *) ENTRY_REPL_SCHEMA_CONSUMER,
&consumer_policy);
+
+ slapi_rwlock_unlock( schema_policy_lock );
+
+ return 0;
+}
+
+/*
+ * It checks if the name/oid of the provided schema item (objectclass/attribute)
+ * is defined in the schema replication policy.
+ * If the replica role is a supplier, it takes the policy from supplier_policy else
+ * it takes it from the consumer_policy.
+ * Then depending on the schema_item, it takes the objectclasses or attributes policies
+ *
+ * If it find the name/oid in the policies, it returns
+ * REPL_SCHEMA_UPDATE_ACCEPT_VALUE: This schema item is accepted and can not prevent
schema update
+ * REPL_SCHEMA_UPDATE_REJECT_VALUE: This schema item is rejected and prevents the
schema update
+ * REPL_SCHEMA_UPDATE_UNKNOWN_VALUE: This schema item as no defined policy
+ *
+ * Caller must hold schema_policy_lock in read
+ */
+static int
+schema_check_policy(int replica_role, int schema_item, char *name, char *oid)
+{
+ repl_schema_policy_t *repl_policy;
+ schema_item_t *policy;
+
+ /* depending on the role, we take the supplier or the consumer policy */
+ if (replica_role == REPL_SCHEMA_AS_SUPPLIER) {
+ repl_policy = &supplier_policy;
+ } else {
+ repl_policy = &consumer_policy;
+ }
+
+ /* Now take the correct schema item policy */
+ if (schema_item == REPL_SCHEMA_OBJECTCLASS) {
+ policy = repl_policy->objectclasses;
+ } else {
+ policy = repl_policy->attributes;
+ }
+
+ /* Try to find the name/oid in the defined policies */
+ while (policy) {
+ if ((strcasecmp( name, policy->name_or_oid) == 0) || (strcasecmp( oid,
policy->name_or_oid) == 0)) {
+ return policy->action;
+ }
+ policy = policy->next;
+ }
+ return REPL_SCHEMA_UPDATE_UNKNOWN_VALUE;
+}
+
static void
schema_dse_lock_read( void )
@@ -5920,11 +6161,12 @@ slapi_schema_get_superior_name(const char *ocname_or_oid)
* If oc_list1 or oc_list2 is global_oc, the caller must hold the oc_lock
*/
static int
-schema_oc_superset_check(struct objclass *oc_list1, struct objclass *oc_list2, char
*message) {
+schema_oc_superset_check(struct objclass *oc_list1, struct objclass *oc_list2, char
*message, int replica_role) {
struct objclass *oc_1, *oc_2;
char *description;
int debug_logging = 0;
int rc, i, j;
+ int repl_schema_policy;
int found;
if (message == NULL) {
@@ -5946,8 +6188,30 @@ schema_oc_superset_check(struct objclass *oc_list1, struct objclass
*oc_list2, c
* - required attributes are also required in oc_2
* - allowed attributes are also allowed in oc_2
*/
+ slapi_rwlock_rdlock( schema_policy_lock );
for (oc_1 = oc_list1; oc_1 != NULL; oc_1 = oc_1->oc_next) {
+
+ /* Check if there is a specific policy for that objectclass */
+ repl_schema_policy = schema_check_policy(replica_role,
REPL_SCHEMA_OBJECTCLASS, oc_1->oc_name, oc_1->oc_oid);
+ if (repl_schema_policy == REPL_SCHEMA_UPDATE_ACCEPT_VALUE) {
+ /* We are skipping the superset checking for that objectclass */
+ slapi_log_error(SLAPI_LOG_REPL, "schema", "Do not
check if this OBJECTCLASS is missing on local/remote schema [%s or %s]\n",
oc_1->oc_name, oc_1->oc_oid);
+ continue;
+ } else if (repl_schema_policy == REPL_SCHEMA_UPDATE_REJECT_VALUE) {
+ /* This objectclass being present, we need to fail as if it was a
superset
+ * keep evaluating to have all the objectclass checking
+ */
+ slapi_log_error(SLAPI_LOG_REPL, "schema", "%s
objectclass prevents replication of the schema\n", oc_1->oc_name);
+ rc = 1;
+ if(debug_logging){
+ /* we continue to check all the objectclasses so we log what
is wrong */
+ continue;
+ } else {
+ break;
+ }
+ }
+
/* Retrieve the remote objectclass in our local schema */
oc_2 = oc_find_nolock(oc_1->oc_oid, oc_list2, PR_TRUE);
if (oc_2 == NULL) {
@@ -6042,16 +6306,18 @@ schema_oc_superset_check(struct objclass *oc_list1, struct
objclass *oc_list2, c
}
}
}
+ slapi_rwlock_unlock( schema_policy_lock );
return rc;
}
static int
-schema_at_superset_check(struct asyntaxinfo *at_list1, struct asyntaxinfo *at_list2, char
*message)
+schema_at_superset_check(struct asyntaxinfo *at_list1, struct asyntaxinfo *at_list2, char
*message, int replica_role)
{
struct asyntaxinfo *at_1, *at_2;
char *info = NULL;
int debug_logging = 0;
+ int repl_schema_policy;
int rc = 0;
if(at_list1 == NULL || at_list2 == NULL){
@@ -6063,8 +6329,29 @@ schema_at_superset_check(struct asyntaxinfo *at_list1, struct
asyntaxinfo *at_li
debug_logging = 1;
}
- for (at_1 = at_list1; at_1 != NULL; at_1 = at_1->asi_next){
+ slapi_rwlock_rdlock( schema_policy_lock );
+ for (at_1 = at_list1; at_1 != NULL; at_1 = at_1->asi_next) {
+ /* Check if there is a specific policy for that objectclass */
+ repl_schema_policy = schema_check_policy(replica_role, REPL_SCHEMA_ATTRIBUTE,
at_1->asi_name, at_1->asi_oid);
+ if (repl_schema_policy == REPL_SCHEMA_UPDATE_ACCEPT_VALUE) {
+ /* We are skipping the superset checking for that attribute */
+ slapi_log_error(SLAPI_LOG_REPL, "schema", "Do not check if
this ATTRIBUTE is missing on local/remote schema [%s or %s]\n", at_1->asi_name,
at_1->asi_oid);
+ continue;
+ } else if (repl_schema_policy == REPL_SCHEMA_UPDATE_REJECT_VALUE) {
+ /* This attribute being present, we need to fail as if it was a superset
+ * but keep evaluating to have all the attribute checking
+ */
+ slapi_log_error(SLAPI_LOG_REPL, "schema", "%s attribute
prevents replication of the schema\n", at_1->asi_name);
+ rc = 1;
+ if (debug_logging) {
+ /* we continue to check all the objectclasses so we log what is
wrong */
+ continue;
+ } else {
+ break;
+ }
+ }
+
/* check if at_1 exists in at_list2 */
if((at_2 = attr_syntax_find(at_1, at_list2))){
/*
@@ -6123,6 +6410,7 @@ schema_at_superset_check(struct asyntaxinfo *at_list1, struct
asyntaxinfo *at_li
}
}
}
+ slapi_rwlock_unlock( schema_policy_lock );
return rc;
@@ -6553,12 +6841,12 @@ schema_objectclasses_superset_check(struct berval **remote_schema,
char *type)
/* Check if the remote_oc_list from a consumer are or not
* a superset of the objectclasses of the local supplier
schema
*/
- rc = schema_oc_superset_check(remote_oc_list,
g_get_global_oc_nolock(), "local supplier" );
+ rc = schema_oc_superset_check(remote_oc_list,
g_get_global_oc_nolock(), "local supplier", REPL_SCHEMA_AS_SUPPLIER);
} else {
/* Check if the objectclasses of the local consumer
schema are or not
* a superset of the remote_oc_list from a supplier
*/
- rc = schema_oc_superset_check(g_get_global_oc_nolock(),
remote_oc_list, "remote supplier");
+ rc = schema_oc_superset_check(g_get_global_oc_nolock(),
remote_oc_list, "remote supplier", REPL_SCHEMA_AS_CONSUMER);
}
oc_unlock();
@@ -6595,13 +6883,13 @@ schema_attributetypes_superset_check(struct berval
**remote_schema, char *type)
* Check if the remote_at_list from a consumer are or not
* a superset of the attributetypes of the local supplier schema
*/
- rc = schema_at_superset_check(remote_at_list,
attr_syntax_get_global_at(), "local supplier" );
+ rc = schema_at_superset_check(remote_at_list,
attr_syntax_get_global_at(), "local supplier", REPL_SCHEMA_AS_SUPPLIER);
} else {
/*
* Check if the attributeypes of the local consumer schema are or not
* a superset of the remote_at_list from a supplier
*/
- rc = schema_at_superset_check(attr_syntax_get_global_at(),
remote_at_list, "remote supplier");
+ rc = schema_at_superset_check(attr_syntax_get_global_at(),
remote_at_list, "remote supplier", REPL_SCHEMA_AS_CONSUMER);
}
attr_syntax_unlock_read();
}
diff --git a/ldap/servers/slapd/slap.h b/ldap/servers/slapd/slap.h
index 6ef0916..068748b 100644
--- a/ldap/servers/slapd/slap.h
+++ b/ldap/servers/slapd/slap.h
@@ -2399,6 +2399,42 @@ typedef struct _slapdFrontendConfig {
#define CONFIG_SCHEMAREPLACE_STR_ON "on"
#define CONFIG_SCHEMAREPLACE_STR_REPLICATION_ONLY "replication-only"
+/* Those define are used when applying the policies for the replication of the schema */
+/* Used to determine if we are looking at the policies from the supplier (push) or
consumer (receive) side */
+#define REPL_SCHEMA_AS_CONSUMER 0
+#define REPL_SCHEMA_AS_SUPPLIER 1
+/* Used to determine if we are looking at objectclass/attribute policies */
+#define REPL_SCHEMA_OBJECTCLASS 0
+#define REPL_SCHEMA_ATTRIBUTE 1
+/* Used to determine the schema replication: never or based on values */
+#define REPL_SCHEMA_UPDATE_NEVER 1
+#define REPL_SCHEMA_UPDATE_ON_VALUE 2
+/* Used to determine the action when schema replition is based on values */
+#define REPL_SCHEMA_UPDATE_ACCEPT_VALUE 3
+#define REPL_SCHEMA_UPDATE_REJECT_VALUE 4
+#define REPL_SCHEMA_UPDATE_UNKNOWN_VALUE 5
+
+/* entries containing the policies */
+#define ENTRY_REPL_SCHEMA_TOP "cn=replSchema, cn=config"
+#define ENTRY_REPL_SCHEMA_SUPPLIER "cn=supplierUpdatePolicy,"
ENTRY_REPL_SCHEMA_TOP
+#define ENTRY_REPL_SCHEMA_CONSUMER "cn=consumerUpdatePolicy,"
ENTRY_REPL_SCHEMA_TOP
+/*
+ * attribute name containing the policy action for
+ * a given objectclass/attribute
+ */
+#define ATTR_SCHEMA_UPDATE_OBJECTCLASS_ACCEPT "schemaUpdateObjectclassAccept"
+#define ATTR_SCHEMA_UPDATE_OBJECTCLASS_REJECT "schemaUpdateObjectclassReject"
+#define ATTR_SCHEMA_UPDATE_ATTRIBUTE_ACCEPT "schemaUpdateAttributeAccept"
+#define ATTR_SCHEMA_UPDATE_ATTRIBUTE_REJECT "schemaUpdateAttributeReject"
+#define SUPPLIER_POLICY_1 ATTR_SCHEMA_UPDATE_OBJECTCLASS_ACCEPT ":
printer-uri-oid\n"
+#if defined(USE_OLD_UNHASHED)
+#define SUPPLIER_POLICY_2 ATTR_SCHEMA_UPDATE_ATTRIBUTE_ACCEPT ":
PSEUDO_ATTR_UNHASHEDUSERPASSWORD_OID\n"
+#else
+#define SUPPLIER_POLICY_2 ATTR_SCHEMA_UPDATE_ATTRIBUTE_ACCEPT ":
2.16.840.1.113730.3.1.2110\n"
+#endif
+#define DEFAULT_SUPPLIER_POLICY SUPPLIER_POLICY_1 SUPPLIER_POLICY_2
+#define DEFAULT_CONSUMER_POLICY DEFAULT_SUPPLIER_POLICY
+
#define CONNECTION_BUFFER_OFF 0
#define CONNECTION_BUFFER_ON 1
#define CONNECTION_BUFFER_ADAPT 2
diff --git a/ldap/servers/slapd/slapi-private.h b/ldap/servers/slapd/slapi-private.h
index fd5c54b..da62282 100644
--- a/ldap/servers/slapd/slapi-private.h
+++ b/ldap/servers/slapd/slapi-private.h
@@ -755,6 +755,9 @@ struct slapi_componentid * plugin_get_default_component_id();
#define SLAPI_COMPONENT_NAME_NSPR "Netscape Portable Runtime"
#define SLAPI_COMPONENT_NAME_LDAPSDK "LDAP sdk"
+/* loads the policies related to the replication of the schema */
+int slapi_schema_load_repl_policies();
+void slapi_schema_get_repl_entries(char **repl_schema_top, char ** repl_schema_supplier,
char **repl_schema_consumer, char **default_supplier_policy, char
**default_consumer_policy);
/* return the list of attr defined in the schema matching the attr flags */
char ** slapi_schema_list_attribute_names(unsigned long flag);
/* return the list of attributes belonging to the objectclass */