summaryrefslogtreecommitdiffstats
path: root/contrib/slapd-modules/variant
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/slapd-modules/variant')
-rw-r--r--contrib/slapd-modules/variant/Makefile77
-rw-r--r--contrib/slapd-modules/variant/slapo-variant.5472
-rw-r--r--contrib/slapd-modules/variant/tests/Rules.mk23
-rw-r--r--contrib/slapd-modules/variant/tests/data/additional-config.ldif23
-rw-r--r--contrib/slapd-modules/variant/tests/data/config.ldif89
-rw-r--r--contrib/slapd-modules/variant/tests/data/hidden.ldif4
-rw-r--r--contrib/slapd-modules/variant/tests/data/test001-01-same-dn.ldif4
-rw-r--r--contrib/slapd-modules/variant/tests/data/test001-01a-same-dn.ldif4
-rw-r--r--contrib/slapd-modules/variant/tests/data/test001-02-same-attribute.ldif6
-rw-r--r--contrib/slapd-modules/variant/tests/data/test001-03-different-types.ldif4
-rw-r--r--contrib/slapd-modules/variant/tests/data/test002-01-entry.ldif16
-rw-r--r--contrib/slapd-modules/variant/tests/data/test002-02-regex.ldif7
-rw-r--r--contrib/slapd-modules/variant/tests/data/test003-out.ldif124
-rw-r--r--contrib/slapd-modules/variant/tests/data/test005-changes.ldif35
-rw-r--r--contrib/slapd-modules/variant/tests/data/test005-modify-missing.ldif4
-rw-r--r--contrib/slapd-modules/variant/tests/data/test005-out.ldif206
-rw-r--r--contrib/slapd-modules/variant/tests/data/test005-variant-missing.ldif4
-rw-r--r--contrib/slapd-modules/variant/tests/data/test006-config.ldif61
-rw-r--r--contrib/slapd-modules/variant/tests/data/test006-out.ldif151
-rw-r--r--contrib/slapd-modules/variant/tests/data/test007-out.ldif6
-rw-r--r--contrib/slapd-modules/variant/tests/data/test010-out.ldif52
-rw-r--r--contrib/slapd-modules/variant/tests/data/test011-out.ldif10
-rw-r--r--contrib/slapd-modules/variant/tests/data/test012-data.ldif13
-rw-r--r--contrib/slapd-modules/variant/tests/data/test012-out.ldif9
-rw-r--r--contrib/slapd-modules/variant/tests/data/variant.conf17
-rwxr-xr-xcontrib/slapd-modules/variant/tests/run229
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/all102
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/common.sh115
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/test001-config209
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/test002-add-delete113
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/test003-search113
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/test004-compare63
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/test005-modify120
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/test006-acl323
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/test007-subtypes67
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/test008-variant-replication194
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/test009-ignored-replication227
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/test010-limits99
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/test011-referral169
-rwxr-xr-xcontrib/slapd-modules/variant/tests/scripts/test012-crossdb90
-rw-r--r--contrib/slapd-modules/variant/variant.c1424
41 files changed, 5078 insertions, 0 deletions
diff --git a/contrib/slapd-modules/variant/Makefile b/contrib/slapd-modules/variant/Makefile
new file mode 100644
index 0000000..07effed
--- /dev/null
+++ b/contrib/slapd-modules/variant/Makefile
@@ -0,0 +1,77 @@
+# $OpenLDAP$
+# This work is part of OpenLDAP Software <http://www.openldap.org/>.
+#
+# Copyright 1998-2022 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
+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
+
+.SUFFIXES: .c .o .lo
+
+.c.lo:
+ $(LIBTOOL) --mode=compile $(CC) $(CFLAGS) $(OPT) $(CPPFLAGS) $(DEFS) $(INCS) -c $<
+
+variant.la: variant.lo
+ $(LIBTOOL) --mode=link $(CC) $(LDFLAGS) -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
index 0000000..a480744
--- /dev/null
+++ b/contrib/slapd-modules/variant/slapo-variant.5
@@ -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/Rules.mk b/contrib/slapd-modules/variant/tests/Rules.mk
new file mode 100644
index 0000000..c25c1d2
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/Rules.mk
@@ -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
index 0000000..6a286fe
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/additional-config.ldif
@@ -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
index 0000000..6e323b9
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/config.ldif
@@ -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
index 0000000..d219746
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/hidden.ldif
@@ -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
index 0000000..880e035
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test001-01-same-dn.ldif
@@ -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
index 0000000..0fb8b2b
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test001-01a-same-dn.ldif
@@ -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
index 0000000..8447018
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test001-02-same-attribute.ldif
@@ -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
index 0000000..dfbde5b
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test001-03-different-types.ldif
@@ -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
index 0000000..21b5b14
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test002-01-entry.ldif
@@ -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
index 0000000..8f0f439
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test002-02-regex.ldif
@@ -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
index 0000000..1c3ca5d
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test003-out.ldif
@@ -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
index 0000000..767f48a
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test005-changes.ldif
@@ -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
index 0000000..ce9c007
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test005-modify-missing.ldif
@@ -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
index 0000000..67e441b
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test005-out.ldif
@@ -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
index 0000000..54fd3a5
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test005-variant-missing.ldif
@@ -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
index 0000000..c668134
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test006-config.ldif
@@ -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
index 0000000..03910c0
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test006-out.ldif
@@ -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
index 0000000..cf1aac8
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test007-out.ldif
@@ -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
index 0000000..28603e1
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test010-out.ldif
@@ -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
index 0000000..07604f8
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test011-out.ldif
@@ -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
index 0000000..8b8d8b3
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test012-data.ldif
@@ -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
index 0000000..bd31fa0
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/test012-out.ldif
@@ -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
index 0000000..dba6c46
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/data/variant.conf
@@ -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
index 0000000..6a38431
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/run
@@ -0,0 +1,229 @@
+#!/bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 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
index 0000000..d6d6dc7
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/all
@@ -0,0 +1,102 @@
+#! /bin/sh
+# $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 1998-2022 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
index 0000000..3b155ad
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/common.sh
@@ -0,0 +1,115 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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
index 0000000..7a5559f
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/test001-config
@@ -0,0 +1,209 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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 support..."
+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
index 0000000..bd316b2
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/test002-add-delete
@@ -0,0 +1,113 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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
index 0000000..2284ab7
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/test003-search
@@ -0,0 +1,113 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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
index 0000000..c87d347
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/test004-compare
@@ -0,0 +1,63 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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
index 0000000..4cbf289
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/test005-modify
@@ -0,0 +1,120 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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
index 0000000..6b34fb8
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/test006-acl
@@ -0,0 +1,323 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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
index 0000000..177fc33
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/test007-subtypes
@@ -0,0 +1,67 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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
index 0000000..63e2d7e
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/test008-variant-replication
@@ -0,0 +1,194 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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
index 0000000..aefbfa9
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/test009-ignored-replication
@@ -0,0 +1,227 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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
index 0000000..5828922
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/test010-limits
@@ -0,0 +1,99 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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
index 0000000..37d6d8c
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/test011-referral
@@ -0,0 +1,169 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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
index 0000000..8854a1b
--- /dev/null
+++ b/contrib/slapd-modules/variant/tests/scripts/test012-crossdb
@@ -0,0 +1,90 @@
+#! /bin/sh
+## $OpenLDAP$
+## This work is part of OpenLDAP Software <http://www.openldap.org/>.
+##
+## Copyright 2016-2022 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
index 0000000..edf4832
--- /dev/null
+++ b/contrib/slapd-modules/variant/variant.c
@@ -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 = ldap_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 = ldap_avl_insert( &ops, nop, variant_cmp_op, ldap_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? */
+ ldap_avl_apply( ops, variant_run_mod, &rc, -1, AVL_INORDER );
+ }
+
+ ldap_avl_apply( ops, variant_reassign_mods, op, -1, AVL_INORDER );
+ ldap_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 */