]> git.ipfire.org Git - thirdparty/openldap.git/commitdiff
ITS#9473 Add variant overlay
authorOndřej Kuzník <ondra@mistotebe.net>
Wed, 24 Feb 2021 14:41:44 +0000 (14:41 +0000)
committerQuanah Gibson-Mount <quanah@openldap.org>
Fri, 26 Mar 2021 18:05:30 +0000 (18:05 +0000)
43 files changed:
contrib/slapd-modules/variant/.gitignore [new file with mode: 0644]
contrib/slapd-modules/variant/Makefile [new file with mode: 0644]
contrib/slapd-modules/variant/slapo-variant.5 [new file with mode: 0644]
contrib/slapd-modules/variant/tests/.gitignore [new file with mode: 0644]
contrib/slapd-modules/variant/tests/Rules.mk [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/additional-config.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/config.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/hidden.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test001-01-same-dn.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test001-01a-same-dn.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test001-02-same-attribute.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test001-03-different-types.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test002-01-entry.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test002-02-regex.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test003-out.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test005-changes.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test005-modify-missing.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test005-out.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test005-variant-missing.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test006-config.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test006-out.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test007-out.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test010-out.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test011-out.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test012-data.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/test012-out.ldif [new file with mode: 0644]
contrib/slapd-modules/variant/tests/data/variant.conf [new file with mode: 0644]
contrib/slapd-modules/variant/tests/run [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/all [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/common.sh [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/test001-config [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/test002-add-delete [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/test003-search [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/test004-compare [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/test005-modify [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/test006-acl [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/test007-subtypes [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/test008-variant-replication [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/test009-ignored-replication [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/test010-limits [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/test011-referral [new file with mode: 0755]
contrib/slapd-modules/variant/tests/scripts/test012-crossdb [new file with mode: 0755]
contrib/slapd-modules/variant/variant.c [new file with mode: 0644]

diff --git a/contrib/slapd-modules/variant/.gitignore b/contrib/slapd-modules/variant/.gitignore
new file mode 100644 (file)
index 0000000..e5fb152
--- /dev/null
@@ -0,0 +1,3 @@
+# test suite
+clients
+servers
diff --git a/contrib/slapd-modules/variant/Makefile b/contrib/slapd-modules/variant/Makefile
new file mode 100644 (file)
index 0000000..43a23b8
--- /dev/null
@@ -0,0 +1,75 @@
+# $OpenLDAP$
+# This work is part of OpenLDAP Software <http://www.openldap.org/>.
+#
+# Copyright 1998-2021 The OpenLDAP Foundation.
+# Copyright 2017 Ondřej Kuzník, Symas Corp. All Rights Reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted only as authorized by the OpenLDAP
+# Public License.
+#
+# A copy of this license is available in the file LICENSE in the
+# top-level directory of the distribution or, alternatively, at
+# <http://www.OpenLDAP.org/license.html>.
+
+LDAP_SRC = ../../..
+LDAP_BUILD = $(LDAP_SRC)
+SRCDIR = ./
+LDAP_INC = -I$(LDAP_BUILD)/include -I$(LDAP_SRC)/include -I$(LDAP_SRC)/servers/slapd
+LDAP_LIB = $(LDAP_BUILD)/libraries/libldap/libldap.la \
+       $(LDAP_BUILD)/libraries/liblber/liblber.la
+
+LIBTOOL = $(LDAP_BUILD)/libtool
+INSTALL = /usr/bin/install
+CC = gcc
+OPT = -g -O2 -Wall
+DEFS = -DSLAPD_OVER_VARIANT=SLAPD_MOD_DYNAMIC
+INCS = $(LDAP_INC)
+LIBS = $(LDAP_LIB)
+
+PROGRAMS = variant.la
+MANPAGES = slapo-variant.5
+CLEAN = *.o *.lo *.la .libs
+LTVER = 0:0:0
+
+prefix=/usr/local
+exec_prefix=$(prefix)
+ldap_subdir=/openldap
+
+libdir=$(exec_prefix)/lib
+libexecdir=$(exec_prefix)/libexec
+moduledir = $(libexecdir)$(ldap_subdir)
+mandir = $(exec_prefix)/share/man
+man5dir = $(mandir)/man5
+
+all: $(PROGRAMS)
+
+d :=
+sp :=
+dir := tests
+include $(dir)/Rules.mk
+
+%.lo: %.c
+       $(LIBTOOL) --mode=compile $(CC) $(OPT) $(DEFS) $(INCS) -c $<
+
+%.la: %.lo
+       $(LIBTOOL) --mode=link $(CC) $(OPT) -version-info $(LTVER) \
+               -rpath $(moduledir) -module -o $@ $? $(LIBS)
+
+clean:
+       rm -rf $(CLEAN)
+
+install: install-lib install-man FORCE
+
+install-lib: $(PROGRAMS)
+       mkdir -p $(DESTDIR)$(moduledir)
+       for p in $(PROGRAMS) ; do \
+               $(LIBTOOL) --mode=install cp $$p $(DESTDIR)$(moduledir) ; \
+       done
+
+install-man: $(MANPAGES)
+       mkdir -p  $(DESTDIR)$(man5dir)
+       $(INSTALL) -m 644 $(MANPAGES) $(DESTDIR)$(man5dir)
+
+FORCE:
+
diff --git a/contrib/slapd-modules/variant/slapo-variant.5 b/contrib/slapd-modules/variant/slapo-variant.5
new file mode 100644 (file)
index 0000000..a480744
--- /dev/null
@@ -0,0 +1,472 @@
+.TH SLAPO-VARIANT 5 "RELEASEDATE" "OpenLDAP"
+.\" Copyright 2016-2017 Symas Corp. All Rights Reserved.
+.\" Copying restrictions apply.  See LICENSE.
+.SH NAME
+slapo\-variant \- share values between entries
+.SH SYNOPSIS
+olcOverlay=variant
+.SH DESCRIPTION
+The
+.B variant
+overlay to
+.BR slapd (8)
+allows attributes/values to be shared between several entries. In some ways
+this is similar to
+.BR slapo-collect (5)
+with the exception that the source and target attributes can be different.
+.LP
+The overlay operates on configured
+.B variant
+entries which can have several
+.B attributes
+each configured to borrow values from an attribute in the
+.B alternate
+entry.
+.LP
+Two types of
+.B variant
+entries can be configured,
+.B regular
+and
+.BR regex ,
+where the latter are configured with a regular expression and patterns to
+locate each alternate entry, with access to the variant DN and first nine
+submatches captured by the regular expression.
+.LP
+For most purposes (see
+.BR LIMITATIONS ,
+especially for
+.B regex
+variants), the resulting entry is completely transparent to the operations
+performed on it, e.g. a modify operation on the
+.B variant
+attribute gets transformed
+into an operation on the
+.B alternate
+entry+attribute. As such, the usual ACL rules apply, appropriate
+access to both the
+.B variant
+and
+.B alternate
+entry is checked.
+.LP
+As a special case,
+.B Add
+and
+.B Delete
+operations will not affect the
+.B alternate
+entries. Should an attempt be made to add a configured
+.B variant
+entry with the
+.B variant
+attributes already populated, the operation will be rejected with a
+.B Constraint
+.BR Violation .
+
+.SH CONFIGURATION LAYOUT
+
+The overlay has to be instantiated under a database adding an entry of
+.B olcOverlay=variant
+with objectClass of
+.BR olcVariantConfig .
+
+The overlay configuration subtree consists of the following levels:
+.RS
+.TP
+.B objectClass=olcVariantConfig
+Main overlay configuration. Created directly under the database
+configuration entry.
+.TP
+.B objectClass=olcVariantVariant
+Specifies a
+.B regular variant
+entry and must be a child of an entry with
+.BR objectClass=olcVariantConfig .
+There may be as many such entries as necessary provided they all specify a
+different DN in the
+.BR olcVariantEntry
+attribute.
+.TP
+.B objectClass=olcVariantAttribute
+Specifies a
+.B regular variant
+attribute together with information where the
+.B alternate
+attribute is stored. Must be a child of an entry with
+.BR objectClass=olcVariantVariant .
+There may be as many such entries as necessary provided they all specify a
+different attribute in
+.BR olcVariantVariantAttribute .
+.TP
+.B objectClass=olcVariantRegex
+Specifies a
+.B regex variant
+entry and must be a child of an entry with
+.BR objectClass=olcVariantConfig .
+There may be as many such entries as necessary provided they all specify a
+different DN in the
+.BR olcVariantEntryRegex
+attribute.
+.TP
+.B objectClass=olcVariantAttributePattern
+Specifies a
+.B regex variant
+attribute together with information where the
+.B alternate
+attribute is stored. Must be a child of an entry with
+.BR objectClass=olcVariantRegex .
+There may be as many such entries as necessary provided they all specify a
+different attribute in
+.BR olcVariantVariantAttribute .
+.RE
+
+In the case of
+.BR slapd.conf (5),
+the variant definition is delimited by the keyword
+.B variantDN
+followed by an arbitrary number of
+.B variantSpec
+providing the attribute definitions following it. Each new
+.B variantDN
+line starts configuring a new variant.
+
+.SH OVERLAY CONFIGURATION ENTRY
+
+The top entry
+.RB ( olcVariantConfig )
+has the following options available:
+
+.RS
+.TP
+.B olcVariantPassReplication: TRUE | FALSE
+If set to
+.BR TRUE ,
+.B search
+operations with the
+.B SyncReplication
+control will be passed unchanged so that replication can be unaffected.
+Defaults to
+.B FALSE
+while unset. The
+.BR slapd.conf (5)
+equivalent is
+.BR passReplication .
+.RE
+
+.SH VARIANT CONFIGURATION ENTRY
+
+The
+.B regular variant entry
+configuration
+.RB ( olcVariantVariant )
+has the following options available:
+
+.RS
+.TP
+.B olcVariantEntry: <dn>
+Mandatory attribute, indicates that the named entry is to be treated as a
+.B variant
+entry. The
+.BR slapd.conf (5)
+equivalent is
+.BR variantDN .
+.TP
+.B name: <reference>
+Name of the entry for reference, usually the attribute present in the
+configuration entry's RDN. There is no
+.BR slapd.conf (5)
+equivalent as this has no effect on the overlay operation.
+.RE
+
+Similarly, the
+.B regex variant entry
+configuration
+.RB ( olcVariantRegex )
+has these options available:
+
+.RS
+.TP
+.B olcVariantRegex: <regex>
+Mandatory attribute, indicates that the entries whose normalised DN matches is
+to be treated as a
+.B regex variant
+entry. The (POSIX.2) regex can use submatches to capture parts of the DN for
+later use in locating the
+.B alternative
+.BR entry .
+The
+.BR slapd.conf (5)
+equivalent is
+.BR variantRegex .
+.TP
+.B name: <reference>
+Name of the entry for reference, usually the attribute present in the
+configuration entry's RDN. There is no
+.BR slapd.conf (5)
+equivalent as this has no effect on the overlay operation.
+.RE
+
+.SH CONFIGURATION PRECEDENCE
+
+While several
+.B regex variants
+can match the same entry, only one can apply at a time. The list of the
+.B regular variants
+is checked first. Should none match, the list of
+.B regex variants
+is checked in the order they have been configured using only the first one that
+matches.
+
+.SH VARIANT ATTRIBUTE CONFIGURATION ENTRY
+
+The
+.B regular variant
+attribute configuration
+.RB ( olcVariantAttribute )
+and
+.B regex variant
+attribute configuration
+.RB ( olcVariantAttributePattern )
+have the following options available:
+
+.RS
+.TP
+.B name: <reference>
+Name of the attribute configuration for reference and/or documentation, if
+present, usually found in the configuration entry's RDN. There is no
+.BR slapd.conf (5)
+equivalent as this has no effect on the overlay operation.
+.TP
+.B olcVariantVariantAttribute: <attr>
+Mandatory attribute, indicates that the named attribute is not present in
+the
+.B variant
+entry but is to be retrieved from the
+.B alternate
+entry.
+.TP
+.B olcVariantAlternativeAttribute: <attr>
+Mandatory attribute, indicates that the values of the named attribute is to
+be retrieved from the
+.B alternate
+entry for use as the values of the
+.B variant
+attribute. The syntaxes of the corresponding
+.B variant
+and
+.B alternate
+attributes have to match or the configuration will be rejected.
+.TP
+.B olcVariantAlternativeEntry: <dn>
+Attribute mandatory for
+.B regular
+.BR variants ,
+indicates the
+.B alternate
+entry to use when retrieving the attribute from.
+.TP
+.B olcVariantAlternativeEntryPattern: <pattern>
+Attribute mandatory for
+.B regex
+.BR variants ,
+indicates the
+.B alternate
+entry to use when retrieving the attribute from. Substitution patterns
+.RB ( $n )
+can be used to insert parts of the variant entry's DN.
+.B $0
+will place the entire variant DN,
+.B $1
+to
+.B $9
+can be used to place respective capture patterns from the
+.B variant
+entry.
+.TP
+.B variantSpec <attr> <attr2> <dn>
+.BR slapd.conf (5)
+only. The equivalent to options above, where
+.B <attr>
+represents the
+.BR olcVariantVariantAttribute ,
+.B <attr2>
+represents the
+.B olcVariantAlternativeAttribute
+and
+.B <dn>
+has the same meaning as the content of
+.BR olcVariantAlternativeEntry .
+Has to follow a
+.B variantDN
+line in the overlay's configuration.
+.TP
+.B variantRegexSpec <attr> <attr2> <pattern>
+.BR slapd.conf (5)
+only. The equivalent to options above, where
+.B <attr>
+represents the
+.BR olcVariantVariantAttribute ,
+.B <attr2>
+represents the
+.B olcVariantAlternativeAttribute
+and
+.B <pattern>
+has the same meaning as the content of
+.BR olcVariantAlternativeEntryPattern .
+Has to follow a
+.B variantRegex
+line in the overlay's configuration.
+.RE
+
+.SH EXAMPLE
+
+The following is an example of a configured overlay, substitute
+.B $DATABASE
+for the DN of the database it is attached to and
+.B {x}
+with the desired position of the overlay in the overlay stack.
+
+.nf
+dn: olcOverlay={x}variant,$DATABASE
+objectClass: olcVariantConfig
+olcOverlay: variant
+# Let replication requests pass through unmodified
+olcVariantPassReplication: TRUE
+
+# when an operation considers dc=example,dc=com
+dn: name=example,olcOverlay={x}variant,$DATABASE
+objectClass: olcVariantVariant
+olcVariantEntry: dc=example,dc=com
+
+# share the Headquarters' address as the company address
+dn: olcVariantVariantAttribute=postaladdress,name={0}example,olcOverlay={x}variant,$DATABASE
+objectClass: olcVariantVariantAttribute
+olcVariantVariantAttribute: postaladdress
+olcVariantAlternativeAttribute: postaladdress
+olcVariantAlternativeEntry: ou=Headquarters,dc=example,dc=com
+
+# populate telephonenumber from CEO's home phone
+dn: name=Take phone from CEO entry,name={0}example,olcOverlay={x}variant,$DATABASE
+objectClass: olcVariantVariantAttribute
+olcVariantVariantAttribute: telephonenumber
+olcVariantAlternativeAttribute: homephone
+olcVariantAlternativeEntry: cn=John Doe,ou=People,dc=example,dc=com
+
+# Match all entries with example in the DN
+#
+# It will not match dc=example,dc=com as that's already configured as a regular
+# variant
+dn: name=example 2,olcOverlay={x}variant,$DATABASE
+objectClass: olcVariantRegex
+olcVariantEntryRegex: .*example[^,]*,(.*)
+
+dn: olcVariantVariantAttribute=location,name={1}example 2,olcOverlay={x}variant,$DATABASE
+objectClass: olcVariantAttributePattern
+olcVariantVariantAttribute: location
+olcVariantAlternativeAttribute: location
+olcVariantAlternativeEntryPattern: ou=object with location,$1
+.fi
+
+The
+.BR slapd.conf (5)
+equivalent of the above follows (note that the converted
+.B cn=config
+will differ in the first variant attribute configuration entry):
+
+.nf
+overlay variant
+passReplication TRUE
+
+variantDN dc=example,dc=com
+variantSpec telephonenumber homephone "cn=John Doe,ou=People,dc=example,dc=com"
+variantSpec postaladdress postaladdress ou=Headquarters,dc=example,dc=com
+
+variantRegex .*example[^,]*,(.*)
+variantRegexSpec location location "ou=object with location,$1"
+.fi
+
+.SH REPLICATION
+
+There are two ways that a database with
+.BR slapo-variant (5)
+might be replicated, either replicating the data as stored in the database,
+or as seen by the clients interacting with the server.
+
+The former can be achieved by setting the overlay option
+.B olcVariantPassReplication
+on the provider and configuring
+.BR slapo-syncprov (5)
+to appear before (with a lower index than)
+.BR slapo-variant (5).
+This is the preferred way and the only to work with
+.B regex variants
+or support multi-provider replication,
+but care must be taken to configure
+.BR slapo-variant (5)
+correctly on each replica.
+
+The latter is mostly possible by keeping the option
+.B olcVariantPassReplication
+set to
+.B FALSE
+on the provider and configuring
+.BR slapo-syncprov (5)
+to appear after (with a higher index than)
+.BR slapo-variant (5).
+However, it will only really work for replication set-ups that do not
+utilise
+.B regex
+.BR variants ,
+delta-replication nor the refresh and persist mode and is therefore
+discouraged.
+
+.SH LIMITATIONS
+For
+.B regex
+.BR variants ,
+the
+.B Search
+operation will only apply if the search scope is set to
+.BR base .
+
+The
+.B ModRDN
+operation is not currently handled and will always modify only the entry in
+question, not the configured
+.B alternate
+entry.
+
+The
+.B Modify
+operation is not atomic with respect to the alternate entries. Currently,
+the overlay processes the operations on the entry, sends the result message
+and, if successful, starts modifying the
+.B alternate
+entries accordingly.
+There is currently no support to indicate whether modifications to the
+.B alternate
+entries have been successful or whether they have finished.
+
+The only control explicitly handled is the
+.B SyncReplication
+control if enabled through the
+.B olcVariantPassReplication
+setting, adding any controls to an operation that is handled by the overlay
+might lead to unexpected behaviour and is therefore discouraged.
+
+.SH FILES
+.TP
+ETCDIR/slapd.conf
+default slapd configuration file
+.TP
+ETCDIR/slapd.d
+default slapd configuration directory
+.SH SEE ALSO
+.BR slapd-config (5),
+.BR slapd.conf (5),
+.BR slapd.overlays (5),
+.BR regex (7),
+.BR slapd (8)
+.SH ACKNOWLEDGEMENTS
+This module was developed in 2016-2017 by Ondřej Kuzník for Symas Corp.
diff --git a/contrib/slapd-modules/variant/tests/.gitignore b/contrib/slapd-modules/variant/tests/.gitignore
new file mode 100644 (file)
index 0000000..9253831
--- /dev/null
@@ -0,0 +1,4 @@
+progs
+schema
+testdata
+testrun
diff --git a/contrib/slapd-modules/variant/tests/Rules.mk b/contrib/slapd-modules/variant/tests/Rules.mk
new file mode 100644 (file)
index 0000000..c25c1d2
--- /dev/null
@@ -0,0 +1,23 @@
+sp := $(sp).x
+dirstack_$(sp) := $(d)
+d := $(dir)
+
+.PHONY: test
+
+CLEAN += clients servers tests/progs tests/schema tests/testdata tests/testrun
+
+test: all clients servers tests/progs
+
+test:
+       cd tests; \
+               SRCDIR=$(abspath $(LDAP_SRC)) \
+               LDAP_BUILD=$(abspath $(LDAP_BUILD)) \
+               TOPDIR=$(abspath $(SRCDIR)) \
+               LIBTOOL=$(abspath $(LIBTOOL)) \
+                       $(abspath $(SRCDIR))/tests/run all
+
+servers clients tests/progs:
+       ln -s $(abspath $(LDAP_BUILD))/$@ $@
+
+d := $(dirstack_$(sp))
+sp := $(basename $(sp))
diff --git a/contrib/slapd-modules/variant/tests/data/additional-config.ldif b/contrib/slapd-modules/variant/tests/data/additional-config.ldif
new file mode 100644 (file)
index 0000000..6a286fe
--- /dev/null
@@ -0,0 +1,23 @@
+dn: name={4}test002,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantVariant
+olcVariantEntry: cn=Gern Jensen,ou=Information Technology Division,ou=People,dc=example,dc=com
+
+dn: name=attribute 1,name={4}test002,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantVariantAttribute: cn
+olcVariantAlternativeAttribute: description
+olcVariantAlternativeEntry: dc=example,dc=com
+
+dn: name=attribute 2,name={4}test002,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantVariantAttribute: pager
+olcVariantAlternativeAttribute: telephonenumber
+olcVariantAlternativeEntry: dc=example,dc=com
+
+dn: name={0}attribute 1,name={4}test002,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcVariantVariantAttribute
+olcVariantVariantAttribute: description
diff --git a/contrib/slapd-modules/variant/tests/data/config.ldif b/contrib/slapd-modules/variant/tests/data/config.ldif
new file mode 100644 (file)
index 0000000..6e323b9
--- /dev/null
@@ -0,0 +1,89 @@
+dn: olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectClass: olcOverlayConfig
+objectclass: olcVariantConfig
+
+dn: olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcVariantPassReplication
+olcVariantPassReplication: TRUE
+
+dn: name={0}variant,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantVariant
+olcVariantEntry: ou=People,dc=example,dc=com
+
+# a basic variant
+dn: olcVariantVariantAttribute=description,name={0}variant,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantAlternativeAttribute: description
+olcVariantAlternativeEntry: dc=example,dc=com
+
+# a nonexistent alternate
+dn: olcVariantVariantAttribute=seealso,name={0}variant,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantAlternativeAttribute: seealso
+olcVariantAlternativeEntry: ou=Societies,dc=example,dc=com
+
+dn: name={1}variant,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantVariant
+olcVariantEntry: ou=Groups,dc=example,dc=com
+
+# recursive retrieval is not done
+dn: olcVariantVariantAttribute=description,name={1}variant,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantAlternativeAttribute: description
+olcVariantAlternativeEntry: ou=People,dc=example,dc=com
+
+# a variant taking data from a different attribute (after the changes below)
+dn: olcVariantVariantAttribute=st,name={1}variant,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantAlternativeAttribute: st
+olcVariantAlternativeEntry: cn=Manager,dc=example,dc=com
+
+# configuration changes
+dn: olcVariantVariantAttribute={1}st,name={1}variant,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcVariantAlternativeAttribute
+olcVariantAlternativeAttribute: ou
+-
+replace: olcVariantAlternativeEntry
+olcVariantAlternativeEntry: ou=Alumni Association,ou=People,dc=example,dc=com
+-
+
+# a regex variant
+dn: name={2}regex,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantRegex
+olcVariantEntryRegex: (.*),(ou=.*technology.*)(,)dc=example,dc=com
+
+dn: olcVariantVariantAttribute=ou,name={2}regex,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttributePattern
+olcVariantAlternativeAttribute: ou
+olcVariantAlternativeEntryPattern: $2$3dc=example$3dc=com
+
+# Duplicate description into title
+dn: olcVariantVariantAttribute=title,name={2}regex,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttributePattern
+olcVariantAlternativeAttribute: description
+olcVariantAlternativeEntryPattern: $0
+
+# everything
+dn: name={3}regex,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantRegex
+olcVariantEntryRegex: .*
+
+dn: olcVariantVariantAttribute=l,name={3}regex,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttributePattern
+olcVariantAlternativeAttribute: l
+olcVariantAlternativeEntryPattern: dc=example,dc=com
+
diff --git a/contrib/slapd-modules/variant/tests/data/hidden.ldif b/contrib/slapd-modules/variant/tests/data/hidden.ldif
new file mode 100644 (file)
index 0000000..d219746
--- /dev/null
@@ -0,0 +1,4 @@
+dn: ou=Groups,dc=example,dc=com
+changetype: modify
+add: description
+description: This is hidden by the overlay config
diff --git a/contrib/slapd-modules/variant/tests/data/test001-01-same-dn.ldif b/contrib/slapd-modules/variant/tests/data/test001-01-same-dn.ldif
new file mode 100644 (file)
index 0000000..880e035
--- /dev/null
@@ -0,0 +1,4 @@
+dn: name=variant,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantVariant
+olcVariantEntry: ou=Groups,dc=example,dc=com
diff --git a/contrib/slapd-modules/variant/tests/data/test001-01a-same-dn.ldif b/contrib/slapd-modules/variant/tests/data/test001-01a-same-dn.ldif
new file mode 100644 (file)
index 0000000..0fb8b2b
--- /dev/null
@@ -0,0 +1,4 @@
+dn: name={0}variant,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcVariantEntry
+olcVariantEntry: ou=Groups,dc=example,dc=com
diff --git a/contrib/slapd-modules/variant/tests/data/test001-02-same-attribute.ldif b/contrib/slapd-modules/variant/tests/data/test001-02-same-attribute.ldif
new file mode 100644 (file)
index 0000000..8447018
--- /dev/null
@@ -0,0 +1,6 @@
+dn: olcVariantAlternativeAttribute=description,name={1}variant,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantVariantAttribute: description
+olcVariantAlternativeAttribute: description
+olcVariantAlternativeEntry: ou=People,dc=example,dc=com
diff --git a/contrib/slapd-modules/variant/tests/data/test001-03-different-types.ldif b/contrib/slapd-modules/variant/tests/data/test001-03-different-types.ldif
new file mode 100644 (file)
index 0000000..dfbde5b
--- /dev/null
@@ -0,0 +1,4 @@
+dn: olcVariantVariantAttribute={1}st,name={1}variant,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcVariantAlternativeAttribute
+olcVariantAlternativeAttribute: userPassword
diff --git a/contrib/slapd-modules/variant/tests/data/test002-01-entry.ldif b/contrib/slapd-modules/variant/tests/data/test002-01-entry.ldif
new file mode 100644 (file)
index 0000000..21b5b14
--- /dev/null
@@ -0,0 +1,16 @@
+dn: cn=Gern Jensen,ou=Information Technology Division,ou=People,dc=example,dc=com
+changetype: add
+objectclass: testPerson
+cn: Gern Jensen
+sn: Jensen
+uid: gjensen
+postaladdress: ITD $ 535 W. William St $ Anytown, MI 48103
+seealso: cn=All Staff,ou=Groups,dc=example,dc=com
+drink: Coffee
+homepostaladdress: 844 Brown St. Apt. 4 $ Anytown, MI 48104
+description: Very odd
+facsimiletelephonenumber: +1 313 555 7557
+telephonenumber: +1 313 555 8343
+mail: gjensen@mailgw.example.com
+homephone: +1 313 555 8844
+testTime: 20050304001801.234Z
diff --git a/contrib/slapd-modules/variant/tests/data/test002-02-regex.ldif b/contrib/slapd-modules/variant/tests/data/test002-02-regex.ldif
new file mode 100644 (file)
index 0000000..8f0f439
--- /dev/null
@@ -0,0 +1,7 @@
+dn: cn=Rosco P. Coltrane, ou=Information Technology Division, ou=People, dc=example,dc=com
+changetype: add
+objectclass: OpenLDAPperson
+cn: Rosco P. Coltrane
+sn: Coltrane
+uid: rosco
+title: Chief Investigator, ITD
diff --git a/contrib/slapd-modules/variant/tests/data/test003-out.ldif b/contrib/slapd-modules/variant/tests/data/test003-out.ldif
new file mode 100644 (file)
index 0000000..1c3ca5d
--- /dev/null
@@ -0,0 +1,124 @@
+# Test 1, list two unrelated entries
+dn: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+cn: Mark Elliot
+cn: Mark A Elliot
+sn: Elliot
+uid: melliot
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 199 Outer Drive $ Ypsilanti, MI 48198
+homePhone: +1 313 555 0388
+drink: Gasoline
+title: Director, UM Alumni Association
+mail: melliot@mail.alumni.example.com
+pager: +1 313 555 7671
+facsimileTelephoneNumber: +1 313 555 7762
+telephoneNumber: +1 313 555 4177
+
+dn: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,dc=example,dc
+ =com
+objectClass: OpenLDAPperson
+cn: Bjorn Jensen
+cn: Biiff Jensen
+sn: Jensen
+uid: bjorn
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+userPassword:: Ympvcm4=
+homePostalAddress: 19923 Seven Mile Rd. $ South Lyon, MI 49999
+drink: Iced Tea
+description: Hiker, biker
+title: Director, Embedded Systems
+postalAddress: Info Tech Division $ 535 W. William St. $ Anytown, MI 48103
+mail: bjorn@mailgw.example.com
+homePhone: +1 313 555 5444
+pager: +1 313 555 4474
+facsimileTelephoneNumber: +1 313 555 2177
+telephoneNumber: +1 313 555 0355
+
+
+# Test 2, list some of the variant entries, checking that attributes have been populated
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Groups
+st: Alumni Association
+
+dn: ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: extensibleObject
+ou: People
+uidNumber: 0
+gidNumber: 0
+description: The Example, Inc. at Anytown
+
+dn: cn=Manager,dc=example,dc=com
+objectClass: person
+cn: Manager
+cn: Directory Manager
+cn: Dir Man
+sn: Manager
+description: Manager of the directory
+userPassword:: c2VjcmV0
+
+
+# Return $BASEDN, location is rewritten to end
+dn: dc=example,dc=com
+objectClass: top
+objectClass: organization
+objectClass: domainRelatedObject
+objectClass: dcObject
+dc: example
+st: Michigan
+o: Example, Inc.
+o: EX
+o: Ex.
+description: The Example, Inc. at Anytown
+postalAddress: Example, Inc. $ 535 W. William St. $ Anytown, MI 48109 $ US
+telephoneNumber: +1 313 555 1817
+associatedDomain: example.com
+l: Anytown, Michigan
+
+
+# Make sure only the first regex applies
+dn: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,dc=example,dc
+ =com
+objectClass: OpenLDAPperson
+cn: Bjorn Jensen
+cn: Biiff Jensen
+sn: Jensen
+uid: bjorn
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+userPassword:: Ympvcm4=
+homePostalAddress: 19923 Seven Mile Rd. $ South Lyon, MI 49999
+drink: Iced Tea
+description: Hiker, biker
+postalAddress: Info Tech Division $ 535 W. William St. $ Anytown, MI 48103
+mail: bjorn@mailgw.example.com
+homePhone: +1 313 555 5444
+pager: +1 313 555 4474
+facsimileTelephoneNumber: +1 313 555 2177
+telephoneNumber: +1 313 555 0355
+title: Hiker, biker
+ou: Information Technology Division
+
+
+# Exercise the last regex
+dn: cn=ITD Staff,ou=Groups,dc=example,dc=com
+owner: cn=Manager,dc=example,dc=com
+description: All ITD Staff
+cn: ITD Staff
+objectClass: groupOfUniqueNames
+uniqueMember: cn=Manager,dc=example,dc=com
+uniqueMember: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,dc=
+ example,dc=com
+uniqueMember: cn=James A Jones 2,ou=Information Technology Division,ou=People,
+ dc=example,dc=com
+uniqueMember: cn=John Doe,ou=Information Technology Division,ou=People,dc=exam
+ ple,dc=com
+l: Anytown, Michigan
+
+
+# Test 3, check filters pick up the new data
+dn: ou=Groups,dc=example,dc=com
+st: Alumni Association
+
diff --git a/contrib/slapd-modules/variant/tests/data/test005-changes.ldif b/contrib/slapd-modules/variant/tests/data/test005-changes.ldif
new file mode 100644 (file)
index 0000000..767f48a
--- /dev/null
@@ -0,0 +1,35 @@
+dn: ou=People,dc=example,dc=com
+changetype: modify
+add: description
+description: Everyone's heard of them
+-
+increment: uidNumber
+uidNumber: 1
+-
+
+dn: ou=Groups,dc=example,dc=com
+changetype: modify
+add: st
+st: Alabama
+-
+
+# check regex
+dn: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,dc=example,dc
+ =com
+changetype: modify
+replace: description
+description: A mouthful
+-
+add: ou
+ou: The IT Crowd
+-
+
+# have the two mods merge
+dn: dc=example,dc=com
+changetype: modify
+add: l
+l: Locally
+-
+replace: st
+st: Antarctica
+-
diff --git a/contrib/slapd-modules/variant/tests/data/test005-modify-missing.ldif b/contrib/slapd-modules/variant/tests/data/test005-modify-missing.ldif
new file mode 100644 (file)
index 0000000..ce9c007
--- /dev/null
@@ -0,0 +1,4 @@
+dn: cn=Gern Jensen,ou=Information Technology Division,ou=People,dc=example,dc=com
+changetype: modify
+replace: description
+description: Ghost
diff --git a/contrib/slapd-modules/variant/tests/data/test005-out.ldif b/contrib/slapd-modules/variant/tests/data/test005-out.ldif
new file mode 100644 (file)
index 0000000..67e441b
--- /dev/null
@@ -0,0 +1,206 @@
+# Test1: list entries that should have been changed by ldapmodify
+dn: dc=example,dc=com
+objectclass: top
+objectclass: organization
+objectclass: domainRelatedObject
+objectclass: dcobject
+dc: example
+l: Anytown, Michigan
+l: Locally
+o: Example, Inc.
+o: EX
+o: Ex.
+description: The Example, Inc. at Anytown
+description: Everyone's heard of them
+postaladdress: Example, Inc. $ 535 W. William St. $ Anytown, MI 48109 $ US
+telephonenumber: +1 313 555 1817
+associateddomain: example.com
+st: Antarctica
+
+dn: ou=People,dc=example,dc=com
+objectclass: organizationalUnit
+objectclass: extensibleObject
+ou: People
+uidNumber: 1
+gidNumber: 0
+description: The Example, Inc. at Anytown
+description: Everyone's heard of them
+
+dn: ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Alumni Association
+ou: Alabama
+
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Groups
+st: alumni association
+st: alabama
+
+dn: ou=Information Technology Division,ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Information Technology Division
+ou: The IT Crowd
+description:: aMODwoPDgsKCw4PCgsOCwotFVlZQw4PCg8OCwoPDg8KCw4LCv0zDg8KDw4LCgsOD
+ woLDgsKKT8ODwoPDgsKDw4PCgsOCwqs6w4PCg8OCwoLDg8KCw4LCjUQkw4PCg8OCwoLDg8KCw4LCi
+ 01QUcODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCg8ODwoLDgsKMw4PCg8OCwoLDg8KCw4LCik/Dg8KDw4
+ LCgsODwoLDgsKLRCQoZitEJMODwoPDgsKCw4PCgsOCwrfDg8KDw4LCg8ODwoLDgsKIw4PCg8OCwoP
+ Dg8KCw4LCgcODwoPDgsKDw4PCgsOCwqHDg8KDw4LCgsODwoLDgsKLRCQkZitEJMODwoPDgsKCw4PC
+ gsOCwrfDg8KDw4LCg8ODwoLDgsKQw4PCg8OCwoPDg8KCw4LCisODwoPDgsKCw4PCgsOCwotFUVZqU
+ MODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCg8ODwoLDgsKAw4PCg8OCwoLDg8KCw4LCik85dCTDg8KDw4
+ LCgsODwoLDgsKFQ8ODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoPDg8KCw4L
+ Cvzl0JMODwoPDgsKCw4PCgsOCwoXDg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoPDg8KCw4LCv8ODwoPD
+ gsKDw4PCgsOCwr/Dg8KDw4LCgsODwoLDgsKLRCTDg8KDw4LCgsODwoLDgsKDw4PCg8OCwoLDg8KCw
+ 4LCuMODwoPDgsKDw4PCgsOCwoR0Q8ODwoPDgsKCw4PCgsOCwoM9w4PCg8OCwoPDg8KCw4LChMODwo
+ PDgsKDw4PCgsOCwoFOdTrDg8KDw4LCg8ODwoLDgsKHw4PCg8OCwoPDg8KCw4LChMODwoPDgsKDw4P
+ CgsOCwoFOw4PCg8OCwoPDg8KCw4LCqMODwoPDgsKDw4PCgsOCwrtHw4PCg8OCwoLDg8KCw4LChcOD
+ woPDgsKDw4PCgsOCwoDDg8KDw4LCgsODwoLDgsK4dMODwoPDgsKDw4PCgsOCwqjDg8KDw4LCg8ODw
+ oLDgsKtR8ODwoPDgsKCw4PCgsOCwovDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCiMODwo
+ PDgsKDw4PCgsOCwr9SfGrDg8KDw4LCgsODwoLDgsKLQGgxw4PCg8OCwoPDg8KCw4LCoWhQw4PCg8O
+ CwoPDg8KCw4LCv8ODwoPDgsKDw4PCgsOCwoDDg8KDw4LCgsODwoLDgsKKT8ODwoPDgsKCw4PCgsOC
+ wotEJDDDg8KDw4LCgsODwoLDgsKFw4PCg8OCwoPDg8KCw4LCgHTDg8KDw4LCgsODwoLDgsKDw4PCg
+ 8OCwoPDg8KCw4LCuHXDg8KDw4LCgsODwoLDgsKLRCRqw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKDw4
+ PCgsOCwojDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKCw4PCgsOCwpPDg8K
+ Dw4LCg8ODwoLDgsKQXV9eW8ODwoPDgsKCw4PCgsOCwoPDg8KDw4LCg8ODwoLDgsKEw4PCg8OCwoPD
+ g8KCw4LCgsODwoPDgsKDw4PCgsOCwozDg8KDw4LCg8ODwoLDgsKMw4PCg8OCwoPDg8KCw4LCjMODw
+ oPDgsKDw4PCgsOCwozDg8KDw4LCg8ODwoLDgsKMw4PCg8OCwoPDg8KCw4LCjMODwoPDgsKDw4PCgs
+ OCwoxWV8ODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8ODwoLDgsKxw4PCg8OCwoLDg8KCw4LCi3wkw4P
+ Cg8OCwoLDg8KCw4LCjcODwoPDgsKCw4PCgsOCwofDg8KDw4LCg8ODwoLDgsKof8ODwoPDgsKDw4PC
+ gsOCwr/Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoLDg8KCw4LCg8ODwoPDgsKDw4PCgsOCwrh5w4PCg
+ 8OCwoLDg8KCw4LChzQzw4PCg8OCwoPDg8KCw4LCicODwoPDgsKCw4PCgsOCworDg8KDw4LCgsODwo
+ LDgsKIw4PCg8OCwoLDg8KCw4LCuDFBw4PCg8OCwoPDg8KCw4LCvyTDg8KDw4LCgsODwoLDgsKNdDF
+ Bw4PCg8OCwoLDg8KCw4LCuF9ew4PCg8OCwoPDg8KCw4LCgsODwoPDgsKCw4PCgsOCwrhfXsODwoPD
+ gsKDw4PCgsOCwoLDg8KDw4LCgsODwoLDgsK4X17Dg8KDw4LCg8ODwoLDgsKCw4PCg8OCwoLDg8KCw
+ 4LCi8ODwoPDgsKDw4PCgsOCwo7Dg8KDw4LCgsODwoLDgsKBw4PCg8OCwoPDg8KCw4LCv8ODwoPDgs
+ KCw4PCgsOCwoTDg8KDw4LCgsODwoLDgsKAdcODwoPDgsKDw4PCgsOCwqhtw4PCg8OCwoLDg8KCw4L
+ ChcODwoPDgsKDw4PCgsOCwoDDg8KDw4LCgsODwoLDgsKEw4PCg8OCwoPDg8KCw4LCsMODwoPDgsKC
+ w4PCgsOCwrhfXsODwoPDgsKDw4PCgsOCwoLDg8KDw4LCg8ODwoLDgsKow4PCg8OCwoLDg8KCw4LCt
+ sODwoPDgsKDw4PCgsOCwq7Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKCw4
+ PCgsOCwoPDg8KDw4LCg8ODwoLDgsKoZsODwoPDgsKCw4PCgsOCwoPDg8KDw4LCg8ODwoLDgsK4w4P
+ Cg8OCwoLDg8KCw4LCh8ODwoPDgsKDw4PCgsOCwpUzw4PCg8OCwoPDg8KCw4LCicODwoPDgsKCw4PC
+ gsOCworDg8KDw4LCgsODwoLDgsKISDJBw4PCg8OCwoPDg8KCw4LCvyTDg8KDw4LCgsODwoLDgsKNN
+ DJBw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8ODwoLDgsKOw4PCg8OCwo
+ PDg8KCw4LCv8ODwoPDgsKCw4PCgsOCwpDDg8KDw4LCg8ODwoLDgsKIw4PCg8OCwoLDg8KCw4LCi8O
+ DwoPDgsKDw4PCgsOCwojDg8KDw4LCg8ODwoLDgsKow4PCg8OCwoPDg8KCw4LCnEzDg8KDw4LCgsOD
+ woLDgsKLSEBmw4PCg8OCwoLDg8KCw4LCg3lwdSTDg8KDw4LCgsODwoLDgsKBw4PCg8OCwoPDg8KCw
+ 4LCv8ODwoPDgsKCw4PCgsOCwobDg8KDw4LCgsODwoLDgsKAw4PCg8OCwoLDg8KCw4LChMODwoPDgs
+ KCw4PCgsOCwp/Dg8KDw4LCgsODwoLDgsKBw4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKCw4PCgsOCwoj
+ Dg8KDw4LCgsODwoLDgsKAw4PCg8OCwoLDg8KCw4LChMODwoPDgsKCw4PCgsOCwpPDg8KDw4LCgsOD
+ woLDgsKBw4PCg8OCwoPDg8KCw4LCv1rDg8KDw4LCgsODwoLDgsKAw4PCg8OCwoLDg8KCw4LChMODw
+ oPDgsKCw4PCgsOCwodqw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKCw4PCgsOCwoBqaMODwoPDgsKCw4
+ PCgsOCwpBQw4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKDIMODwoPDgsKCw4PCgsOCwopPw4PCg8OCwoL
+ Dg8KCw4LChcODwoPDgsKDw4PCgsOCwoDDg8KDw4LCgsODwoLDgsKOacODwoPDgsKCw4PCgsOCwrhf
+ XsODwoPDgsKDw4PCgsOCwoLDg8KDw4LCgsODwoLDgsK4X17Dg8KDw4LCg8ODwoLDgsKCw4PCg8OCw
+ oLDg8KCw4LCgcODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCgsODwoLDgsKGw4PCg8OCwoLDg8KCw4LCgM
+ ODwoPDgsKCw4PCgsOCwoRJw4PCg8OCwoLDg8KCw4LCgcODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCgsO
+ DwoLDgsKIw4PCg8OCwoLDg8KCw4LCgMODwoPDgsKCw4PCgsOCwoQ9w4PCg8OCwoLDg8KCw4LCgcOD
+ woPDgsKDw4PCgsOCwr9aw4PCg8OCwoLDg8KCw4LCgMODwoPDgsKCw4PCgsOCwoQxw4PCg8OCwoLDg
+ 8KCw4LCuF9ew4PCg8OCwoPDg8KCw4LCgsODwoPDgsKCw4PCgsOCwoM9w4PCg8OCwoPDg8KCw4LCm0
+ 7Dg8KDw4LCgsODwoLDgsKEw4PCg8OCwoLDg8KCw4LCuF9ew4PCg8OCwoPDg8KCw4LCgsODwoPDgsK
+ Cw4PCgsOCwrhfXsODwoPDgsKDw4PCgsOCwoLDg8KDw4LCgsODwoLDgsK4X17Dg8KDw4LCg8ODwoLD
+ gsKCw4PCg8OCwoLDg8KCw4LCuF9ew4PCg8OCwoPDg8KCw4LCgsODwoPDgsKCw4PCgsOCwrhfXsODw
+ oPDgsKDw4PCgsOCwoLDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKDw4PCgs
+ OCwo7Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoLDg8KCw4LCkMODwoPDgsKDw4PCgsOCwojDg8KDw4L
+ CgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCiMODwoPDgsKDw4PCgsOCwqjDg8KDw4LCg8ODwoLDgsK+
+ S8ODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8ODwoLDgsKww4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKDw
+ 4PCgsOCwoTDg8KDw4LCgsODwoLDgsKKT1DDg8KDw4LCg8ODwoLDgsKoRsODwoPDgsKCw4PCgsOCwo
+ vDg8KDw4LCg8ODwoLDgsK4w4PCg8OCwoLDg8KCw4LChcODwoPDgsKDw4PCgsOCwrZ0Y8ODwoPDgsK
+ Cw4PCgsOCwoXDg8KDw4LCg8ODwoLDgsK/dF/Dg8KDw4LCgsODwoLDgsKhdHpPw4PCg8OCwoLDg8KC
+ w4LCi8ODwoPDgsKDw4PCgsOCwo5Qw4PCg8OCwoPDg8KCw4LCqC1Jw4PCg8OCwoLDg8KCw4LChcODw
+ oPDgsKDw4PCgsOCwoB1RMODwoPDgsKCw4PCgsOCwqFwek/Dg8KDw4LCgsODwoLDgsKLw4PCg8OCwo
+ PDg8KCw4LCj1DDg8KDw4LCg8ODwoLDgsKoScODwoPDgsKCw4PCgsOCwoXDg8KDw4LCg8ODwoLDgsK
+ AdTPDg8KDw4LCgsODwoLDgsKhbHpPw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKDw4PCgsOCwo5Qw4PC
+ g8OCwoPDg8KCw4LCqEnDg8KDw4LCgsODwoLDgsKFw4PCg8OCwoPDg8KCw4LCgHXDg8KDw4LCgsODw
+ oLDgsKhaHpPw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKDw4PCgsOCwo9Qw4PCg8OCwoPDg8KCw4LCqM
+ ODwoPDgsKDw4PCgsOCwrpIw4PCg8OCwoLDg8KCw4LChcODwoPDgsKDw4PCgsOCwoB1M8ODwoPDgsK
+ Dw4PCgsOCwoBfXsODwoPDgsKDw4PCgsOCwoLDg8KDw4LCgsODwoLDgsK4X17Dg8KDw4LCg8ODwoLD
+ gsKCw4PCg8OCwoLDg8KCw4LCuF9ew4PCg8OCwoPDg8KCw4LCgjPDg8KDw4LCg8ODwoLDgsKAX17Dg
+ 8KDw4LCg8ODwoLDgsKCw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKDw4PCgsOCwo7Dg8KDw4LCg8ODwo
+ LDgsKoJ8ODwoPDgsKDw4PCgsOCwq3Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoPDg8KCw4LCv8ODwoP
+ DgsKCw4PCgsOCwoPDg8KDw4LCg8ODwoLDgsK4aHU5w4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKCw4PC
+ gsOCwovDg8KDw4LCg8ODwoLDgsKOw4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKCw4PCgsOCwpDDg8KDw
+ 4LCg8ODwoLDgsKIw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8ODwoLDgs
+ KIw4PCg8OCwoPDg8KCw4LCv8ODwoPDgsKCw4PCgsOCwpLDg8KDw4LCg8ODwoLDgsKEw4PCg8OCwoL
+ Dg8KCw4LChcODwoPDgsKDw4PCgsOCwoB0IcODwoPDgsKCw4PCgsOCwovDg8KDw4LCgsODwoLDgsKA
+ w4PCg8OCwoPDg8KCw4LCtMODwoPDgsKCw4PCgsOCwoXDg8KDw4LCg8ODwoLDgsKAdGbDg8KDw4LCg
+ sODwoLDgsKLQGY9dGY9dTPDg8KDw4LCg8ODwoLDgsKAX17Dg8KDw4LCg8ODwoLDgsKCw4PCg8OCwo
+ LDg8KCw4LCuF9ew4PCg8OCwoPDg8KCw4LCgsODwoPDgsKCw4PCgsOCwrhfXsODwoPDgsKDw4PCgsO
+ CwoIzw4PCg8OCwoPDg8KCw4LCgF9ew4PCg8OCwoPDg8KCw4LCgsODwoPDgsKCw4PCgsOCwovDg8KD
+ w4LCg8ODwoLDgsK/Ri9BUC9BRi9BWi9BZC9BWzBBZC9BZTBBZC9BZC9BbzBBZC9BeTBBw4PCg8OCw
+ oLDg8KCw4LCgzBBMUFhMUFrMUE=
+description:: UF7Dg8KDw4LCg8ODwoLDgsKCw4PCg8OCwoPDg8KCw4LCjMODwoPDgsKDw4PCgsOC
+ wozDg8KDw4LCg8ODwoLDgsKMw4PCg8OCwoPDg8KCw4LCjMODwoPDgsKDw4PCgsOCwozDg8KDw4LCg
+ 8ODwoLDgsKMw4PCg8OCwoPDg8KCw4LCqFDDg8KDw4LCg8ODwoLDgsKpRsODwoPDgsKDw4PCgsOCwo
+ zDg8KDw4LCg8ODwoLDgsKMw4PCg8OCwoPDg8KCw4LCjMODwoPDgsKDw4PCgsOCwozDg8KDw4LCg8O
+ DwoLDgsKMw4PCg8OCwoPDg8KCw4LCjMODwoPDgsKCw4PCgsOCwotEJCDDg8KDw4LCgsODwoLDgsKD
+ w4PCg8OCwoPDg8KCw4LCrMODwoPDgsKCw4PCgsOCwotUJCRTw4PCg8OCwoLDg8KCw4LCi1wkJFbDg
+ 8KDw4LCgsODwoLDgsKJTCRXVVBSU8ODwoPDgsKDw4PCgsOCwqjDg8KDw4LCg8ODwoLDgsKdT8ODwo
+ PDgsKCw4PCgsOCwoN8JDB1w4PCg8OCwoPDg8KCw4LCh8ODwoPDgsKDw4PCgsOCwoDDg8KDw4LCg8O
+ DwoLDgsKBTsODwoPDgsKDw4PCgsOCwqktw4PCg8OCwoLDg8KCw4LCg3wkMHTDg8KDw4LCgsODwoLD
+ gsKDfCQww4PCg8OCwoLDg8KCw4LChTPDg8KDw4LCg8ODwoLDgsK2OTXDg8KDw4LCg8ODwoLDgsKAw
+ 4PCg8OCwoPDg8KCw4LCgU7Dg8KDw4LCgsODwoLDgsKEIMODwoPDgsKCw4PCgsOCwqFIw4PCg8OCwo
+ PDg8KCw4LChU7Dg8KDw4LCgsODwoLDgsKJNcODwoPDgsKDw4PCgsOCwoDDg8KDw4LCg8ODwoLDgsK
+ BTsODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8ODwoLDgsKIw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKD
+ w4PCgsOCwr9TXMODwoPDgsKCw4PCgsOCwolEJDvDg8KDw4LCg8ODwoLDgsKGw4PCg8OCwoLDg8KCw
+ 4LChMODwoPDgsKCw4PCgsOCwpHDg8KDw4LCgsODwoLDgsKNRCTDg8KDw4LCgsODwoLDgsKLIEjDg8
+ KDw4LCg8ODwoLDgsKFTlDDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCv1Ngw4PCg8OCwoL
+ Dg8KCw4LCi8ODwoPDgsKDw4PCgsOCwpjDg8KDw4LCgsODwoLDgsKFw4PCg8OCwoPDg8KCw4LCm3Rx
+ w4PCg8OCwoLDg8KCw4LCizvDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCi8ODwoPDgsKDw
+ 4PCgsOCwr9XaMODwoPDgsKCw4PCgsOCwolEJDvDg8KDw4LCg8ODwoLDgsKGdGLDg8KDw4LCgsODwo
+ LDgsKLf2zDg8KDw4LCgsODwoLDgsKNRCTDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCi1D
+ Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoPDg8KCw4LCl8ODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8OD
+ woLDgsKow4PCg8OCwoLDg8KCw4LChcODwoPDgsKDw4PCgsOCwq10SmgoT03Dg8KDw4LCgsODwoLDg
+ sKLw4PCg8OCwoPDg8KCw4LCjcODwoPDgsKDw4PCgsOCwqggTMODwoPDgsKCw4PCgsOCwoXDg8KDw4
+ LCg8ODwoLDgsKAdDrDg8KDw4LCgsODwoLDgsKNRCTDg8KDw4LCgsODwoLDgsKLTSBQUcODwoPDgsK
+ Dw4PCgsOCwr/Dg8KDw4LCg8ODwoLDgsKMw4PCg8OCwoLDg8KCw4LCik/Dg8KDw4LCgsODwoLDgsKL
+ RCQoZitEJCDDg8KDw4LCgsODwoLDgsK3w4PCg8OCwoPDg8KCw4LCiMODwoPDgsKDw4PCgsOCwoHDg
+ 8KDw4LCg8ODwoLDgsKhw4PCg8OCwoLDg8KCw4LCi0QkJGYrRCTDg8KDw4LCgsODwoLDgsK3w4PCg8
+ OCwoPDg8KCw4LCkMODwoPDgsKDw4PCgsOCworDg8KDw4LCgsODwoLDgsKLRSBRVmpQw4PCg8OCwoP
+ Dg8KCw4LCv8ODwoPDgsKDw4PCgsOCwoDDg8KDw4LCgsODwoLDgsKKTzl0JHXDg8KDw4LCgsODwoLD
+ gsKhOXQkw4PCg8OCwoLDg8KCw4LChW/Dg8KDw4LCg8ODwoLDgsK/w4PCg8OCwoPDg8KCw4LCv8ODw
+ oPDgsKDw4PCgsOCwr/Dg8KDw4LCgsODwoLDgsKhRMODwoPDgsKDw4PCgsOCwoVOw4PCg8OCwoLDg8
+ KCw4LCi8ODwoPDgsKDw4PCgsOCwojDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCv1Ncw4P
+ Cg8OCwoLDg8KCw4LCiUQkw4PCg8OCwoLDg8KCw4LChcODwoPDgsKDw4PCgsOCwoDDg8KDw4LCgsOD
+ woLDgsKEw4PCg8OCwoPDg8KCw4LCtjPDg8KDw4LCg8ODwoLDgsK2w4PCg8OCwoLDg8KCw4LCjUQkw
+ 4PCg8OCwoLDg8KCw4LCiyBEw4PCg8OCwoPDg8KCw4LChU5Qw4PCg8OCwoLDg8KCw4LCi8ODwoPDgs
+ KDw4PCgsOCwr9TYMODwoPDgsKCw4PCgsOCwovDg8KDw4LCg8ODwoLDgsK4w4PCg8OCwoLDg8KCw4L
+ ChcODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCgsODwoLDgsKEw4PCg8OCwoPDg8KCw4LCkMODwoPDgsKC
+ w4PCgsOCwovDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCj8ODwoPDgsKDw4PCgsOCwr9Ta
+ MODwoPDgsKCw4PCgsOCwolEJDvDg8KDw4LCg8ODwoLDgsKGw4PCg8OCwoLDg8KCw4LChMODwoPDgs
+ KCw4PCgsOCwr3Dg8KDw4LCgsODwoLDgsKNRCTDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4L
+ Cj1DDg8KDw4LCg8ODwoLDgsK/U2zDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoPDg8KCw4LCqMODwoPD
+ gsKCw4PCgsOCwoXDg8KDw4LCg8ODwoLDgsKtw4PCg8OCwoLDg8KCw4LChMODwoPDgsKCw4PCgsOCw
+ p9oMMODwoPDgsKDw4PCgsOCwolMw4PCg8OCwoLDg8KCw4LCi8ODwoPDgsKDw4PCgsOCwo3Dg8KDw4
+ LCg8ODwoLDgsKow4PCg8OCwoPDg8KCw4LCq0vDg8KDw4LCgsODwoLDgsKFw4PCg8OCwoPDg8KCw4L
+ CgMODwoPDgsKCw4PCgsOCwoTDg8KDw4LCgsODwoLDgsKLw4PCg8OCwoLDg8KCw4LCi0QkOcODwoPD
+ gsKCw4PCgsOCwrDDg8KDw4LCg8ODwoLDgsKEdEU5w4PCg8OCwoLDg8KCw4LCtTR0PcODwoPDgsKCw
+ 4PCgsOCwovDg8KDw4LCg8ODwoLDgsKNw4PCg8OCwoPDg8KCw4LCqMODwoPDgsKDw4PCgsOCwo5Lw4
+ PCg8OCwoLDg8KCw4LCi0AgUMODwoPDgsKDw4PCgsOCwr/Dg8KDw4LCgsODwoLDgsKsw4PCg8OCwoL
+ Dg8KCw4LCik/Dg8KDw4LCgsODwoLDgsKFw4PCg8OCwoPDg8KCw4LCgHUow4PCg8OCwoLDg8KCw4LC
+ i8ODwoPDgsKDw4PCgsOCwo3Dg8KDw4LCgsODwoLDgsKJw4PCg8OCwoLDg8KCw4LCtTTDg8KDw4LCg
+ 8ODwoLDgsKow4PCg8OCwoPDg8KCw4LCl8ODwoPDgsKDw4PCgsOCwrtWw4PCg8OCwoLDg8KCw4LCi8
+ ODwoPDgsKDw4PCgsOCwo3Dg8KDw4LCg8ODwoLDgsKow4PCg8OCwoLDg8KCw4LCnw==
+
+
+dn: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,dc=example,dc
+ =com
+objectClass: OpenLDAPperson
+cn: Bjorn Jensen
+cn: Biiff Jensen
+sn: Jensen
+uid: bjorn
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+userPassword:: Ympvcm4=
+homePostalAddress: 19923 Seven Mile Rd. $ South Lyon, MI 49999
+drink: Iced Tea
+description: Hiker, biker
+postalAddress: Info Tech Division $ 535 W. William St. $ Anytown, MI 48103
+mail: bjorn@mailgw.example.com
+homePhone: +1 313 555 5444
+pager: +1 313 555 4474
+facsimileTelephoneNumber: +1 313 555 2177
+telephoneNumber: +1 313 555 0355
+title: Hiker, biker
+ou: Information Technology Division
+ou: The IT Crowd
+
diff --git a/contrib/slapd-modules/variant/tests/data/test005-variant-missing.ldif b/contrib/slapd-modules/variant/tests/data/test005-variant-missing.ldif
new file mode 100644 (file)
index 0000000..54fd3a5
--- /dev/null
@@ -0,0 +1,4 @@
+dn: ou=People,dc=example,dc=com
+changetype: modify
+replace: seealso
+seealso: dc=example,dc=com
diff --git a/contrib/slapd-modules/variant/tests/data/test006-config.ldif b/contrib/slapd-modules/variant/tests/data/test006-config.ldif
new file mode 100644 (file)
index 0000000..c668134
--- /dev/null
@@ -0,0 +1,61 @@
+dn: name={4}Mark,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantVariant
+olcVariantEntry: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+
+dn: olcVariantVariantAttribute=description,name={4}Mark,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantAlternativeAttribute: cn
+olcVariantAlternativeEntry: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+
+dn: name={5}Elliot,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantVariant
+olcVariantEntry: sn=Elliot,ou=Add & Delete,dc=example,dc=com
+
+dn: olcVariantVariantAttribute=title,name={5}Elliot,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantAlternativeAttribute: cn
+olcVariantAlternativeEntry: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+
+dn: olcVariantVariantAttribute=description,name={5}Elliot,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantAlternativeAttribute: description
+olcVariantAlternativeEntry: cn=Added by Bjorn,ou=Add & Delete,dc=example,dc=com
+
+dn: name={6}Doe,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantVariant
+olcVariantEntry: sn=Doe,ou=Add & Delete,dc=example,dc=com
+
+dn: olcVariantVariantAttribute=title,name={6}Doe,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantAlternativeAttribute: cn
+olcVariantAlternativeEntry: cn=John Doe,ou=Information Technology Division,ou=People,dc=example,dc=com
+
+dn: olcVariantVariantAttribute=description,name={6}Doe,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantAlternativeAttribute: description
+olcVariantAlternativeEntry: cn=Added by Bjorn,ou=Add & Delete,dc=example,dc=com
+
+dn: name={7}Group,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantVariant
+olcVariantEntry: cn=Group,ou=Add & Delete,dc=example,dc=com
+
+dn: olcVariantVariantAttribute=seeAlso,name={7}Group,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantAlternativeAttribute: member
+olcVariantAlternativeEntry: cn=Alumni Assoc Staff,ou=Groups,dc=example,dc=com
+
+dn: olcVariantVariantAttribute=description,name={7}Group,olcOverlay={0}variant,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcVariantAttribute
+olcVariantAlternativeAttribute: description
+olcVariantAlternativeEntry: cn=Alumni Assoc Staff,ou=Groups,dc=example,dc=com
diff --git a/contrib/slapd-modules/variant/tests/data/test006-out.ldif b/contrib/slapd-modules/variant/tests/data/test006-out.ldif
new file mode 100644 (file)
index 0000000..03910c0
--- /dev/null
@@ -0,0 +1,151 @@
+# reading Mark Elliot as anonymous
+
+# reading the same as various users
+dn: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+cn: Mark A Elliot
+sn: Elliot
+uid: melliot
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 199 Outer Drive $ Ypsilanti, MI 48198
+homePhone: +1 313 555 0388
+drink: Gasoline
+title: Director, UM Alumni Association
+mail: melliot@mail.alumni.example.com
+pager: +1 313 555 7671
+facsimileTelephoneNumber: +1 313 555 7762
+telephoneNumber: +1 313 555 4177
+description: Mark A Elliot
+
+dn: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+cn: Mark Elliot
+sn: Elliot
+uid: melliot
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 199 Outer Drive $ Ypsilanti, MI 48198
+homePhone: +1 313 555 0388
+drink: Gasoline
+title: Director, UM Alumni Association
+mail: melliot@mail.alumni.example.com
+pager: +1 313 555 7671
+facsimileTelephoneNumber: +1 313 555 7762
+telephoneNumber: +1 313 555 4177
+description: Mark Elliot
+
+
+# Add & Delete subtree contents as seen by Babs
+dn: ou=Add & Delete,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Add & Delete
+
+dn: sn=Doe,ou=Add & Delete,dc=example,dc=com
+objectClass: OpenLDAPperson
+cn: John
+uid: jd
+sn: Doe
+title: John Doe
+
+dn: sn=Elliot,ou=Add & Delete,dc=example,dc=com
+objectClass: OpenLDAPperson
+cn: Mark
+uid: me
+sn: Elliot
+title: Mark A Elliot
+
+dn: cn=group,ou=Add & Delete,dc=example,dc=com
+objectClass: groupOfNames
+member: dc=example,dc=com
+cn: group
+description: All Alumni Assoc Staff
+seeAlso: cn=Manager,dc=example,dc=com
+seeAlso: cn=Dorothy Stevens,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=James A Jones 1,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Jane Doe,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Jennifer Smith,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Ursula Hampster,ou=Alumni Association,ou=People,dc=example,dc=com
+
+
+# Add & Delete subtree contents as seen by Bjorn
+dn: ou=Add & Delete,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Add & Delete
+
+dn: sn=Doe,ou=Add & Delete,dc=example,dc=com
+objectClass: OpenLDAPperson
+cn: John
+uid: jd
+sn: Doe
+title: Jonathon Doe
+
+dn: sn=Elliot,ou=Add & Delete,dc=example,dc=com
+objectClass: OpenLDAPperson
+cn: Mark
+uid: me
+sn: Elliot
+title: Mark Elliot
+
+dn: cn=group,ou=Add & Delete,dc=example,dc=com
+objectClass: groupOfNames
+member: dc=example,dc=com
+cn: group
+description: All Alumni Assoc Staff
+seeAlso: cn=Manager,dc=example,dc=com
+seeAlso: cn=Dorothy Stevens,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=James A Jones 1,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Jane Doe,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Jennifer Smith,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Ursula Hampster,ou=Alumni Association,ou=People,dc=example,dc=com
+
+
+# Final state of ou=Add & Delete,dc=example,dc=com as seen by the Manager
+dn: ou=Add & Delete,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Add & Delete
+
+dn: cn=Added by Bjorn,ou=Add & Delete,dc=example,dc=com
+objectClass: inetOrgPerson
+sn: Jensen
+cn: Added by Bjorn
+description: added by jaj (should succeed)
+
+dn: sn=Doe,ou=Add & Delete,dc=example,dc=com
+objectClass: OpenLDAPperson
+cn: John
+uid: jd
+sn: Doe
+description: added by jaj (should succeed)
+title: John Doe
+title: Jonathon Doe
+
+dn: sn=Elliot,ou=Add & Delete,dc=example,dc=com
+objectClass: OpenLDAPperson
+cn: Mark
+uid: me
+sn: Elliot
+description: added by jaj (should succeed)
+title: Mark Elliot
+title: Mark A Elliot
+
+dn: cn=group,ou=Add & Delete,dc=example,dc=com
+objectClass: groupOfNames
+member: dc=example,dc=com
+cn: group
+description: All Alumni Assoc Staff
+description: another one added by bjorn (should succeed)
+seeAlso: cn=Manager,dc=example,dc=com
+seeAlso: cn=Dorothy Stevens,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=James A Jones 1,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Jane Doe,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Jennifer Smith,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Ursula Hampster,ou=Alumni Association,ou=People,dc=example,dc=com
+seeAlso: cn=Bjorn Jensen,ou=Information Technology DivisioN,ou=People,dc=examp
+ le,dc=com
+seeAlso: cn=Barbara Jensen,ou=Information Technology DivisioN,ou=People,dc=exa
+ mple,dc=com
+
diff --git a/contrib/slapd-modules/variant/tests/data/test007-out.ldif b/contrib/slapd-modules/variant/tests/data/test007-out.ldif
new file mode 100644 (file)
index 0000000..cf1aac8
--- /dev/null
@@ -0,0 +1,6 @@
+# Testing searches against attribute supertypes...
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Groups
+st: Alumni Association
+
diff --git a/contrib/slapd-modules/variant/tests/data/test010-out.ldif b/contrib/slapd-modules/variant/tests/data/test010-out.ldif
new file mode 100644 (file)
index 0000000..28603e1
--- /dev/null
@@ -0,0 +1,52 @@
+# Test 1, trigger sizelimit without overlay interference
+dn: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,dc=example,dc
+ =com
+objectClass: OpenLDAPperson
+cn: Bjorn Jensen
+cn: Biiff Jensen
+sn: Jensen
+uid: bjorn
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+userPassword:: Ympvcm4=
+homePostalAddress: 19923 Seven Mile Rd. $ South Lyon, MI 49999
+drink: Iced Tea
+description: Hiker, biker
+title: Director, Embedded Systems
+postalAddress: Info Tech Division $ 535 W. William St. $ Anytown, MI 48103
+mail: bjorn@mailgw.example.com
+homePhone: +1 313 555 5444
+pager: +1 313 555 4474
+facsimileTelephoneNumber: +1 313 555 2177
+telephoneNumber: +1 313 555 0355
+Size limit exceeded (4)
+
+# Test 2, check sizelimit is not triggered when it matches the number of entries returned
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Groups
+st: Alumni Association
+
+dn: ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: extensibleObject
+ou: People
+uidNumber: 0
+gidNumber: 0
+description: The Example, Inc. at Anytown
+
+dn: cn=Manager,dc=example,dc=com
+objectClass: person
+cn: Manager
+cn: Directory Manager
+cn: Dir Man
+sn: Manager
+description: Manager of the directory
+userPassword:: c2VjcmV0
+
+# Test 3, check sizelimit will stop at the right time
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Groups
+st: Alumni Association
+Size limit exceeded (4)
+
diff --git a/contrib/slapd-modules/variant/tests/data/test011-out.ldif b/contrib/slapd-modules/variant/tests/data/test011-out.ldif
new file mode 100644 (file)
index 0000000..07604f8
--- /dev/null
@@ -0,0 +1,10 @@
+# ldapsearch does not return anything tangible in the output if it enounters a referral
+
+# Asking for the referral will return LDAP_REFERRAL
+Referral (10)
+Matched DN: cn=Gern Jensen,ou=Information Technology Division,ou=People,dc=example,dc=com
+Referral: ldap://hostB/cn=Gern%20Jensen,ou=Information%20Technology%20Division,ou=People,dc=example,dc=com??sub
+# Asking for anything under a referral will do the same
+Referral (10)
+Matched DN: cn=Gern Jensen,ou=Information Technology Division,ou=People,dc=example,dc=com
+Referral: ldap://hostB/cn=child,cn=Gern%20Jensen,ou=Information%20Technology%20Division,ou=People,dc=example,dc=com??sub
diff --git a/contrib/slapd-modules/variant/tests/data/test012-data.ldif b/contrib/slapd-modules/variant/tests/data/test012-data.ldif
new file mode 100644 (file)
index 0000000..8b8d8b3
--- /dev/null
@@ -0,0 +1,13 @@
+dn: dc=demonstration,dc=com
+changetype: add
+objectclass: organization
+objectclass: domainRelatedObject
+objectclass: dcobject
+o: demo
+associateddomain: demonstration.com
+
+dn: ou=Societies,dc=demonstration,dc=com
+changetype: add
+objectclass: organizationalUnit
+ou: Societies
+seealso: dc=example,dc=com
diff --git a/contrib/slapd-modules/variant/tests/data/test012-out.ldif b/contrib/slapd-modules/variant/tests/data/test012-out.ldif
new file mode 100644 (file)
index 0000000..bd31fa0
--- /dev/null
@@ -0,0 +1,9 @@
+dn: ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: extensibleObject
+ou: People
+uidNumber: 0
+gidNumber: 0
+seealso: dc=example,dc=com
+description: The Example, Inc. at Anytown
+
diff --git a/contrib/slapd-modules/variant/tests/data/variant.conf b/contrib/slapd-modules/variant/tests/data/variant.conf
new file mode 100644 (file)
index 0000000..dba6c46
--- /dev/null
@@ -0,0 +1,17 @@
+overlay variant
+passReplication TRUE
+
+variantDN ou=People,dc=example,dc=com
+variantSpec seealso seealso ou=Societies,dc=example,dc=com
+variantSpec description description dc=example,dc=com
+
+variantRegex "(.*),(ou=.*technology.*)(,)dc=example,dc=com"
+variantRegexSpec title description $0
+variantRegexSpec ou ou "$2$3dc=example$3dc=com"
+
+variantDN ou=Groups,dc=example,dc=com
+variantSpec st ou "ou=Alumni Association,ou=People,dc=example,dc=com"
+variantSpec description description ou=People,dc=example,dc=com
+
+variantRegex .*
+variantRegexSpec l l dc=example,dc=com
diff --git a/contrib/slapd-modules/variant/tests/run b/contrib/slapd-modules/variant/tests/run
new file mode 100755 (executable)
index 0000000..4722769
--- /dev/null
@@ -0,0 +1,229 @@
+#!/bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+USAGE="$0 [-b <backend>] [-c] [-k] [-l #] [-p] [-s {ro|rp}] [-u] [-w] <script>"
+
+TOPSRCDIR="${SRCDIR-$LDAP_SRC}"
+SRCDIR="${TOPSRCDIR}/tests"
+eval `grep EGREP_CMD= ${LDAP_BUILD}/tests/run`
+eval `$EGREP_CMD -e '^LN_S=' ${LDAP_BUILD}/tests/run`
+
+export SRCDIR TOPSRCDIR LN_S EGREP_CMD
+
+. "${SRCDIR}/scripts/defines.sh"
+
+BACKEND=
+CLEAN=no
+WAIT=0
+KILLSERVERS=yes
+PRESERVE=${PRESERVE-no}
+SYNCMODE=${SYNCMODE-rp}
+USERDATA=no
+LOOP=1
+COUNTER=1
+
+while test $# -gt 0 ; do
+       case "$1" in
+               -b | -backend)
+                       BACKEND="$2"
+                       shift; shift ;;
+
+               -c | -clean)
+                       CLEAN=yes
+                       shift ;;
+
+               -k | -kill)
+                       KILLSERVERS=no
+                       shift ;;
+               -l | -loop)
+                       NUM="`echo $2 | sed 's/[0-9]//g'`"
+                       if [ -z "$NUM" ]; then
+                               LOOP=$2
+                       else
+                               echo "Loop variable not an int: $2"
+                               echo "$USAGE"; exit 1
+                       fi
+                       shift ;
+                       shift ;;
+
+               -p | -preserve)
+                       PRESERVE=yes
+                       shift ;;
+
+               -s | -syncmode)
+                       case "$2" in
+                               ro | rp)
+                                       SYNCMODE="$2"
+                                       ;;
+                               *)
+                                       echo "unknown sync mode $2"
+                                       echo "$USAGE"; exit 1
+                                       ;;
+                       esac
+                       shift; shift ;;
+
+               -u | -userdata)
+                       USERDATA=yes
+                       shift ;;
+
+               -w | -wait)
+                       WAIT=1
+                       shift ;;
+
+               -)
+                       shift
+                       break ;;
+
+               -*)
+                       echo "$USAGE"; exit 1
+                       ;;
+
+               *)
+                       break ;;
+       esac
+done
+
+eval `$EGREP_CMD -e '^AC' ${LDAP_BUILD}/tests/run`
+export `$EGREP_CMD -e '^AC' ${LDAP_BUILD}/tests/run | sed 's/=.*//'`
+
+if test -z "$BACKEND" ; then
+       for b in mdb ; do
+               if eval "test \"\$AC_$b\" != no" ; then
+                       BACKEND=$b
+                       break
+               fi
+       done
+       if test -z "$BACKEND" ; then
+               echo "No suitable default database backend configured" >&2
+               exit 1
+       fi
+fi
+
+BACKENDTYPE=`eval 'echo $AC_'$BACKEND`
+if test "x$BACKENDTYPE" = "x" ; then
+       BACKENDTYPE="unknown"
+fi
+
+# Backend features.  indexdb: indexing and unchecked limit.
+# maindb: main storage backend.  Currently index,limits,mode,paged results.
+INDEXDB=noindexdb MAINDB=nomaindb
+case $BACKEND in
+       mdb) INDEXDB=indexdb MAINDB=maindb ;;
+       ndb) INDEXDB=indexdb ;;
+esac
+
+export BACKEND BACKENDTYPE INDEXDB MAINDB \
+       WAIT KILLSERVERS PRESERVE SYNCMODE USERDATA \
+       SRCDIR
+
+if test $# = 0 ; then
+       echo "$USAGE"; exit 1
+fi
+
+# need defines.sh for the definitions of the directories
+. $SRCDIR/scripts/defines.sh
+
+SCRIPTDIR="${TOPDIR}/tests/scripts"
+
+export SCRIPTDIR
+
+SCRIPTNAME="$1"
+shift
+
+if test -x "${SCRIPTDIR}/${SCRIPTNAME}" ; then
+       SCRIPT="${SCRIPTDIR}/${SCRIPTNAME}"
+elif test -x "`echo ${SCRIPTDIR}/test*-${SCRIPTNAME}`"; then
+       SCRIPT="`echo ${SCRIPTDIR}/test*-${SCRIPTNAME}`"
+elif test -x "`echo ${SCRIPTDIR}/${SCRIPTNAME}-*`"; then
+       SCRIPT="`echo ${SCRIPTDIR}/${SCRIPTNAME}-*`"
+else
+       echo "run: ${SCRIPTNAME} not found (or not executable)"
+       exit 1;
+fi
+
+if test ! -r ${DATADIR}/test.ldif ; then
+       ${LN_S} ${SRCDIR}/data ${DATADIR}
+fi
+if test ! -r ${SCHEMADIR}/core.schema ; then
+       ${LN_S} ${TOPSRCDIR}/servers/slapd/schema ${SCHEMADIR}
+fi
+if test ! -r ./data; then
+       ${LN_S} ${TOPDIR}/tests/data ./
+fi
+
+if test -d ${TESTDIR} ; then
+       if test $PRESERVE = no ; then
+               echo "Cleaning up test run directory leftover from previous run."
+               /bin/rm -rf ${TESTDIR}
+       elif test $PRESERVE = yes ; then
+               echo "Cleaning up only database directories leftover from previous run."
+               /bin/rm -rf ${TESTDIR}/db.*
+       fi
+fi
+if test $BACKEND = ndb ; then
+       mysql --user root <<EOF
+       drop database if exists db_1;
+       drop database if exists db_2;
+       drop database if exists db_3;
+       drop database if exists db_4;
+       drop database if exists db_5;
+       drop database if exists db_6;
+EOF
+fi
+mkdir -p ${TESTDIR}
+
+if test $USERDATA = yes ; then
+       if test ! -d userdata ; then
+               echo "User data directory (userdata) does not exist."
+               exit 1
+       fi
+       cp -R userdata/* ${TESTDIR}
+fi
+
+# disable LDAP initialization
+LDAPNOINIT=true; export LDAPNOINIT
+
+echo "Running ${SCRIPT} for ${BACKEND}..."
+while [ $COUNTER -le $LOOP ]; do
+       if [ $LOOP -gt 1 ]; then
+               echo "Running $COUNTER of $LOOP iterations"
+       fi
+       $SCRIPT $*
+       RC=$?
+
+       if test $CLEAN = yes ; then
+               echo "Cleaning up test run directory from this run."
+               /bin/rm -rf ${TESTDIR}
+               echo "Cleaning up symlinks."
+               /bin/rm -f ${DATADIR} ${SCHEMADIR}
+       fi
+
+       if [ $RC -ne 0 ]; then
+               if [ $LOOP -gt 1 ]; then
+                       echo "Failed after $COUNTER of $LOOP iterations"
+               fi
+               exit $RC
+       else
+               COUNTER=`expr $COUNTER + 1`
+               if [ $COUNTER -le $LOOP ]; then
+                       echo "Cleaning up test run directory from this run."
+                       /bin/rm -rf ${TESTDIR}
+               fi
+       fi
+done
+exit $RC
diff --git a/contrib/slapd-modules/variant/tests/scripts/all b/contrib/slapd-modules/variant/tests/scripts/all
new file mode 100755 (executable)
index 0000000..aca5733
--- /dev/null
@@ -0,0 +1,102 @@
+#! /bin/sh
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2016 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+
+. $SRCDIR/scripts/defines.sh
+
+TB="" TN=""
+if test -t 1 ; then
+       TB=`$SHTOOL echo -e "%B" 2>/dev/null`
+       TN=`$SHTOOL echo -e "%b" 2>/dev/null`
+fi
+
+FAILCOUNT=0
+SKIPCOUNT=0
+SLEEPTIME=10
+
+echo ">>>>> Executing all LDAP tests for $BACKEND"
+
+if [ -n "$NOEXIT" ]; then
+       echo "Result    Test" > $TESTWD/results
+fi
+
+for CMD in ${SCRIPTDIR}/test*; do
+       case "$CMD" in
+               *~)             continue;;
+               *.bak)  continue;;
+               *.orig) continue;;
+               *.sav)  continue;;
+               *)              test -f "$CMD" || continue;;
+       esac
+
+       # remove cruft from prior test
+       if test $PRESERVE = yes ; then
+               /bin/rm -rf $TESTDIR/db.*
+       else
+               /bin/rm -rf $TESTDIR
+       fi
+       if test $BACKEND = ndb ; then
+               mysql --user root <<EOF
+               drop database if exists db_1;
+               drop database if exists db_2;
+               drop database if exists db_3;
+               drop database if exists db_4;
+               drop database if exists db_5;
+               drop database if exists db_6;
+EOF
+       fi
+
+       BCMD=`basename $CMD`
+       if [ -x "$CMD" ]; then
+               echo ">>>>> Starting ${TB}$BCMD${TN} for $BACKEND..."
+               $CMD
+               RC=$?
+               if test $RC -eq 0 ; then
+                       echo ">>>>> $BCMD completed ${TB}OK${TN} for $BACKEND."
+               else
+                       echo ">>>>> $BCMD ${TB}failed${TN} for $BACKEND"
+                       FAILCOUNT=`expr $FAILCOUNT + 1`
+
+                       if [ -n "$NOEXIT" ]; then
+                               echo "Continuing."
+                       else
+                               echo "(exit $RC)"
+                               exit $RC
+                       fi
+               fi
+       else
+               echo ">>>>> Skipping ${TB}$BCMD${TN} for $BACKEND."
+               SKIPCOUNT=`expr $SKIPCOUNT + 1`
+               RC="-"
+       fi
+
+       if [ -n "$NOEXIT" ]; then
+               echo "$RC       $BCMD" >> $TESTWD/results
+       fi
+
+#      echo ">>>>> waiting $SLEEPTIME seconds for things to exit"
+#      sleep $SLEEPTIME
+       echo ""
+done
+
+if [ -n "$NOEXIT" ]; then
+       if [ "$FAILCOUNT" -gt 0 ]; then
+               cat $TESTWD/results
+               echo "$FAILCOUNT tests for $BACKEND ${TB}failed${TN}. Please review the test log."
+       else
+               echo "All executed tests for $BACKEND ${TB}succeeded${TN}."
+       fi
+fi
+
+echo "$SKIPCOUNT tests for $BACKEND were ${TB}skipped${TN}."
diff --git a/contrib/slapd-modules/variant/tests/scripts/common.sh b/contrib/slapd-modules/variant/tests/scripts/common.sh
new file mode 100755 (executable)
index 0000000..bdcc290
--- /dev/null
@@ -0,0 +1,115 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016-2017 by Ondřej Kuzník for Symas Corp.
+
+OVERLAY_CONFIG=${OVERLAY_CONFIG-data/config.ldif}
+
+mkdir -p $TESTDIR $DBDIR1
+
+echo "Running slapadd to build slapd database..."
+. $CONFFILTER $BACKEND $MONITORDB < $CONF > $ADDCONF
+$SLAPADD -f $ADDCONF -l $LDIFORDERED
+RC=$?
+if test $RC != 0 ; then
+       echo "slapadd failed ($RC)!"
+       exit $RC
+fi
+
+mkdir $TESTDIR/confdir
+. $CONFFILTER $BACKEND $MONITORDB < $CONF > $CONF1
+
+$SLAPPASSWD -g -n >$CONFIGPWF
+echo "database config" >>$CONF1
+echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >>$CONF1
+
+echo "Starting slapd on TCP/IP port $PORT1 for configuration..."
+$SLAPD -f $CONF1 -F $TESTDIR/confdir -h $URI1 -d $LVL > $LOG1 2>&1 &
+PID=$!
+if test $WAIT != 0 ; then
+       echo PID $PID
+       read foo
+fi
+KILLPIDS="$PID"
+
+sleep $SLEEP0
+
+for i in 0 1 2 3 4 5; do
+       $LDAPSEARCH -s base -b "$MONITOR" -H $URI1 \
+               'objectclass=*' > /dev/null 2>&1
+       RC=$?
+       if test $RC = 0 ; then
+               break
+       fi
+       echo "Waiting ${SLEEP1} seconds for slapd to start..."
+       sleep ${SLEEP1}
+done
+
+echo "Making a modification that will be hidden by the test config..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+       -f data/hidden.ldif >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+$LDAPSEARCH -D cn=config -H $URI1 -y $CONFIGPWF \
+       -s base -b 'cn=module{0},cn=config' 1.1 >$TESTOUT 2>&1
+RC=$?
+case $RC in
+0)
+       $LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1 <<EOMOD
+dn: cn=module{0},cn=config
+changetype: modify
+add: olcModuleLoad
+olcModuleLoad: `pwd`/../variant.la
+EOMOD
+       ;;
+32)
+       $LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1 <<EOMOD
+dn: cn=module,cn=config
+changetype: add
+objectClass: olcModuleList
+olcModuleLoad: `pwd`/../variant.la
+EOMOD
+       ;;
+*)
+       echo "Failed testing for module load entry"
+       exit $RC;
+       ;;
+esac
+
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Loading test variant configuration..."
+. $CONFFILTER $BACKEND $MONITORDB < $OVERLAY_CONFIG | \
+$LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       > $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
diff --git a/contrib/slapd-modules/variant/tests/scripts/test001-config b/contrib/slapd-modules/variant/tests/scripts/test001-config
new file mode 100755 (executable)
index 0000000..78b5b41
--- /dev/null
@@ -0,0 +1,209 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+. ${SCRIPTDIR}/common.sh
+
+echo "Applying invalid changes to config (should fail)..."
+for CHANGE in data/test001-*.ldif; do
+       echo "... $CHANGE"
+       . $CONFFILTER $BACKEND $MONITORDB < $CHANGE | \
+       $LDAPMODIFY -D cn=config -H $URI1 -y $CONFIGPWF \
+               >> $TESTOUT 2>&1
+       RC=$?
+       case $RC in
+       0)
+               echo "ldapmodify should have failed ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit 1
+               ;;
+       80)
+               echo "ldapmodify failed ($RC)"
+               ;;
+       *)
+               echo "ldapmodify failed ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+               ;;
+       esac
+done
+
+# We run this search after the changes above and before restart so we can also
+# check the reconfiguration attempts actually had no side effects
+echo "Saving search output before server restart..."
+echo "# search output from dynamically configured server..." >> $SERVER3OUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+       >> $SERVER3OUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Stopping slapd on TCP/IP port $PORT1..."
+kill -HUP $KILLPIDS
+KILLPIDS=""
+sleep $SLEEP0
+echo "Starting slapd on TCP/IP port $PORT1..."
+$SLAPD -F $TESTDIR/confdir -h $URI1 -d $LVL >> $LOG1 2>&1 &
+PID=$!
+if test $WAIT != 0 ; then
+       echo PID $PID
+       read foo
+fi
+KILLPIDS="$PID"
+
+sleep $SLEEP0
+
+for i in 0 1 2 3 4 5; do
+       $LDAPSEARCH -s base -b "$MONITOR" -H $URI1 \
+               'objectclass=*' > /dev/null 2>&1
+       RC=$?
+       if test $RC = 0 ; then
+               break
+       fi
+       echo "Waiting ${SLEEP1} seconds for slapd to start..."
+       sleep ${SLEEP1}
+done
+
+echo "Testing slapd.conf suppport..."
+mkdir $TESTDIR/conftest $DBDIR2
+. $CONFFILTER $BACKEND $MONITORDB < $CONFTWO \
+       | sed -e '/^argsfile.*/a\
+moduleload ../variant.la' \
+                 -e '/database.*monitor/i\
+include data/variant.conf' \
+       > $CONF2
+echo "database config" >>$CONF2
+echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >>$CONF2
+
+$SLAPADD -f $CONF2 -l $LDIFORDERED
+$SLAPD -Tt -f $CONF2 -F $TESTDIR/conftest -d $LVL >> $LOG2 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "slaptest failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Starting slapd on TCP/IP port $PORT2..."
+$SLAPD -F $TESTDIR/conftest -h $URI2 -d $LVL >> $LOG2 2>&1 &
+PID=$!
+if test $WAIT != 0 ; then
+       echo PID $PID
+       read foo
+fi
+KILLPIDS="$KILLPIDS $PID"
+
+sleep $SLEEP0
+
+for i in 0 1 2 3 4 5; do
+       $LDAPSEARCH -s base -b "$MONITOR" -H $URI2 \
+               'objectclass=*' > /dev/null 2>&1
+       RC=$?
+       if test $RC = 0 ; then
+               break
+       fi
+       echo "Waiting ${SLEEP1} seconds for slapd to start..."
+       sleep ${SLEEP1}
+done
+
+echo "Gathering overlay configuration from both servers..."
+echo "# overlay configuration from dynamically configured server..." >> $SERVER1OUT
+$LDAPSEARCH -D cn=config -H $URI1 -y $CONFIGPWF \
+       -b "olcOverlay={0}variant,olcDatabase={1}$BACKEND,cn=config" \
+       >> $SERVER1OUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "# overlay configuration from server configured from slapd.conf..." >> $SERVER2OUT
+$LDAPSEARCH -D cn=config -H $URI2 -y $CONFIGPWF \
+       -b "olcOverlay={0}variant,olcDatabase={1}$BACKEND,cn=config" \
+       >> $SERVER2OUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+# We've already filtered out the ordering markers, now sort the entries
+echo "Filtering ldapsearch results..."
+$LDIFFILTER -s a < $SERVER2OUT > $SERVER2FLT
+echo "Filtering expected entries..."
+$LDIFFILTER -s a < $SERVER1OUT > $SERVER1FLT
+echo "Comparing filter output..."
+$CMP $SERVER2FLT $SERVER1FLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "Comparison failed"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+
+rm $SERVER1OUT $SERVER2OUT
+
+echo "Comparing search output on both servers..."
+echo "# search output from dynamically configured server..." >> $SERVER1OUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+       >> $SERVER1OUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "# search output from server configured from slapd.conf..." >> $SERVER2OUT
+$LDAPSEARCH -b "$BASEDN" -H $URI2 \
+       >> $SERVER2OUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER -s e < $SERVER1OUT > $SERVER1FLT
+$LDIFFILTER -s e < $SERVER2OUT > $SERVER2FLT
+echo "Filtering expected entries..."
+$LDIFFILTER -s e < $SERVER3OUT > $SERVER3FLT
+echo "Comparing filter output..."
+$CMP $SERVER3FLT $SERVER1FLT > $CMPOUT && \
+$CMP $SERVER3FLT $SERVER2FLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "Comparison failed"
+       exit 1
+fi
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/variant/tests/scripts/test002-add-delete b/contrib/slapd-modules/variant/tests/scripts/test002-add-delete
new file mode 100755 (executable)
index 0000000..43e47e2
--- /dev/null
@@ -0,0 +1,113 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+. ${SCRIPTDIR}/common.sh
+
+echo "Adding entry..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+       -f data/test002-01-entry.ldif >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Configuring entry as variant..."
+. $CONFFILTER $BACKEND $MONITORDB < data/additional-config.ldif | \
+$LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Removing entry..."
+$LDAPDELETE -D $MANAGERDN -H $URI1 -w $PASSWD \
+       "cn=Gern Jensen,ou=Information Technology Division,ou=People,$BASEDN" \
+       >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapdelete failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Adding entry again (should fail)..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+       -f data/test002-01-entry.ldif >> $TESTOUT 2>&1
+RC=$?
+case $RC in
+0)
+       echo "ldapmodify should have failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+       ;;
+19)
+       echo "ldapmodify failed ($RC)"
+       ;;
+*)
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+       ;;
+esac
+
+echo "Adding a regex entry (should fail)..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+       -f data/test002-02-regex.ldif >> $TESTOUT 2>&1
+RC=$?
+case $RC in
+0)
+       echo "ldapmodify should have failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+       ;;
+19)
+       echo "ldapmodify failed ($RC)"
+       ;;
+*)
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+       ;;
+esac
+
+echo "Adding entry with offending attributes removed..."
+grep -v '^description:' data/test002-01-entry.ldif | \
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+       >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/variant/tests/scripts/test003-search b/contrib/slapd-modules/variant/tests/scripts/test003-search
new file mode 100755 (executable)
index 0000000..73f3801
--- /dev/null
@@ -0,0 +1,113 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+. ${SCRIPTDIR}/common.sh
+
+echo "Testing searches against regular entries..."
+echo "# Testing searches against regular entries..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+       "(|(name=Elliot)(description=*hiker*))" \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Testing searches listing variants..."
+echo >> $SEARCHOUT
+echo "# Testing searches listing variants..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -s one -H $URI1 \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -s base -H $URI1 \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo >> $SEARCHOUT
+$LDAPSEARCH -s base -H $URI1 \
+       -b "cn=Bjorn Jensen,ou=Information Technology Division,ou=People,$BASEDN" \
+       '(ou=Information Technology Division)' \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo >> $SEARCHOUT
+$LDAPSEARCH -b "cn=ITD Staff,ou=Groups,$BASEDN" -s base -H $URI1 \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Testing searches filtering on variants..."
+echo >> $SEARCHOUT
+echo "# Testing searches filtering on variants..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+       "(st=Alumni Association)" st \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+LDIF=data/test003-out.ldif
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER -s e < $SEARCHOUT > $SEARCHFLT
+echo "Filtering expected entries..."
+$LDIFFILTER -s e < $LDIF > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "Comparison failed"
+       exit 1
+fi
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/variant/tests/scripts/test004-compare b/contrib/slapd-modules/variant/tests/scripts/test004-compare
new file mode 100755 (executable)
index 0000000..5cf9e75
--- /dev/null
@@ -0,0 +1,63 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+. ${SCRIPTDIR}/common.sh
+
+echo "Comparing a regular entry..."
+$LDAPCOMPARE -H $URI1 \
+       "cn=Mark Elliot,ou=Alumni Association,ou=People,$BASEDN" \
+       "cn:Mark Elliot" >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 6 && test $RC,$BACKEND != 5,null ; then
+       echo "ldapcompare failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+
+echo "Comparing a variant entry..."
+$LDAPCOMPARE -H $URI1 \
+       "ou=People,$BASEDN" \
+       "description:The Example, Inc. at Anytown" >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 6 && test $RC,$BACKEND != 5,null ; then
+       echo "ldapcompare failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+
+echo "Comparing a regex entry..."
+$LDAPCOMPARE -H $URI1 \
+       "cn=Barbara Jensen,ou=Information Technology Division,ou=People,$BASEDN" \
+       "ou:Information Technology Division" >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 6 && test $RC,$BACKEND != 5,null ; then
+       echo "ldapcompare failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/variant/tests/scripts/test005-modify b/contrib/slapd-modules/variant/tests/scripts/test005-modify
new file mode 100755 (executable)
index 0000000..b862bd1
--- /dev/null
@@ -0,0 +1,120 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+. ${SCRIPTDIR}/common.sh
+
+echo "Modifying entry..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+       -f data/test005-changes.ldif >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+# for now, overlay returns success just after the modifications to the main
+# entry succeed, ignoring the rest should they fail
+echo "Modifying a nonexistent variant of an existing entry..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+       -f data/test005-variant-missing.ldif >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Configuring nonexistent entry as variant..."
+. $CONFFILTER $BACKEND $MONITORDB < data/additional-config.ldif | \
+$LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Modifying an existing variant of above missing entry..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+       -f data/test005-modify-missing.ldif >> $TESTOUT 2>&1
+RC=$?
+case $RC in
+0)
+       echo "ldapmodify should have failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+       ;;
+32)
+       echo "ldapmodify failed ($RC)"
+       ;;
+*)
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+       ;;
+esac
+
+echo "Reading affected entries back..."
+echo "# Reading affected entries back..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+       '(|(description=*heard*)(st=*)(ou=alabama)(ou=*IT*))' \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo >>$SEARCHOUT
+$LDAPSEARCH -H $URI1 -s base \
+       -b "cn=Bjorn Jensen,ou=Information Technology Division,ou=People,$BASEDN" \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+LDIF=data/test005-out.ldif
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER -s e < $SEARCHOUT > $SEARCHFLT
+echo "Filtering expected entries..."
+$LDIFFILTER -s e < $LDIF > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "Comparison failed"
+       exit 1
+fi
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/variant/tests/scripts/test006-acl b/contrib/slapd-modules/variant/tests/scripts/test006-acl
new file mode 100755 (executable)
index 0000000..73e444e
--- /dev/null
@@ -0,0 +1,323 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+case "$BACKEND" in ldif | null)
+       echo "$BACKEND backend does not support access controls, test skipped"
+       exit 0
+esac
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+CONF=$ACLCONF
+. ${SCRIPTDIR}/common.sh
+
+echo "Applying test-specific configuration..."
+. $CONFFILTER $BACKEND $MONITORDB < data/test006-config.ldif | \
+$LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+$LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w $PASSWD >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: ou=Add & Delete,dc=example,dc=com
+changetype: add
+objectClass: organizationalUnit
+ou: Add & Delete
+
+dn: cn=group,ou=Add & Delete,dc=example,dc=com
+changetype: add
+objectclass: groupOfNames
+member: dc=example,dc=com
+
+dn: sn=Doe,ou=Add & Delete,dc=example,dc=com
+changetype: add
+objectclass: OpenLDAPperson
+cn: John
+uid: jd
+
+dn: sn=Elliot,ou=Add & Delete,dc=example,dc=com
+changetype: add
+objectclass: OpenLDAPperson
+cn: Mark
+uid: me
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Testing search ACL processing..."
+
+echo "# Try to read an entry inside the Alumni Association container.
+# It should give us noSuchObject if we're not bound..." \
+>> $SEARCHOUT
+# FIXME: temporarily remove the "No such object" message to make
+# the test succeed even if SLAP_ACL_HONOR_DISCLOSE is not #define'd
+$LDAPSEARCH -b "$MELLIOTDN" -H $URI1 "(objectclass=*)" \
+       2>&1 | grep -v "No such object" >> $SEARCHOUT
+
+echo >>$SEARCHOUT
+echo "# ... and should return appropriate attributes if we're bound as anyone
+# under Example." \
+>> $SEARCHOUT
+$LDAPSEARCH -b "$MELLIOTDN" -H $URI1 \
+       -D "$BABSDN" -w bjensen "(objectclass=*)" >> $SEARCHOUT 2>&1
+
+$LDAPSEARCH -b "$MELLIOTDN" -H $URI1 \
+       -D "$BJORNSDN" -w bjorn "(objectclass=*)" >> $SEARCHOUT 2>&1
+
+echo >>$SEARCHOUT
+echo "# Add & Delete subtree contents as seen by Babs" >> $SEARCHOUT
+$LDAPSEARCH -b "ou=Add & Delete,dc=example,dc=com" -H $URI1 \
+       -D "$BABSDN" -w bjensen "(objectclass=*)" >> $SEARCHOUT 2>&1
+
+echo >>$SEARCHOUT
+echo "# Add & Delete subtree contents as seen by Bjorn" >> $SEARCHOUT
+$LDAPSEARCH -b "ou=Add & Delete,dc=example,dc=com" -H $URI1 \
+       -D "$BJORNSDN" -w bjorn "(objectclass=*)" >> $SEARCHOUT 2>&1
+
+echo "Testing modifications..."
+echo "... ACL on the alternative entry"
+$LDAPMODIFY -D "$BJORNSDN" -H $URI1 -w bjorn >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: cn=group,ou=Add & Delete,dc=example,dc=com
+changetype: modify
+add: seealso
+seealso: $BJORNSDN
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+$LDAPMODIFY -D "$BABSDN" -H $URI1 -w bjensen >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: cn=Alumni Assoc Staff, ou=Groups, dc=example, dc=com
+changetype: modify
+add: description
+description: added by bjensen (should fail)
+EOMODS
+RC=$?
+case $RC in
+50)
+       ;;
+0)
+       echo "ldapmodify should have failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+       ;;
+*)
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+       ;;
+esac
+
+$LDAPMODIFY -D "$MANAGERDN" -H $URI1 -w $PASSWD >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: cn=group,ou=Add & Delete,dc=example,dc=com
+changetype: modify
+add: seealso
+seealso: $BABSDN
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+$LDAPMODIFY -D "$BJORNSDN" -H $URI1 -w bjorn >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: cn=Alumni Assoc Staff, ou=Groups, dc=example, dc=com
+changetype: modify
+add: description
+description: added by bjorn (removed later)
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+$LDAPMODIFY -D "$BABSDN" -H $URI1 -w bjensen >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: cn=Group,ou=Add & Delete,dc=example,dc=com
+changetype: modify
+delete: description
+description: added by bjorn (removed later)
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+$LDAPMODIFY -D "$BJORNSDN" -H $URI1 -w bjorn >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: cn=Added by Bjorn,ou=Add & Delete,dc=example,dc=com
+changetype: add
+objectClass: inetOrgPerson
+sn: Jensen
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+$LDAPMODIFY -D "$BJORNSDN" -H $URI1 -w bjorn >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: cn=Group,ou=Add & Delete,dc=example,dc=com
+changetype: modify
+add: description
+description: another one added by bjorn (should succeed)
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "... ACL on the variant entry"
+$LDAPMODIFY -D "$BABSDN" -H $URI1 -w bjensen >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: cn=Group,ou=Add & Delete,dc=example,dc=com
+changetype: modify
+add: description
+description: added by bjensen (should fail)
+EOMODS
+RC=$?
+case $RC in
+50)
+       ;;
+0)
+       echo "ldapmodify should have failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+       ;;
+*)
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+       ;;
+esac
+
+$LDAPMODIFY -D "$BJORNSDN" -H $URI1 -w bjorn >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: sn=Doe,ou=Add & Delete,dc=example,dc=com
+changetype: modify
+add: description
+description: added by bjorn (will be removed)
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+$LDAPMODIFY -D "$BABSDN" -H $URI1 -w bjensen >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: cn=Added by Bjorn,ou=Add & Delete,dc=example,dc=com
+changetype: modify
+replace: description
+description: added by bjensen (should fail)
+EOMODS
+RC=$?
+case $RC in
+50)
+       ;;
+0)
+       echo "ldapmodify should have failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit -1
+       ;;
+*)
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+       ;;
+esac
+
+$LDAPMODIFY -D "$JAJDN" -H $URI1 -w jaj >> \
+       $TESTOUT 2>&1 << EOMODS
+dn: sn=Elliot,ou=Add & Delete,dc=example,dc=com
+changetype: modify
+delete: description
+description: added by bjorn (will be removed)
+-
+add: description
+description: added by jaj (should succeed)
+EOMODS
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+sleep $SLEEP0
+
+echo >>$SEARCHOUT
+echo "Using ldapsearch to retrieve all the entries..."
+echo "# Using ldapsearch to retrieve all the entries..." >> $SEARCHOUT
+$LDAPSEARCH -S "" -b "ou=Add & Delete,dc=example,dc=com" \
+       -D "$MANAGERDN" -H $URI1 -w $PASSWD \
+       'objectClass=*' >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+LDIF=data/test006-out.ldif
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER -s e < $SEARCHOUT > $SEARCHFLT
+echo "Filtering expected entries..."
+$LDIFFILTER -s e < $LDIF > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "comparison failed - operations did not complete correctly"
+       exit 1
+fi
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/variant/tests/scripts/test007-subtypes b/contrib/slapd-modules/variant/tests/scripts/test007-subtypes
new file mode 100755 (executable)
index 0000000..41a98d1
--- /dev/null
@@ -0,0 +1,67 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+. ${SCRIPTDIR}/common.sh
+
+echo "Comparing supertype of a variant attribute..."
+$LDAPCOMPARE -H $URI1 \
+       "ou=Groups,$BASEDN" \
+       "name:Alumni Association" >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 6 && test $RC,$BACKEND != 5,null ; then
+       echo "ldapcompare failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+
+echo "Testing searches against attribute supertypes..."
+echo "# Testing searches against attribute supertypes..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+       "(&(name=groups)(name=Alumni Association))" \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+LDIF=data/test007-out.ldif
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER -s e < $SEARCHOUT > $SEARCHFLT
+echo "Filtering expected entries..."
+$LDIFFILTER -s e < $LDIF > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "Comparison failed"
+       exit 1
+fi
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/variant/tests/scripts/test008-variant-replication b/contrib/slapd-modules/variant/tests/scripts/test008-variant-replication
new file mode 100755 (executable)
index 0000000..692e4a2
--- /dev/null
@@ -0,0 +1,194 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+if test "$SYNCPROV" = syncprovno; then 
+       echo "Syncrepl provider overlay not available, test skipped"
+       exit 0
+fi 
+
+. ${SCRIPTDIR}/common.sh
+
+$LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       > $TESTOUT 2>&1 <<EOMOD
+dn: olcOverlay={0}variant,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+replace: olcVariantPassReplication
+olcVariantPassReplication: FALSE
+EOMOD
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+if test "$SYNCPROV" = syncprovmod; then
+       $LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       > $TESTOUT 2>&1 <<EOMOD
+dn: cn=module{0},cn=config
+changetype: modify
+add: olcModuleLoad
+olcModuleLoad: $LDAP_BUILD/servers/slapd/overlays/syncprov.la
+EOMOD
+
+       RC=$?
+       if test $RC != 0 ; then
+               echo "ldapmodify failed ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+       fi
+fi
+
+echo "Configuring syncprov on the provider..."
+$LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       > $TESTOUT 2>&1 <<EOMOD
+dn: olcOverlay=syncprov,olcDatabase={1}$BACKEND,cn=config
+changetype: add
+objectclass: olcSyncProvConfig
+EOMOD
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+mkdir $DBDIR4
+
+echo "Starting consumer slapd on TCP/IP port $PORT4..."
+. $CONFFILTER $BACKEND $MONITORDB < $P1SRCONSUMERCONF > $CONF4
+$SLAPD -f $CONF4 -h $URI4 -d $LVL > $LOG4 2>&1 &
+CONSUMERPID=$!
+if test $WAIT != 0 ; then
+       echo CONSUMERPID $CONSUMERPID
+       read foo
+fi
+KILLPIDS="$KILLPIDS $CONSUMERPID"
+
+sleep $SLEEP0
+
+for i in 0 1 2 3 4 5; do
+       $LDAPSEARCH -s base -b "$BASEDN" -H $URI4 \
+               'objectclass=*' > /dev/null 2>&1
+       RC=$?
+       if test $RC = 0 ; then
+               break
+       fi
+       echo "Waiting ${SLEEP1} seconds for consumer to start replication..."
+       sleep ${SLEEP1}
+done
+
+echo "Waiting ${SLEEP1} seconds for consumer to finish replicating..."
+sleep ${SLEEP1}
+
+echo "Testing searches against regular entries..."
+echo "# Testing searches against regular entries..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI4 \
+       "(|(name=Elliot)(description=*hiker*))" \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Testing searches listing replicated variants..."
+echo >> $SEARCHOUT
+echo "# Testing searches listing replicated variants..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -s one -H $URI4 \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+# regex variants do not replicate correctly and this is documented
+echo >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -s base -H $URI1 \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+# regex variants do not replicate correctly and this is documented
+echo >> $SEARCHOUT
+$LDAPSEARCH -s base -H $URI1 \
+       -b "cn=Bjorn Jensen,ou=Information Technology Division,ou=People,$BASEDN" \
+       '(ou=Information Technology Division)' \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+# regex variants do not replicate correctly and this is documented
+echo >> $SEARCHOUT
+$LDAPSEARCH -b "cn=ITD Staff,ou=Groups,$BASEDN" -s base -H $URI1 \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Testing searches filtering on replicated variants..."
+echo >> $SEARCHOUT
+echo "# Testing searches filtering on replicated variants..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI4 \
+       "(st=Alumni Association)" st \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+LDIF=data/test003-out.ldif
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER -s e < $SEARCHOUT > $SEARCHFLT
+echo "Filtering expected entries..."
+$LDIFFILTER -s e < $LDIF > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "Comparison failed"
+       exit 1
+fi
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/variant/tests/scripts/test009-ignored-replication b/contrib/slapd-modules/variant/tests/scripts/test009-ignored-replication
new file mode 100755 (executable)
index 0000000..d8d63c8
--- /dev/null
@@ -0,0 +1,227 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+if test "$SYNCPROV" = syncprovno; then 
+       echo "Syncrepl provider overlay not available, test skipped"
+       exit 0
+fi 
+
+. ${SCRIPTDIR}/common.sh
+
+if test "$SYNCPROV" = syncprovmod; then
+       $LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       > $TESTOUT 2>&1 <<EOMOD
+dn: cn=module{0},cn=config
+changetype: modify
+add: olcModuleLoad
+olcModuleLoad: $LDAP_BUILD/servers/slapd/overlays/syncprov.la
+EOMOD
+
+       RC=$?
+       if test $RC != 0 ; then
+               echo "ldapmodify failed ($RC)!"
+               test $KILLSERVERS != no && kill -HUP $KILLPIDS
+               exit $RC
+       fi
+fi
+
+echo "Configuring syncprov on the provider..."
+$LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       > $TESTOUT 2>&1 <<EOMOD
+dn: olcOverlay={0}syncprov,olcDatabase={1}$BACKEND,cn=config
+changetype: add
+objectclass: olcSyncProvConfig
+EOMOD
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+mkdir $DBDIR4 $TESTDIR/confdir-consumer
+
+echo "Starting consumer slapd on TCP/IP port $PORT4..."
+. $CONFFILTER $BACKEND $MONITORDB < $P1SRCONSUMERCONF > $CONF4
+
+echo "database config" >>$CONF4
+echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >>$CONF4
+
+$SLAPD -f $CONF4 -F $TESTDIR/confdir-consumer -h $URI4 -d $LVL > $LOG4 2>&1 &
+CONSUMERPID=$!
+if test $WAIT != 0 ; then
+       echo CONSUMERPID $CONSUMERPID
+       read foo
+fi
+KILLPIDS="$KILLPIDS $CONSUMERPID"
+
+sleep $SLEEP0
+
+echo "Setting up variant overlay on consumer..."
+$LDAPSEARCH -D cn=config -H $URI4 -y $CONFIGPWF \
+       -s base -b 'cn=module{0},cn=config' 1.1 >$TESTOUT 2>&1
+RC=$?
+case $RC in
+0)
+       $LDAPMODIFY -v -D cn=config -H $URI4 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1 <<EOMOD
+dn: cn=module{0},cn=config
+changetype: modify
+add: olcModuleLoad
+olcModuleLoad: `pwd`/../variant.la
+EOMOD
+       ;;
+32)
+       $LDAPMODIFY -v -D cn=config -H $URI4 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1 <<EOMOD
+dn: cn=module,cn=config
+changetype: add
+objectClass: olcModuleList
+olcModuleLoad: `pwd`/../variant.la
+EOMOD
+       ;;
+*)
+       echo "Failed testing for module load entry"
+       exit $RC;
+       ;;
+esac
+
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+. $CONFFILTER $BACKEND $MONITORDB < $OVERLAY_CONFIG | \
+$LDAPMODIFY -v -D cn=config -H $URI4 -y $CONFIGPWF \
+       > $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+for i in 0 1 2 3 4 5; do
+       $LDAPSEARCH -s base -b "$BASEDN" -H $URI4 \
+               'objectclass=*' > /dev/null 2>&1
+       RC=$?
+       if test $RC = 0 ; then
+               break
+       fi
+       echo "Waiting ${SLEEP1} seconds for consumer to start replication..."
+       sleep ${SLEEP1}
+done
+
+echo "Waiting ${SLEEP1} seconds for consumer to finish replicating..."
+sleep ${SLEEP1}
+
+echo "Testing searches against regular entries..."
+echo "# Testing searches against regular entries..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI4 \
+       "(|(name=Elliot)(description=*hiker*))" \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Testing searches listing replicated variants..."
+echo >> $SEARCHOUT
+echo "# Testing searches listing replicated variants..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -s one -H $URI4 \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -s base -H $URI4 \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo >> $SEARCHOUT
+$LDAPSEARCH -s base -H $URI4 \
+       -b "cn=Bjorn Jensen,ou=Information Technology Division,ou=People,$BASEDN" \
+       '(ou=Information Technology Division)' \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo >> $SEARCHOUT
+$LDAPSEARCH -b "cn=ITD Staff,ou=Groups,$BASEDN" -s base -H $URI4 \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Testing searches filtering on replicated variants..."
+echo >> $SEARCHOUT
+echo "# Testing searches filtering on replicated variants..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI4 \
+       "(st=Alumni Association)" st \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+LDIF=data/test003-out.ldif
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER -s e < $SEARCHOUT > $SEARCHFLT
+echo "Filtering expected entries..."
+$LDIFFILTER -s e < $LDIF > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "Comparison failed"
+       exit 1
+fi
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/variant/tests/scripts/test010-limits b/contrib/slapd-modules/variant/tests/scripts/test010-limits
new file mode 100755 (executable)
index 0000000..3e91929
--- /dev/null
@@ -0,0 +1,99 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+. ${SCRIPTDIR}/common.sh
+
+echo "Testing searches against regular entries..."
+echo "# Testing searches against regular entries..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+       -z 1 "(|(name=Elliot)(description=*hiker*))" \
+       >> $SEARCHOUT 2>&1
+RC=$?
+case $RC in
+0)
+       echo "ldapsearch should have failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+       ;;
+4)
+       echo "sizelimit reached ($RC)"
+       ;;
+*)
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+       ;;
+esac
+
+echo "Testing searches listing variants where limits just fit..."
+echo "# Testing searches listing variants where limits just fit..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -s one -H $URI1 \
+       -z 3 >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Testing searches filtering on variants going over the specified limit..."
+echo "# Testing searches filtering on variants going over the specified limit..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+       -z 1 "(name=Alumni Association)" \
+       >> $SEARCHOUT 2>&1
+RC=$?
+case $RC in
+0)
+       echo "ldapsearch should have failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+       ;;
+4)
+       echo "sizelimit reached ($RC)"
+       ;;
+*)
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+       ;;
+esac
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+LDIF=data/test010-out.ldif
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER -s e < $SEARCHOUT > $SEARCHFLT
+echo "Filtering expected entries..."
+$LDIFFILTER -s e < $LDIF > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "Comparison failed"
+       exit 1
+fi
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/variant/tests/scripts/test011-referral b/contrib/slapd-modules/variant/tests/scripts/test011-referral
new file mode 100755 (executable)
index 0000000..4c2da28
--- /dev/null
@@ -0,0 +1,169 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+. ${SCRIPTDIR}/common.sh
+
+TESTDN="cn=Gern Jensen,ou=Information Technology Division,ou=People,$BASEDN"
+
+echo "Adding referral..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+       >> $TESTOUT 2>&1 <<EOMOD
+dn: $TESTDN
+changetype: add
+objectclass: referral
+objectclass: extensibleObject
+ref: ldap://hostB HostB
+EOMOD
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Configuring referral as variant..."
+. $CONFFILTER $BACKEND $MONITORDB < data/additional-config.ldif | \
+$LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Retrieving a referral variant..."
+echo "# Retrieving a referral variant..." >> $SEARCHOUT
+$LDAPSEARCH -LLL -b "$BASEDN" -H $URI1 \
+       '(cn=Gern Jensen)' >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch: unexpected result ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Retrieving a referral variant (returns a referral)..."
+echo "# Retrieving a referral variant (returns a referral)..." >> $SEARCHOUT
+$LDAPSEARCH -b "$TESTDN" -H $URI1 \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 10 ; then
+       echo "ldapsearch: unexpected result ($RC)! (referral expected)"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Modifying a referral variant (returns a referral)..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+       >> $TESTOUT 2>&1 <<EOMOD
+dn: $TESTDN
+changetype: modify
+delete: description
+EOMOD
+RC=$?
+if test $RC != 10 ; then
+       echo "ldapmodify: unexpected result ($RC)! (referral expected)"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Comparing a referral variant (returns a referral)..."
+$LDAPCOMPARE -H $URI1 "$TESTDN" \
+       "description:The Example, Inc. at Anytown" >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 10; then
+       echo "ldapcompare: unexpected result ($RC)! (referral expected)"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+
+echo "Reconfiguring variant underneath a referral..."
+$LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       >> $TESTOUT 2>&1 <<EOMOD
+dn: name={4}test002,olcOverlay={0}variant,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+replace: olcVariantEntry
+olcVariantEntry: cn=child,$TESTDN
+EOMOD
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Retrieving a variant under a referral (returns a referral)..."
+echo "# Retrieving a variant under a referral (returns a referral)..." >> $SEARCHOUT
+$LDAPSEARCH -b "cn=child,$TESTDN" -H $URI1 \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 10 ; then
+       echo "ldapsearch: unexpected result ($RC)! (referral expected)"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Modifying a variant under a referral (returns a referral)..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+       >> $TESTOUT 2>&1 <<EOMOD
+dn: cn=child,$TESTDN
+changetype: modify
+delete: description
+EOMOD
+RC=$?
+if test $RC != 10 ; then
+       echo "ldapmodify: unexpected result ($RC)! (referral expected)"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Comparing a variant under a referral (returns a referral)..."
+$LDAPCOMPARE -H $URI1 "cn=child,$TESTDN" \
+       "description:The Example, Inc. at Anytown" >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 10; then
+       echo "ldapcompare: unexpected result ($RC)! (referral expected)"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit 1
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+LDIF=data/test011-out.ldif
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
+echo "Filtering expected entries..."
+$LDIFFILTER < $LDIF > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "Comparison failed"
+       exit 1
+fi
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/variant/tests/scripts/test012-crossdb b/contrib/slapd-modules/variant/tests/scripts/test012-crossdb
new file mode 100755 (executable)
index 0000000..b0fbbd3
--- /dev/null
@@ -0,0 +1,90 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2021 The OpenLDAP Foundation.
+## All rights reserved.
+##
+## Redistribution and use in source and binary forms, with or without
+## modification, are permitted only as authorized by the OpenLDAP
+## Public License.
+##
+## A copy of this license is available in the file LICENSE in the
+## top-level directory of the distribution or, alternatively, at
+## <http://www.OpenLDAP.org/license.html>.
+##
+## ACKNOWLEDGEMENTS:
+## This module was written in 2016 by Ondřej Kuzník for Symas Corp.
+
+echo "running defines.sh"
+. $SRCDIR/scripts/defines.sh
+
+. ${SCRIPTDIR}/common.sh
+
+echo "Setting up another database and variant using an alternate there..."
+mkdir $DBDIR2
+$LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+       <<EOMOD >> $TESTOUT 2>&1
+dn: olcDatabase=ldif,cn=config
+changetype: add
+objectclass: olcLdifConfig
+olcSuffix: dc=demonstration,dc=com
+olcDbDirectory: $DBDIR2
+olcRootDn: $MANAGERDN
+
+dn: olcVariantVariantAttribute={1}seealso,name={0}variant,olcOverlay={0}variant,olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+replace: olcVariantAlternativeEntry
+olcVariantAlternativeEntry: ou=Societies,dc=demonstration,dc=com
+EOMOD
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Adding alternate entry..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+       -f data/test012-data.ldif >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapmodify failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+echo "Checking the variant gets resolved correctly..."
+echo "# Testing a search against a variant using another DB..." >> $SEARCHOUT
+#$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+#    "seealso=dc=example,dc=com" \
+$LDAPSEARCH -b "ou=People,$BASEDN" -s base -H $URI1 \
+       >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+       echo "ldapsearch failed ($RC)!"
+       test $KILLSERVERS != no && kill -HUP $KILLPIDS
+       exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+LDIF=data/test012-out.ldif
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER < $SEARCHOUT > $SEARCHFLT
+echo "Filtering expected entries..."
+$LDIFFILTER < $LDIF > $LDIFFLT
+echo "Comparing filter output..."
+$CMP $SEARCHFLT $LDIFFLT > $CMPOUT
+
+if test $? != 0 ; then
+       echo "Comparison failed"
+       exit 1
+fi
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/variant/variant.c b/contrib/slapd-modules/variant/variant.c
new file mode 100644 (file)
index 0000000..a89ec0c
--- /dev/null
@@ -0,0 +1,1424 @@
+/* variant.c - variant overlay */
+/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
+ *
+ * Copyright 2016-2021 Symas Corporation.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted only as authorized by the OpenLDAP
+ * Public License.
+ *
+ * A copy of this license is available in the file LICENSE in the
+ * top-level directory of the distribution or, alternatively, at
+ * <http://www.OpenLDAP.org/license.html>.
+ */
+/* ACKNOWLEDGEMENTS:
+ * This work was developed in 2016-2017 by Ondřej Kuzník for Symas Corp.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_VARIANT
+
+#include "slap.h"
+#include "slap-config.h"
+#include "ldap_queue.h"
+
+typedef enum variant_type_t {
+       VARIANT_INFO_PLAIN = 1 << 0,
+       VARIANT_INFO_REGEX = 1 << 1,
+
+       VARIANT_INFO_ALL = ~0
+} variant_type_t;
+
+typedef struct variant_info_t {
+       int passReplication;
+       LDAP_STAILQ_HEAD(variant_list, variantEntry_info) variants, regex_variants;
+} variant_info_t;
+
+typedef struct variantEntry_info {
+       variant_info_t *ov;
+       struct berval dn;
+       variant_type_t type;
+       regex_t *regex;
+       LDAP_SLIST_HEAD(attribute_list, variantAttr_info) attributes;
+       LDAP_STAILQ_ENTRY(variantEntry_info) next;
+} variantEntry_info;
+
+typedef struct variantAttr_info {
+       variantEntry_info *variant;
+       struct berval dn;
+       AttributeDescription *attr, *alternative;
+       LDAP_SLIST_ENTRY(variantAttr_info) next;
+} variantAttr_info;
+
+static int
+variant_build_dn(
+               Operation *op,
+               variantAttr_info *vai,
+               int nmatch,
+               regmatch_t *pmatch,
+               struct berval *out )
+{
+       struct berval dn, *ndn = &op->o_req_ndn;
+       char *dest, *p, *prev, *end = vai->dn.bv_val + vai->dn.bv_len;
+       size_t len = vai->dn.bv_len;
+       int rc;
+
+       p = vai->dn.bv_val;
+       while ( (p = memchr( p, '$', end - p )) != NULL ) {
+               len -= 1;
+               p += 1;
+
+               if ( ( *p >= '0' ) && ( *p <= '9' ) ) {
+                       int i = *p - '0';
+
+                       len += ( pmatch[i].rm_eo - pmatch[i].rm_so );
+               } else if ( *p != '$' ) {
+                       /* Should have been checked at configuration time */
+                       assert(0);
+               }
+               len -= 1;
+               p += 1;
+       }
+
+       dest = dn.bv_val = ch_realloc( out->bv_val, len + 1 );
+       dn.bv_len = len;
+
+       prev = vai->dn.bv_val;
+       while ( (p = memchr( prev, '$', end - prev )) != NULL ) {
+               len = p - prev;
+               AC_MEMCPY( dest, prev, len );
+               dest += len;
+               p += 1;
+
+               if ( ( *p >= '0' ) && ( *p <= '9' ) ) {
+                       int i = *p - '0';
+                       len = pmatch[i].rm_eo - pmatch[i].rm_so;
+
+                       AC_MEMCPY( dest, ndn->bv_val + pmatch[i].rm_so, len );
+                       dest += len;
+               } else if ( *p == '$' ) {
+                       *dest++ = *p;
+               }
+               prev = p + 1;
+       }
+       len = end - prev;
+       AC_MEMCPY( dest, prev, len );
+       dest += len;
+       *dest = '\0';
+
+       rc = dnNormalize( 0, NULL, NULL, &dn, out, NULL );
+       ch_free( dn.bv_val );
+
+       return rc;
+}
+
+static int
+variant_build_entry(
+               Operation *op,
+               variantEntry_info *vei,
+               struct berval *dn,
+               Entry **ep,
+               int nmatch,
+               regmatch_t *pmatch )
+{
+       slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+       BackendDB *be_orig = op->o_bd, *db;
+       struct berval ndn = BER_BVNULL;
+       variantAttr_info *vai;
+       Attribute *a;
+       BerVarray nvals;
+       Entry *e;
+       unsigned int i;
+       int rc;
+
+       assert( ep );
+       assert( !*ep );
+
+       rc = overlay_entry_get_ov( op, dn, NULL, NULL, 0, &e, on );
+       if ( rc == LDAP_SUCCESS && is_entry_referral( e ) ) {
+               overlay_entry_release_ov( op, e, 0, on );
+               rc = LDAP_REFERRAL;
+       }
+
+       if ( rc != LDAP_SUCCESS ) {
+               goto done;
+       }
+
+       *ep = entry_dup( e );
+       overlay_entry_release_ov( op, e, 0, on );
+
+       LDAP_SLIST_FOREACH( vai, &vei->attributes, next ) {
+               if ( vei->type == VARIANT_INFO_REGEX ) {
+                       rc = variant_build_dn( op, vai, nmatch, pmatch, &ndn );
+                       if ( rc != LDAP_SUCCESS ) {
+                               goto done;
+                       }
+               } else {
+                       ndn = vai->dn;
+               }
+
+               (void)attr_delete( &(*ep)->e_attrs, vai->attr );
+               op->o_bd = be_orig;
+
+               /* only select backend if not served by ours, would retrace all
+                * overlays again */
+               db = select_backend( &ndn, 0 );
+               if ( db && db != be_orig->bd_self ) {
+                       op->o_bd = db;
+                       rc = be_entry_get_rw( op, &ndn, NULL, vai->alternative, 0, &e );
+               } else {
+                       rc = overlay_entry_get_ov(
+                                       op, &ndn, NULL, vai->alternative, 0, &e, on );
+               }
+
+               switch ( rc ) {
+                       case LDAP_SUCCESS:
+                               break;
+                       case LDAP_INSUFFICIENT_ACCESS:
+                       case LDAP_NO_SUCH_ATTRIBUTE:
+                       case LDAP_NO_SUCH_OBJECT:
+                               rc = LDAP_SUCCESS;
+                               continue;
+                               break;
+                       default:
+                               goto done;
+                               break;
+               }
+
+               a = attr_find( e->e_attrs, vai->alternative );
+
+               /* back-ldif doesn't check the attribute exists in the entry before
+                * returning it */
+               if ( a ) {
+                       if ( a->a_nvals ) {
+                               nvals = a->a_nvals;
+                       } else {
+                               nvals = a->a_vals;
+                       }
+
+                       for ( i = 0; i < a->a_numvals; i++ ) {
+                               if ( backend_access( op, e, &ndn, vai->alternative, &nvals[i],
+                                                       ACL_READ, NULL ) != LDAP_SUCCESS ) {
+                                       continue;
+                               }
+
+                               rc = attr_merge_one( *ep, vai->attr, &a->a_vals[i], &nvals[i] );
+                               if ( rc != LDAP_SUCCESS ) {
+                                       break;
+                               }
+                       }
+               }
+
+               if ( db && db != be_orig->bd_self ) {
+                       be_entry_release_rw( op, e, 0 );
+               } else {
+                       overlay_entry_release_ov( op, e, 0, on );
+               }
+               if ( rc != LDAP_SUCCESS ) {
+                       goto done;
+               }
+       }
+
+done:
+       op->o_bd = be_orig;
+       if ( rc != LDAP_SUCCESS && *ep ) {
+               entry_free( *ep );
+               *ep = NULL;
+       }
+       if ( vei->type == VARIANT_INFO_REGEX ) {
+               ch_free( ndn.bv_val );
+       }
+
+       return rc;
+}
+
+static int
+variant_find_config(
+               Operation *op,
+               variant_info_t *ov,
+               struct berval *ndn,
+               int which,
+               variantEntry_info **veip,
+               size_t nmatch,
+               regmatch_t *pmatch )
+{
+       variantEntry_info *vei;
+
+       assert( veip );
+
+       if ( which & VARIANT_INFO_PLAIN ) {
+               int diff;
+
+               LDAP_STAILQ_FOREACH( vei, &ov->variants, next ) {
+                       dnMatch( &diff, 0, NULL, NULL, ndn, &vei->dn );
+                       if ( diff ) continue;
+
+                       *veip = vei;
+                       return LDAP_SUCCESS;
+               }
+       }
+
+       if ( which & VARIANT_INFO_REGEX ) {
+               LDAP_STAILQ_FOREACH( vei, &ov->regex_variants, next ) {
+                       if ( regexec( vei->regex, ndn->bv_val, nmatch, pmatch, 0 ) ) {
+                               continue;
+                       }
+
+                       *veip = vei;
+                       return LDAP_SUCCESS;
+               }
+       }
+
+       return SLAP_CB_CONTINUE;
+}
+
+static int
+variant_op_add( Operation *op, SlapReply *rs )
+{
+       slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+       variant_info_t *ov = on->on_bi.bi_private;
+       variantEntry_info *vei;
+       int rc;
+
+       /* Replication always uses the rootdn */
+       if ( ov->passReplication && SLAPD_SYNC_IS_SYNCCONN(op->o_connid) &&
+                       be_isroot( op ) ) {
+               return SLAP_CB_CONTINUE;
+       }
+
+       Debug( LDAP_DEBUG_TRACE, "variant_op_add: "
+                       "dn=%s\n", op->o_req_ndn.bv_val );
+
+       rc = variant_find_config(
+                       op, ov, &op->o_req_ndn, VARIANT_INFO_ALL, &vei, 0, NULL );
+       if ( rc == LDAP_SUCCESS ) {
+               variantAttr_info *vai;
+
+               LDAP_SLIST_FOREACH( vai, &vei->attributes, next ) {
+                       Attribute *a;
+                       for ( a = op->ora_e->e_attrs; a; a = a->a_next ) {
+                               if ( a->a_desc == vai->attr ) {
+                                       rc = LDAP_CONSTRAINT_VIOLATION;
+                                       send_ldap_error( op, rs, rc,
+                                                       "variant: trying to add variant attributes" );
+                                       goto done;
+                               }
+                       }
+               }
+       }
+       rc = SLAP_CB_CONTINUE;
+
+done:
+       Debug( LDAP_DEBUG_TRACE, "variant_op_add: "
+                       "finished with %d\n",
+                       rc );
+       return rc;
+}
+
+static int
+variant_op_compare( Operation *op, SlapReply *rs )
+{
+       slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+       variant_info_t *ov = on->on_bi.bi_private;
+       variantEntry_info *vei;
+       regmatch_t pmatch[10];
+       int rc, nmatch = sizeof(pmatch) / sizeof(regmatch_t);
+
+       Debug( LDAP_DEBUG_TRACE, "variant_op_compare: "
+                       "dn=%s\n", op->o_req_ndn.bv_val );
+
+       rc = variant_find_config(
+                       op, ov, &op->o_req_ndn, VARIANT_INFO_ALL, &vei, nmatch, pmatch );
+       if ( rc == LDAP_SUCCESS ) {
+               Entry *e = NULL;
+
+               rc = variant_build_entry( op, vei, &op->o_req_ndn, &e, nmatch, pmatch );
+               /* in case of error, just let the backend deal with the mod and the
+                * client should get a meaningful error back */
+               if ( rc != LDAP_SUCCESS ) {
+                       rc = SLAP_CB_CONTINUE;
+               } else {
+                       rc = slap_compare_entry( op, e, op->orc_ava );
+
+                       entry_free( e );
+                       e = NULL;
+               }
+       }
+
+       if ( rc != SLAP_CB_CONTINUE ) {
+               rs->sr_err = rc;
+               send_ldap_result( op, rs );
+       }
+
+       Debug( LDAP_DEBUG_TRACE, "variant_op_compare: "
+                       "finished with %d\n", rc );
+       return rc;
+}
+
+static int
+variant_cmp_op( const void *l, const void *r )
+{
+       const Operation *left = l, *right = r;
+       int diff;
+
+       dnMatch( &diff, 0, NULL, NULL, (struct berval *)&left->o_req_ndn,
+                       (void *)&right->o_req_ndn );
+
+       return diff;
+}
+
+static int
+variant_run_mod( void *nop, void *arg )
+{
+       SlapReply nrs = { REP_RESULT };
+       slap_callback cb = { 0 };
+       Operation *op = nop;
+       slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+       int *rc = arg;
+
+       cb.sc_response = slap_null_cb;
+       op->o_callback = &cb;
+
+       Debug( LDAP_DEBUG_TRACE, "variant_run_mod: "
+                       "running mod on dn=%s\n",
+                       op->o_req_ndn.bv_val );
+       *rc = on->on_info->oi_orig->bi_op_modify( op, &nrs );
+       Debug( LDAP_DEBUG_TRACE, "variant_run_mod: "
+                       "finished with %d\n", *rc );
+
+       return ( *rc != LDAP_SUCCESS );
+}
+
+/** Move the Modifications back to the original Op so that they can be disposed
+ * of by the original creator
+ */
+static int
+variant_reassign_mods( void *nop, void *arg )
+{
+       Operation *op = nop, *orig_op = arg;
+       Modifications *mod;
+
+       assert( op->orm_modlist );
+
+       for ( mod = op->orm_modlist; mod->sml_next; mod = mod->sml_next )
+               /* get the tail mod */;
+
+       mod->sml_next = orig_op->orm_modlist;
+       orig_op->orm_modlist = op->orm_modlist;
+
+       return LDAP_SUCCESS;
+}
+
+void
+variant_free_op( void *op )
+{
+       ch_free( ((Operation *)op)->o_req_ndn.bv_val );
+       ch_free( op );
+}
+
+static int
+variant_op_mod( Operation *op, SlapReply *rs )
+{
+       slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+       variant_info_t *ov = on->on_bi.bi_private;
+       variantEntry_info *vei;
+       variantAttr_info *vai;
+       Avlnode *ops = NULL;
+       Entry *e = NULL;
+       Modifications *mod, *nextmod;
+       regmatch_t pmatch[10];
+       int rc, nmatch = sizeof(pmatch) / sizeof(regmatch_t);
+
+       /* Replication always uses the rootdn */
+       if ( ov->passReplication && SLAPD_SYNC_IS_SYNCCONN(op->o_connid) &&
+                       be_isroot( op ) ) {
+               return SLAP_CB_CONTINUE;
+       }
+
+       Debug( LDAP_DEBUG_TRACE, "variant_op_mod: "
+                       "dn=%s\n", op->o_req_ndn.bv_val );
+
+       rc = variant_find_config(
+                       op, ov, &op->o_req_ndn, VARIANT_INFO_ALL, &vei, nmatch, pmatch );
+       if ( rc != LDAP_SUCCESS ) {
+               Debug( LDAP_DEBUG_TRACE, "variant_op_mod: "
+                               "not a variant\n" );
+               rc = SLAP_CB_CONTINUE;
+               goto done;
+       }
+
+       rc = variant_build_entry( op, vei, &op->o_req_ndn, &e, nmatch, pmatch );
+       /* in case of error, just let the backend deal with the mod and the client
+        * should get a meaningful error back */
+       if ( rc != LDAP_SUCCESS ) {
+               Debug( LDAP_DEBUG_TRACE, "variant_op_mod: "
+                               "failed to retrieve entry\n" );
+               rc = SLAP_CB_CONTINUE;
+               goto done;
+       }
+
+       rc = acl_check_modlist( op, e, op->orm_modlist );
+       entry_free( e );
+
+       if ( !rc ) {
+               rc = rs->sr_err = LDAP_INSUFFICIENT_ACCESS;
+               send_ldap_error( op, rs, rc, "" );
+               return rc;
+       }
+
+       for ( mod = op->orm_modlist; mod; mod = nextmod ) {
+               Operation needle = { .o_req_ndn = BER_BVNULL }, *nop;
+
+               nextmod = mod->sml_next;
+
+               LDAP_SLIST_FOREACH( vai, &vei->attributes, next ) {
+                       if ( vai->attr == mod->sml_desc ) {
+                               break;
+                       }
+               }
+
+               if ( vai ) {
+                       if ( vei->type == VARIANT_INFO_REGEX ) {
+                               rc = variant_build_dn(
+                                               op, vai, nmatch, pmatch, &needle.o_req_ndn );
+                               if ( rc != LDAP_SUCCESS ) {
+                                       continue;
+                               }
+                       } else {
+                               needle.o_req_ndn = vai->dn;
+                       }
+
+                       nop = avl_find( ops, &needle, variant_cmp_op );
+                       if ( nop == NULL ) {
+                               nop = ch_calloc( 1, sizeof(Operation) );
+                               *nop = *op;
+
+                               ber_dupbv( &nop->o_req_ndn, &needle.o_req_ndn );
+                               nop->o_req_dn = nop->o_req_ndn;
+                               nop->orm_modlist = NULL;
+
+                               rc = avl_insert( &ops, nop, variant_cmp_op, avl_dup_error );
+                               assert( rc == 0 );
+                       }
+                       mod->sml_desc = vai->alternative;
+
+                       op->orm_modlist = nextmod;
+                       mod->sml_next = nop->orm_modlist;
+                       nop->orm_modlist = mod;
+
+                       if ( vei->type == VARIANT_INFO_REGEX ) {
+                               ch_free( needle.o_req_ndn.bv_val );
+                       }
+               }
+       }
+
+       if ( !ops ) {
+               Debug( LDAP_DEBUG_TRACE, "variant_op_mod: "
+                               "no variant attributes in mod\n" );
+               return SLAP_CB_CONTINUE;
+       }
+
+       /*
+        * First run original Operation
+        * This will take care of making sure the entry exists as well.
+        *
+        * FIXME?
+        * Since we cannot make the subsequent Ops atomic wrt. this one, we just
+        * let it send the response as well. After all, the changes on the main DN
+        * have finished by then
+        */
+       rc = on->on_info->oi_orig->bi_op_modify( op, rs );
+       if ( rc == LDAP_SUCCESS ) {
+               /* FIXME: if a mod fails, should we attempt to apply the rest? */
+               avl_apply( ops, variant_run_mod, &rc, -1, AVL_INORDER );
+       }
+
+       avl_apply( ops, variant_reassign_mods, op, -1, AVL_INORDER );
+       avl_free( ops, variant_free_op );
+
+done:
+       Debug( LDAP_DEBUG_TRACE, "variant_op_mod: "
+                       "finished with %d\n", rc );
+       return rc;
+}
+
+static int
+variant_search_response( Operation *op, SlapReply *rs )
+{
+       slap_overinst *on = op->o_callback->sc_private;
+       variant_info_t *ov = on->on_bi.bi_private;
+       variantEntry_info *vei;
+       int rc;
+
+       if ( rs->sr_type == REP_RESULT ) {
+               ch_free( op->o_callback );
+               op->o_callback = NULL;
+       }
+
+       if ( rs->sr_type != REP_SEARCH ) {
+               return SLAP_CB_CONTINUE;
+       }
+
+       rc = variant_find_config(
+                       op, ov, &rs->sr_entry->e_nname, VARIANT_INFO_PLAIN, &vei, 0, NULL );
+       if ( rc == LDAP_SUCCESS ) {
+               rs->sr_nentries--;
+               return rc;
+       }
+
+       return SLAP_CB_CONTINUE;
+}
+
+static int
+variant_op_search( Operation *op, SlapReply *rs )
+{
+       slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+       variant_info_t *ov = on->on_bi.bi_private;
+       variantEntry_info *vei;
+       slap_callback *cb;
+       Entry *e = NULL;
+       regmatch_t pmatch[10];
+       int variantInScope = 0, rc = SLAP_CB_CONTINUE,
+               nmatch = sizeof(pmatch) / sizeof(regmatch_t);
+
+       if ( ov->passReplication && ( op->o_sync > SLAP_CONTROL_IGNORED ) ) {
+               return SLAP_CB_CONTINUE;
+       }
+
+       Debug( LDAP_DEBUG_TRACE, "variant_op_search: "
+                       "dn=%s, scope=%d\n",
+                       op->o_req_ndn.bv_val, op->ors_scope );
+
+       LDAP_STAILQ_FOREACH( vei, &ov->variants, next ) {
+               if ( !dnIsSuffixScope( &vei->dn, &op->o_req_ndn, op->ors_scope ) )
+                       continue;
+
+               variantInScope = 1;
+
+               rc = variant_build_entry( op, vei, &vei->dn, &e, 0, NULL );
+               if ( rc == LDAP_NO_SUCH_OBJECT || rc == LDAP_REFERRAL ) {
+                       rc = SLAP_CB_CONTINUE;
+                       continue;
+               } else if ( rc != LDAP_SUCCESS ) {
+                       Debug( LDAP_DEBUG_TRACE, "variant_op_search: "
+                                       "failed to retrieve entry: dn=%s\n",
+                                       vei->dn.bv_val );
+                       goto done;
+               }
+
+               if ( test_filter( op, e, op->ors_filter ) == LDAP_COMPARE_TRUE ) {
+                       Debug( LDAP_DEBUG_TRACE, "variant_op_search: "
+                                       "entry matched: dn=%s\n",
+                                       vei->dn.bv_val );
+                       rs->sr_entry = e;
+                       rs->sr_attrs = op->ors_attrs;
+                       rc = send_search_entry( op, rs );
+               }
+               entry_free( e );
+               e = NULL;
+       }
+
+       /* Three options:
+        * - the entry has been handled above, in that case vei->type is VARIANT_INFO_PLAIN
+        * - the entry matches a regex, use the first one and we're finished
+        * - no configuration matches entry - do nothing
+        */
+       if ( op->ors_scope == LDAP_SCOPE_BASE &&
+                       variant_find_config( op, ov, &op->o_req_ndn, VARIANT_INFO_ALL, &vei,
+                                       nmatch, pmatch ) == LDAP_SUCCESS &&
+                       vei->type == VARIANT_INFO_REGEX ) {
+               rc = variant_build_entry( op, vei, &op->o_req_ndn, &e, nmatch, pmatch );
+               if ( rc == LDAP_NO_SUCH_OBJECT || rc == LDAP_REFERRAL ) {
+                       rc = SLAP_CB_CONTINUE;
+               } else if ( rc != LDAP_SUCCESS ) {
+                       Debug( LDAP_DEBUG_TRACE, "variant_op_search: "
+                                       "failed to retrieve entry: dn=%s\n",
+                                       vei->dn.bv_val );
+                       goto done;
+               } else {
+                       if ( test_filter( op, e, op->ors_filter ) == LDAP_COMPARE_TRUE ) {
+                               Debug( LDAP_DEBUG_TRACE, "variant_op_search: "
+                                               "entry matched: dn=%s\n",
+                                               vei->dn.bv_val );
+                               rs->sr_entry = e;
+                               rs->sr_attrs = op->ors_attrs;
+                               rc = send_search_entry( op, rs );
+                       }
+                       entry_free( e );
+                       e = NULL;
+                       goto done;
+               }
+       }
+       rc = SLAP_CB_CONTINUE;
+
+       if ( variantInScope ) {
+               cb = ch_calloc( 1, sizeof(slap_callback) );
+               cb->sc_private = on;
+               cb->sc_response = variant_search_response;
+               cb->sc_next = op->o_callback;
+
+               op->o_callback = cb;
+       }
+
+done:
+       if ( rc != SLAP_CB_CONTINUE ) {
+               rs->sr_err = (rc == LDAP_SUCCESS) ? rc : LDAP_OTHER;
+               send_ldap_result( op, rs );
+       }
+       Debug( LDAP_DEBUG_TRACE, "variant_op_search: "
+                       "finished with %d\n", rc );
+       return rc;
+}
+
+/* Configuration */
+
+static ConfigLDAPadd variant_ldadd;
+static ConfigLDAPadd variant_regex_ldadd;
+static ConfigLDAPadd variant_attr_ldadd;
+
+static ConfigDriver variant_set_dn;
+static ConfigDriver variant_set_regex;
+static ConfigDriver variant_set_alt_dn;
+static ConfigDriver variant_set_alt_pattern;
+static ConfigDriver variant_set_attribute;
+static ConfigDriver variant_add_alt_attr;
+static ConfigDriver variant_add_alt_attr_regex;
+
+static ConfigCfAdd variant_cfadd;
+
+enum
+{
+       VARIANT_ATTR = 1,
+       VARIANT_ATTR_ALT,
+
+       VARIANT_LAST,
+};
+
+static ConfigTable variant_cfg[] = {
+       { "passReplication", "on|off", 2, 2, 0,
+               ARG_ON_OFF|ARG_OFFSET,
+               (void *)offsetof( variant_info_t, passReplication ),
+               "( OLcfgOvAt:FIXME.1 NAME 'olcVariantPassReplication' "
+                       "DESC 'Whether to let searches with replication control "
+                               "pass unmodified' "
+                       "SYNTAX OMsBoolean "
+                       "SINGLE-VALUE )",
+                       NULL, NULL
+       },
+       { "variantDN", "dn", 2, 2, 0,
+               ARG_DN|ARG_QUOTE|ARG_MAGIC,
+               variant_set_dn,
+               "( OLcfgOvAt:FIXME.2 NAME 'olcVariantEntry' "
+                       "DESC 'DN of the variant entry' "
+                       "EQUALITY distinguishedNameMatch "
+                       "SYNTAX OMsDN "
+                       "SINGLE-VALUE )",
+                       NULL, NULL
+       },
+       { "variantRegex", "regex", 2, 2, 0,
+               ARG_BERVAL|ARG_QUOTE|ARG_MAGIC,
+               variant_set_regex,
+               "( OLcfgOvAt:FIXME.6 NAME 'olcVariantEntryRegex' "
+                       "DESC 'Pattern for the variant entry' "
+                       "EQUALITY caseExactMatch "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+                       NULL, NULL
+       },
+       /* These have no equivalent in slapd.conf */
+       { "", NULL, 2, 2, 0,
+               ARG_STRING|ARG_MAGIC|VARIANT_ATTR,
+               variant_set_attribute,
+               "( OLcfgOvAt:FIXME.3 NAME 'olcVariantVariantAttribute' "
+                       "DESC 'Attribute to fill in the entry' "
+                       "EQUALITY caseIgnoreMatch "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+                       NULL, NULL
+       },
+       { "", NULL, 2, 2, 0,
+               ARG_STRING|ARG_MAGIC|VARIANT_ATTR_ALT,
+               variant_set_attribute,
+               "( OLcfgOvAt:FIXME.4 NAME 'olcVariantAlternativeAttribute' "
+                       "DESC 'Attribute to take from the alternative entry' "
+                       "EQUALITY caseIgnoreMatch "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+                       NULL, NULL
+       },
+       { "", NULL, 2, 2, 0,
+               ARG_DN|ARG_QUOTE|ARG_MAGIC,
+               variant_set_alt_dn,
+               "( OLcfgOvAt:FIXME.5 NAME 'olcVariantAlternativeEntry' "
+                       "DESC 'DN of the alternative entry' "
+                       "EQUALITY distinguishedNameMatch "
+                       "SYNTAX OMsDN "
+                       "SINGLE-VALUE )",
+                       NULL, NULL
+       },
+       { "", NULL, 2, 2, 0,
+               ARG_BERVAL|ARG_QUOTE|ARG_MAGIC,
+               variant_set_alt_pattern,
+               "( OLcfgOvAt:FIXME.7 NAME 'olcVariantAlternativeEntryPattern' "
+                       "DESC 'Replacement pattern to locate the alternative entry' "
+                       "EQUALITY caseExactMatch "
+                       "SYNTAX OMsDirectoryString "
+                       "SINGLE-VALUE )",
+                       NULL, NULL
+       },
+       /* slapd.conf alternatives for the four above */
+       { "variantSpec", "attr attr2 dn", 4, 4, 0,
+               ARG_QUOTE|ARG_MAGIC,
+               variant_add_alt_attr,
+               NULL, NULL, NULL
+       },
+       { "variantRegexSpec", "attr attr2 pattern", 4, 4, 0,
+               ARG_QUOTE|ARG_MAGIC,
+               variant_add_alt_attr_regex,
+               NULL, NULL, NULL
+       },
+
+       { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs variant_ocs[] = {
+       { "( OLcfgOvOc:FIXME.1 "
+               "NAME 'olcVariantConfig' "
+               "DESC 'Variant overlay configuration' "
+               "SUP olcOverlayConfig "
+               "MAY ( olcVariantPassReplication ) )",
+               Cft_Overlay, variant_cfg, NULL, variant_cfadd },
+       { "( OLcfgOvOc:FIXME.2 "
+               "NAME 'olcVariantVariant' "
+               "DESC 'Variant configuration' "
+               "MUST ( olcVariantEntry ) "
+               "MAY ( name ) "
+               "SUP top "
+               "STRUCTURAL )",
+               Cft_Misc, variant_cfg, variant_ldadd },
+       { "( OLcfgOvOc:FIXME.3 "
+               "NAME 'olcVariantAttribute' "
+               "DESC 'Variant attribute description' "
+               "MUST ( olcVariantVariantAttribute $ "
+                       "olcVariantAlternativeAttribute $ "
+                       "olcVariantAlternativeEntry "
+               ") "
+               "MAY name "
+               "SUP top "
+               "STRUCTURAL )",
+               Cft_Misc, variant_cfg, variant_attr_ldadd },
+       { "( OLcfgOvOc:FIXME.4 "
+               "NAME 'olcVariantRegex' "
+               "DESC 'Variant configuration' "
+               "MUST ( olcVariantEntryRegex ) "
+               "MAY ( name ) "
+               "SUP top "
+               "STRUCTURAL )",
+               Cft_Misc, variant_cfg, variant_regex_ldadd },
+       { "( OLcfgOvOc:FIXME.5 "
+               "NAME 'olcVariantAttributePattern' "
+               "DESC 'Variant attribute description' "
+               "MUST ( olcVariantVariantAttribute $ "
+                       "olcVariantAlternativeAttribute $ "
+                       "olcVariantAlternativeEntryPattern "
+               ") "
+               "MAY name "
+               "SUP top "
+               "STRUCTURAL )",
+               Cft_Misc, variant_cfg, variant_attr_ldadd },
+
+       { NULL, 0, NULL }
+};
+
+static int
+variant_set_dn( ConfigArgs *ca )
+{
+       variantEntry_info *vei2, *vei = ca->ca_private;
+       slap_overinst *on = (slap_overinst *)ca->bi;
+       variant_info_t *ov = on->on_bi.bi_private;
+       int diff;
+
+       if ( ca->op == SLAP_CONFIG_EMIT ) {
+               value_add_one( &ca->rvalue_vals, &vei->dn );
+               return LDAP_SUCCESS;
+       } else if ( ca->op == LDAP_MOD_DELETE ) {
+               ber_memfree( vei->dn.bv_val );
+               BER_BVZERO( &vei->dn );
+               return LDAP_SUCCESS;
+       }
+
+       if ( !vei ) {
+               vei = ch_calloc( 1, sizeof(variantEntry_info) );
+               vei->ov = ov;
+               vei->type = VARIANT_INFO_PLAIN;
+               LDAP_SLIST_INIT(&vei->attributes);
+               LDAP_STAILQ_ENTRY_INIT(vei, next);
+               LDAP_STAILQ_INSERT_TAIL(&ov->variants, vei, next);
+
+               ca->ca_private = vei;
+       }
+       vei->dn = ca->value_ndn;
+       ber_memfree( ca->value_dn.bv_val );
+
+       /* Each DN should only be listed once */
+       LDAP_STAILQ_FOREACH( vei2, &vei->ov->variants, next ) {
+               if ( vei == vei2 ) continue;
+
+               dnMatch( &diff, 0, NULL, NULL, &vei->dn, &vei2->dn );
+               if ( !diff ) {
+                       ca->reply.err = LDAP_CONSTRAINT_VIOLATION;
+                       return ca->reply.err;
+               }
+       }
+
+       return LDAP_SUCCESS;
+}
+
+static int
+variant_set_regex( ConfigArgs *ca )
+{
+       variantEntry_info *vei2, *vei = ca->ca_private;
+       slap_overinst *on = (slap_overinst *)ca->bi;
+       variant_info_t *ov = on->on_bi.bi_private;
+
+       if ( ca->op == SLAP_CONFIG_EMIT ) {
+               ca->value_bv = vei->dn;
+               return LDAP_SUCCESS;
+       } else if ( ca->op == LDAP_MOD_DELETE ) {
+               ber_memfree( vei->dn.bv_val );
+               BER_BVZERO( &vei->dn );
+               regfree( vei->regex );
+               return LDAP_SUCCESS;
+       }
+
+       if ( !vei ) {
+               vei = ch_calloc( 1, sizeof(variantEntry_info) );
+               vei->ov = ov;
+               vei->type = VARIANT_INFO_REGEX;
+               LDAP_SLIST_INIT(&vei->attributes);
+               LDAP_STAILQ_ENTRY_INIT(vei, next);
+               LDAP_STAILQ_INSERT_TAIL(&ov->regex_variants, vei, next);
+
+               ca->ca_private = vei;
+       }
+       vei->dn = ca->value_bv;
+
+       /* Each regex should only be listed once */
+       LDAP_STAILQ_FOREACH( vei2, &vei->ov->regex_variants, next ) {
+               if ( vei == vei2 ) continue;
+
+               if ( !ber_bvcmp( &ca->value_bv, &vei2->dn ) ) {
+                       ch_free( vei );
+                       ca->ca_private = NULL;
+                       ca->reply.err = LDAP_CONSTRAINT_VIOLATION;
+                       return ca->reply.err;
+               }
+       }
+
+       vei->regex = ch_calloc( 1, sizeof(regex_t) );
+       if ( regcomp( vei->regex, vei->dn.bv_val, REG_EXTENDED ) ) {
+               ch_free( vei->regex );
+               ca->reply.err = LDAP_CONSTRAINT_VIOLATION;
+               return ca->reply.err;
+       }
+
+       return LDAP_SUCCESS;
+}
+
+static int
+variant_set_alt_dn( ConfigArgs *ca )
+{
+       variantAttr_info *vai = ca->ca_private;
+
+       if ( ca->op == SLAP_CONFIG_EMIT ) {
+               value_add_one( &ca->rvalue_vals, &vai->dn );
+               return LDAP_SUCCESS;
+       } else if ( ca->op == LDAP_MOD_DELETE ) {
+               ber_memfree( vai->dn.bv_val );
+               BER_BVZERO( &vai->dn );
+               return LDAP_SUCCESS;
+       }
+
+       vai->dn = ca->value_ndn;
+       ber_memfree( ca->value_dn.bv_val );
+
+       return LDAP_SUCCESS;
+}
+
+static int
+variant_set_alt_pattern( ConfigArgs *ca )
+{
+       variantAttr_info *vai = ca->ca_private;
+       char *p = ca->value_bv.bv_val,
+                *end = ca->value_bv.bv_val + ca->value_bv.bv_len;
+
+       if ( ca->op == SLAP_CONFIG_EMIT ) {
+               ca->value_bv = vai->dn;
+               return LDAP_SUCCESS;
+       } else if ( ca->op == LDAP_MOD_DELETE ) {
+               ber_memfree( vai->dn.bv_val );
+               BER_BVZERO( &vai->dn );
+               return LDAP_SUCCESS;
+       }
+
+       while ( (p = memchr( p, '$', end - p )) != NULL ) {
+               p += 1;
+
+               if ( ( ( *p >= '0' ) && ( *p <= '9' ) ) || ( *p == '$' ) ) {
+                       p += 1;
+               } else {
+                       Debug( LDAP_DEBUG_ANY, "variant_set_alt_pattern: "
+                                       "invalid replacement pattern supplied '%s'\n",
+                                       ca->value_bv.bv_val );
+                       ca->reply.err = LDAP_CONSTRAINT_VIOLATION;
+                       return ca->reply.err;
+               }
+       }
+
+       vai->dn = ca->value_bv;
+
+       return LDAP_SUCCESS;
+}
+
+static int
+variant_set_attribute( ConfigArgs *ca )
+{
+       variantAttr_info *vai2, *vai = ca->ca_private;
+       char *s = ca->value_string;
+       const char *text;
+       AttributeDescription **ad;
+       int rc;
+
+       if ( ca->type == VARIANT_ATTR ) {
+               ad = &vai->attr;
+       } else {
+               ad = &vai->alternative;
+       }
+
+       if ( ca->op == SLAP_CONFIG_EMIT ) {
+               ca->value_string = ch_strdup( (*ad)->ad_cname.bv_val );
+               return LDAP_SUCCESS;
+       } else if ( ca->op == LDAP_MOD_DELETE ) {
+               *ad = NULL;
+               return LDAP_SUCCESS;
+       }
+
+       if ( *s == '{' ) {
+               s = strchr( s, '}' );
+               if ( !s ) {
+                       ca->reply.err = LDAP_UNDEFINED_TYPE;
+                       return ca->reply.err;
+               }
+               s += 1;
+       }
+
+       rc = slap_str2ad( s, ad, &text );
+       ber_memfree( ca->value_string );
+       if ( rc ) {
+               return rc;
+       }
+
+       /* Both attributes have to share the same syntax */
+       if ( vai->attr && vai->alternative &&
+                       vai->attr->ad_type->sat_syntax !=
+                                       vai->alternative->ad_type->sat_syntax ) {
+               ca->reply.err = LDAP_CONSTRAINT_VIOLATION;
+               return ca->reply.err;
+       }
+
+       if ( ca->type == VARIANT_ATTR ) {
+               /* Each attribute should only be listed once */
+               LDAP_SLIST_FOREACH( vai2, &vai->variant->attributes, next ) {
+                       if ( vai == vai2 ) continue;
+                       if ( vai->attr == vai2->attr ) {
+                               ca->reply.err = LDAP_CONSTRAINT_VIOLATION;
+                               return ca->reply.err;
+                       }
+               }
+       }
+
+       return LDAP_SUCCESS;
+}
+
+static int
+variant_add_alt_attr( ConfigArgs *ca )
+{
+       slap_overinst *on = (slap_overinst *)ca->bi;
+       variant_info_t *ov = on->on_bi.bi_private;
+       variantEntry_info *vei =
+                       LDAP_STAILQ_LAST( &ov->variants, variantEntry_info, next );
+       variantAttr_info *vai;
+       struct berval dn, ndn;
+       int rc;
+
+       vai = ch_calloc( 1, sizeof(variantAttr_info) );
+       vai->variant = vei;
+       LDAP_SLIST_ENTRY_INIT( vai, next );
+       ca->ca_private = vai;
+
+       ca->value_string = ch_strdup( ca->argv[1] );
+       ca->type = VARIANT_ATTR;
+       rc = variant_set_attribute( ca );
+       if ( rc != LDAP_SUCCESS ) {
+               goto done;
+       }
+
+       ca->value_string = ch_strdup( ca->argv[2] );
+       ca->type = VARIANT_ATTR_ALT;
+       rc = variant_set_attribute( ca );
+       if ( rc != LDAP_SUCCESS ) {
+               goto done;
+       }
+
+       dn.bv_val = ca->argv[3];
+       dn.bv_len = strlen( dn.bv_val );
+       rc = dnNormalize( 0, NULL, NULL, &dn, &ndn, NULL );
+       if ( rc != LDAP_SUCCESS ) {
+               goto done;
+       }
+
+       ca->type = 0;
+       BER_BVZERO( &ca->value_dn );
+       ca->value_ndn = ndn;
+       rc = variant_set_alt_dn( ca );
+       if ( rc != LDAP_SUCCESS ) {
+               ch_free( ndn.bv_val );
+               goto done;
+       }
+
+done:
+       if ( rc == LDAP_SUCCESS ) {
+               LDAP_SLIST_INSERT_HEAD( &vei->attributes, vai, next );
+       } else {
+               ca->reply.err = rc;
+       }
+
+       return rc;
+}
+
+static int
+variant_add_alt_attr_regex( ConfigArgs *ca )
+{
+       slap_overinst *on = (slap_overinst *)ca->bi;
+       variant_info_t *ov = on->on_bi.bi_private;
+       variantEntry_info *vei =
+                       LDAP_STAILQ_LAST( &ov->regex_variants, variantEntry_info, next );
+       variantAttr_info *vai;
+       int rc;
+
+       vai = ch_calloc( 1, sizeof(variantAttr_info) );
+       vai->variant = vei;
+       LDAP_SLIST_ENTRY_INIT( vai, next );
+       ca->ca_private = vai;
+
+       ca->value_string = ch_strdup( ca->argv[1] );
+       ca->type = VARIANT_ATTR;
+       rc = variant_set_attribute( ca );
+       if ( rc != LDAP_SUCCESS ) {
+               goto done;
+       }
+
+       ca->value_string = ch_strdup( ca->argv[2] );
+       ca->type = VARIANT_ATTR_ALT;
+       rc = variant_set_attribute( ca );
+       if ( rc != LDAP_SUCCESS ) {
+               goto done;
+       }
+
+       ca->type = 0;
+       ber_str2bv( ca->argv[3], 0, 1, &ca->value_bv );
+       rc = variant_set_alt_pattern( ca );
+       if ( rc != LDAP_SUCCESS ) {
+               goto done;
+       }
+
+done:
+       if ( rc == LDAP_SUCCESS ) {
+               LDAP_SLIST_INSERT_HEAD( &vei->attributes, vai, next );
+       } else {
+               ca->reply.err = rc;
+       }
+
+       return rc;
+}
+
+static int
+variant_ldadd_cleanup( ConfigArgs *ca )
+{
+       variantEntry_info *vei = ca->ca_private;
+       slap_overinst *on = (slap_overinst *)ca->bi;
+       variant_info_t *ov = on->on_bi.bi_private;
+
+       if ( ca->reply.err != LDAP_SUCCESS ) {
+               assert( LDAP_SLIST_EMPTY(&vei->attributes) );
+               ch_free( vei );
+               return LDAP_SUCCESS;
+       }
+
+       if ( vei->type == VARIANT_INFO_PLAIN ) {
+               LDAP_STAILQ_INSERT_TAIL(&ov->variants, vei, next);
+       } else {
+               LDAP_STAILQ_INSERT_TAIL(&ov->regex_variants, vei, next);
+       }
+
+       return LDAP_SUCCESS;
+}
+
+static int
+variant_ldadd( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
+{
+       slap_overinst *on;
+       variant_info_t *ov;
+       variantEntry_info *vei;
+
+       if ( cei->ce_type != Cft_Overlay || !cei->ce_bi ||
+                       cei->ce_bi->bi_cf_ocs != variant_ocs )
+               return LDAP_CONSTRAINT_VIOLATION;
+
+       on = (slap_overinst *)cei->ce_bi;
+       ov = on->on_bi.bi_private;
+
+       vei = ch_calloc( 1, sizeof(variantEntry_info) );
+       vei->ov = ov;
+       vei->type = VARIANT_INFO_PLAIN;
+       LDAP_SLIST_INIT(&vei->attributes);
+       LDAP_STAILQ_ENTRY_INIT(vei, next);
+
+       ca->bi = cei->ce_bi;
+       ca->ca_private = vei;
+       config_push_cleanup( ca, variant_ldadd_cleanup );
+       /* config_push_cleanup is only run in the case of online config but we use it to
+        * save the new config when done with the entry */
+       ca->lineno = 0;
+
+       return LDAP_SUCCESS;
+}
+
+static int
+variant_regex_ldadd( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
+{
+       slap_overinst *on;
+       variant_info_t *ov;
+       variantEntry_info *vei;
+
+       if ( cei->ce_type != Cft_Overlay || !cei->ce_bi ||
+                       cei->ce_bi->bi_cf_ocs != variant_ocs )
+               return LDAP_CONSTRAINT_VIOLATION;
+
+       on = (slap_overinst *)cei->ce_bi;
+       ov = on->on_bi.bi_private;
+
+       vei = ch_calloc( 1, sizeof(variantEntry_info) );
+       vei->ov = ov;
+       vei->type = VARIANT_INFO_REGEX;
+       LDAP_SLIST_INIT(&vei->attributes);
+       LDAP_STAILQ_ENTRY_INIT(vei, next);
+
+       ca->bi = cei->ce_bi;
+       ca->ca_private = vei;
+       config_push_cleanup( ca, variant_ldadd_cleanup );
+       /* config_push_cleanup is only run in the case of online config but we use it to
+        * save the new config when done with the entry */
+       ca->lineno = 0;
+
+       return LDAP_SUCCESS;
+}
+
+static int
+variant_attr_ldadd_cleanup( ConfigArgs *ca )
+{
+       variantAttr_info *vai = ca->ca_private;
+       variantEntry_info *vei = vai->variant;
+
+       if ( ca->reply.err != LDAP_SUCCESS ) {
+               ch_free( vai );
+               return LDAP_SUCCESS;
+       }
+
+       LDAP_SLIST_INSERT_HEAD(&vei->attributes, vai, next);
+
+       return LDAP_SUCCESS;
+}
+
+static int
+variant_attr_ldadd( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
+{
+       variantEntry_info *vei;
+       variantAttr_info *vai;
+       CfEntryInfo *parent = cei->ce_parent;
+
+       if ( cei->ce_type != Cft_Misc || !parent || !parent->ce_bi ||
+                       parent->ce_bi->bi_cf_ocs != variant_ocs )
+               return LDAP_CONSTRAINT_VIOLATION;
+
+       vei = (variantEntry_info *)cei->ce_private;
+
+       vai = ch_calloc( 1, sizeof(variantAttr_info) );
+       vai->variant = vei;
+       LDAP_SLIST_ENTRY_INIT(vai, next);
+
+       ca->ca_private = vai;
+       config_push_cleanup( ca, variant_attr_ldadd_cleanup );
+       /* config_push_cleanup is only run in the case of online config but we use it to
+        * save the new config when done with the entry */
+       ca->lineno = 0;
+
+       return LDAP_SUCCESS;
+}
+
+static int
+variant_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
+{
+       slap_overinst *on = (slap_overinst *)ca->bi;
+       variant_info_t *ov = on->on_bi.bi_private;
+       variantEntry_info *vei;
+       variantAttr_info *vai;
+       Entry *e;
+       struct berval rdn;
+       int i = 0;
+
+       LDAP_STAILQ_FOREACH( vei, &ov->variants, next ) {
+               int j = 0;
+               rdn.bv_len = snprintf(
+                               ca->cr_msg, sizeof(ca->cr_msg), "name={%d}variant", i++ );
+               rdn.bv_val = ca->cr_msg;
+
+               ca->ca_private = vei;
+               e = config_build_entry(
+                               op, rs, p->e_private, ca, &rdn, &variant_ocs[1], NULL );
+               assert( e );
+
+               LDAP_SLIST_FOREACH( vai, &vei->attributes, next ) {
+                       rdn.bv_len = snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+                                       "olcVariantVariantAttribute={%d}%s", j++,
+                                       vai->attr->ad_cname.bv_val );
+                       rdn.bv_val = ca->cr_msg;
+
+                       ca->ca_private = vai;
+                       config_build_entry(
+                                       op, rs, e->e_private, ca, &rdn, &variant_ocs[2], NULL );
+               }
+       }
+
+       LDAP_STAILQ_FOREACH( vei, &ov->regex_variants, next ) {
+               int j = 0;
+               rdn.bv_len = snprintf(
+                               ca->cr_msg, sizeof(ca->cr_msg), "name={%d}regex", i++ );
+               rdn.bv_val = ca->cr_msg;
+
+               ca->ca_private = vei;
+               e = config_build_entry(
+                               op, rs, p->e_private, ca, &rdn, &variant_ocs[3], NULL );
+               assert( e );
+
+               LDAP_SLIST_FOREACH( vai, &vei->attributes, next ) {
+                       rdn.bv_len = snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+                                       "olcVariantVariantAttribute={%d}%s", j++,
+                                       vai->attr->ad_cname.bv_val );
+                       rdn.bv_val = ca->cr_msg;
+
+                       ca->ca_private = vai;
+                       config_build_entry(
+                                       op, rs, e->e_private, ca, &rdn, &variant_ocs[4], NULL );
+               }
+       }
+       return LDAP_SUCCESS;
+}
+
+static slap_overinst variant;
+
+static int
+variant_db_init( BackendDB *be, ConfigReply *cr )
+{
+       slap_overinst *on = (slap_overinst *)be->bd_info;
+       variant_info_t *ov;
+
+       if ( SLAP_ISGLOBALOVERLAY(be) ) {
+               Debug( LDAP_DEBUG_ANY, "variant overlay must be instantiated within "
+                               "a database.\n" );
+               return 1;
+       }
+
+       ov = ch_calloc( 1, sizeof(variant_info_t) );
+       LDAP_STAILQ_INIT(&ov->variants);
+       LDAP_STAILQ_INIT(&ov->regex_variants);
+
+       on->on_bi.bi_private = ov;
+
+       return LDAP_SUCCESS;
+}
+
+static int
+variant_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+       slap_overinst *on = (slap_overinst *)be->bd_info;
+       variant_info_t *ov = on->on_bi.bi_private;
+
+       if ( ov ) {
+               while ( !LDAP_STAILQ_EMPTY( &ov->variants ) ) {
+                       variantEntry_info *vei = LDAP_STAILQ_FIRST( &ov->variants );
+                       LDAP_STAILQ_REMOVE_HEAD( &ov->variants, next );
+
+                       while ( !LDAP_SLIST_EMPTY( &vei->attributes ) ) {
+                               variantAttr_info *vai = LDAP_SLIST_FIRST( &vei->attributes );
+                               LDAP_SLIST_REMOVE_HEAD( &vei->attributes, next );
+
+                               ber_memfree( vai->dn.bv_val );
+                               ch_free( vai );
+                       }
+                       ber_memfree( vei->dn.bv_val );
+                       ch_free( vei );
+               }
+               while ( !LDAP_STAILQ_EMPTY( &ov->regex_variants ) ) {
+                       variantEntry_info *vei = LDAP_STAILQ_FIRST( &ov->regex_variants );
+                       LDAP_STAILQ_REMOVE_HEAD( &ov->regex_variants, next );
+
+                       while ( !LDAP_SLIST_EMPTY( &vei->attributes ) ) {
+                               variantAttr_info *vai = LDAP_SLIST_FIRST( &vei->attributes );
+                               LDAP_SLIST_REMOVE_HEAD( &vei->attributes, next );
+
+                               ber_memfree( vai->dn.bv_val );
+                               ch_free( vai );
+                       }
+                       ber_memfree( vei->dn.bv_val );
+                       ch_free( vei );
+               }
+               ch_free( ov );
+       }
+
+       return LDAP_SUCCESS;
+}
+
+int
+variant_initialize()
+{
+       int rc;
+
+       variant.on_bi.bi_type = "variant";
+       variant.on_bi.bi_db_init = variant_db_init;
+       variant.on_bi.bi_db_destroy = variant_db_destroy;
+
+       variant.on_bi.bi_op_add = variant_op_add;
+       variant.on_bi.bi_op_compare = variant_op_compare;
+       variant.on_bi.bi_op_modify = variant_op_mod;
+       variant.on_bi.bi_op_search = variant_op_search;
+
+       variant.on_bi.bi_cf_ocs = variant_ocs;
+
+       rc = config_register_schema( variant_cfg, variant_ocs );
+       if ( rc ) return rc;
+
+       return overlay_register( &variant );
+}
+
+#if SLAPD_OVER_VARIANT == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+       return variant_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_VARIANT */