summaryrefslogtreecommitdiffstats
path: root/contrib/slapd-modules/datamorph
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:35:32 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:35:32 +0000
commit5ea77a75dd2d2158401331879f3c8f47940a732c (patch)
treed89dc06e9f4850a900f161e25f84e922c4f86cc8 /contrib/slapd-modules/datamorph
parentInitial commit. (diff)
downloadopenldap-5ea77a75dd2d2158401331879f3c8f47940a732c.tar.xz
openldap-5ea77a75dd2d2158401331879f3c8f47940a732c.zip
Adding upstream version 2.5.13+dfsg.upstream/2.5.13+dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'contrib/slapd-modules/datamorph')
-rw-r--r--contrib/slapd-modules/datamorph/Makefile77
-rw-r--r--contrib/slapd-modules/datamorph/datamorph.c2091
-rw-r--r--contrib/slapd-modules/datamorph/slapo-datamorph.5338
-rw-r--r--contrib/slapd-modules/datamorph/tests/Rules.mk23
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/config.ldif108
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/datamorph.conf49
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test.ldif434
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test001-01-same-attr.ldif3
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test001-02-same-index.ldif4
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test001-02a-same-index.ldif4
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test001-03-invalid-attr.ldif3
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test002-config.ldif9
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test002-entry.ldif31
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test002-fail.ldif23
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test002-transformed-rdn.ldif5
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test003-config.ldif30
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test003-out.ldif125
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test005-01-fail.ldif5
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test005-02-fail.ldif5
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test005-03-fail.ldif5
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test005-03a-fail.ldif5
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test005-04-fail.ldif10
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test005-04a-fail.ldif6
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test005-changes.ldif30
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test005-out.ldif212
-rw-r--r--contrib/slapd-modules/datamorph/tests/data/test007-config.ldif30
-rwxr-xr-xcontrib/slapd-modules/datamorph/tests/run229
-rwxr-xr-xcontrib/slapd-modules/datamorph/tests/scripts/all102
-rwxr-xr-xcontrib/slapd-modules/datamorph/tests/scripts/common.sh152
-rwxr-xr-xcontrib/slapd-modules/datamorph/tests/scripts/test001-config248
-rwxr-xr-xcontrib/slapd-modules/datamorph/tests/scripts/test002-add-delete147
-rwxr-xr-xcontrib/slapd-modules/datamorph/tests/scripts/test003-search106
-rwxr-xr-xcontrib/slapd-modules/datamorph/tests/scripts/test004-compare62
-rwxr-xr-xcontrib/slapd-modules/datamorph/tests/scripts/test005-modify89
-rwxr-xr-xcontrib/slapd-modules/datamorph/tests/scripts/test006-modrdn52
-rwxr-xr-xcontrib/slapd-modules/datamorph/tests/scripts/test007-transformed-replication296
-rwxr-xr-xcontrib/slapd-modules/datamorph/tests/scripts/test008-ignored-replication299
37 files changed, 5447 insertions, 0 deletions
diff --git a/contrib/slapd-modules/datamorph/Makefile b/contrib/slapd-modules/datamorph/Makefile
new file mode 100644
index 0000000..82bce49
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/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_DATAMORPH=SLAPD_MOD_DYNAMIC
+INCS = $(LDAP_INC)
+LIBS = $(LDAP_LIB)
+
+PROGRAMS = datamorph.la
+MANPAGES = slapo-datamorph.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 $<
+
+datamorph.la: datamorph.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/datamorph/datamorph.c b/contrib/slapd-modules/datamorph/datamorph.c
new file mode 100644
index 0000000..7767586
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/datamorph.c
@@ -0,0 +1,2091 @@
+/* datamorph.c - enumerated and native integer value support */
+/* $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 work was developed in 2016 by Ondřej Kuzník for Symas Corp.
+ */
+
+#include "portable.h"
+
+#ifdef SLAPD_OVER_DATAMORPH
+
+#include <inttypes.h>
+#include <ac/stdlib.h>
+
+#if defined(__linux__)
+#include <endian.h>
+
+#elif defined(sun)
+
+#define be16toh(x) BE_16(x)
+#define le16toh(x) LE_16(x)
+#define htobe16(x) BE_16(x)
+#define htole16(x) LE_16(x)
+
+#define be32toh(x) BE_32(x)
+#define le32toh(x) LE_32(x)
+#define htobe32(x) BE_32(x)
+#define htole32(x) LE_32(x)
+
+#define be64toh(x) BE_64(x)
+#define le64toh(x) LE_64(x)
+#define htobe64(x) BE_64(x)
+#define htole64(x) LE_64(x)
+
+#elif defined(__NetBSD__) || defined(__FreeBSD__)
+#include <sys/endian.h>
+
+#elif defined(__OpenBSD__)
+#include <sys/endian.h>
+
+#define be16toh(x) betoh16(x)
+#define le16toh(x) letoh16(x)
+
+#define be32toh(x) betoh32(x)
+#define le32toh(x) letoh32(x)
+
+#define be64toh(x) betoh64(x)
+#define le64toh(x) letoh64(x)
+
+#elif defined(__BYTE_ORDER__) && \
+ ( defined(__GNUC__) || defined(__clang__) )
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+#define be16toh(x) __builtin_bswap16(x)
+#define le16toh(x) (x)
+#define htobe16(x) __builtin_bswap16(x)
+#define htole16(x) (x)
+
+#define be32toh(x) __builtin_bswap32(x)
+#define le32toh(x) (x)
+#define htobe32(x) __builtin_bswap32(x)
+#define htole32(x) (x)
+
+#define be64toh(x) __builtin_bswap64(x)
+#define le64toh(x) (x)
+#define htobe64(x) __builtin_bswap64(x)
+#define htole64(x) (x)
+
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define be16toh(x) (x)
+#define le16toh(x) __builtin_bswap16(x)
+#define htobe16(x) (x)
+#define htole16(x) __builtin_bswap16(x)
+
+#define be32toh(x) (x)
+#define le32toh(x) __builtin_bswap32(x)
+#define htobe32(x) (x)
+#define htole32(x) __builtin_bswap32(x)
+
+#define be64toh(x) (x)
+#define le64toh(x) __builtin_bswap64(x)
+#define htobe64(x) (x)
+#define htole64(x) __builtin_bswap64(x)
+
+#else
+#error "Only support pure big and little endian at the moment"
+#endif
+
+#else
+#error "I lack the way to check my endianness and convert to/from big-endian"
+#endif
+
+#include "slap.h"
+#include "slap-config.h"
+#include "lutil.h"
+#include "ldap_queue.h"
+
+typedef enum datamorph_type_t {
+ DATAMORPH_UNSET,
+ DATAMORPH_ENUM,
+ DATAMORPH_INT,
+} datamorph_type;
+
+typedef enum datamorph_flags_t {
+ DATAMORPH_FLAG_SIGNED = 1 << 0,
+ DATAMORPH_FLAG_LOWER = 1 << 1,
+ DATAMORPH_FLAG_UPPER = 1 << 2,
+} datamorph_flags;
+
+typedef union datamorph_interval_bound_t {
+ int64_t i;
+ uint64_t u;
+} datamorph_interval_bound;
+
+typedef struct transformation_info_t {
+ AttributeDescription *attr;
+ datamorph_type type;
+ union {
+ struct {
+ Avlnode *to_db;
+ struct berval from_db[256];
+ } maps;
+#define ti_enum info.maps
+ struct {
+ datamorph_flags flags;
+ unsigned int size;
+ datamorph_interval_bound lower, upper;
+ } interval;
+#define ti_int info.interval
+ } info;
+} transformation_info;
+
+typedef struct datamorph_enum_mapping_t {
+ struct berval wire_value;
+ uint8_t db_value;
+ transformation_info *transformation;
+} datamorph_enum_mapping;
+
+typedef struct datamorph_info_t {
+ Avlnode *transformations;
+ transformation_info *wip_transformation;
+} datamorph_info;
+
+static int
+transformation_mapping_cmp( const void *l, const void *r )
+{
+ const datamorph_enum_mapping *left = l, *right = r;
+
+ return ber_bvcmp( &left->wire_value, &right->wire_value );
+}
+
+static int
+transformation_info_cmp( const void *l, const void *r )
+{
+ const transformation_info *left = l, *right = r;
+
+ return ( left->attr == right->attr ) ? 0 :
+ ( left->attr < right->attr ) ? -1 :
+ 1;
+}
+
+static int
+transform_to_db_format_one(
+ Operation *op,
+ transformation_info *definition,
+ struct berval *value,
+ struct berval *outval )
+{
+ switch ( definition->type ) {
+ case DATAMORPH_ENUM: {
+ datamorph_enum_mapping *mapping, needle = { .wire_value = *value };
+ struct berval db_value = { .bv_len = 1 };
+
+ mapping = ldap_avl_find( definition->ti_enum.to_db, &needle,
+ transformation_mapping_cmp );
+ if ( !mapping ) {
+ Debug( LDAP_DEBUG_ANY, "transform_to_db_format_one: "
+ "value '%s' not mapped\n",
+ value->bv_val );
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+
+ db_value.bv_val = (char *)&mapping->db_value;
+ ber_dupbv( outval, &db_value );
+ assert( outval->bv_val );
+ break;
+ }
+
+ case DATAMORPH_INT: {
+ union {
+ char s[8];
+ uint8_t be8;
+ uint16_t be16;
+ uint32_t be32;
+ uint64_t be64;
+ } buf;
+ struct berval db_value = { .bv_val = buf.s };
+ char *ptr = value->bv_val + value->bv_len;
+ uint64_t unsigned_value;
+ int64_t signed_value;
+
+ assert( definition->ti_int.size == 1 ||
+ definition->ti_int.size == 2 ||
+ definition->ti_int.size == 4 ||
+ definition->ti_int.size == 8 );
+
+ /* Read number */
+ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ signed_value = strtoll( value->bv_val, &ptr, 10 );
+ } else {
+ unsigned_value = strtoull( value->bv_val, &ptr, 10 );
+ }
+ if ( *value->bv_val == '\0' || *ptr != '\0' ) {
+ Debug( LDAP_DEBUG_ANY, "transform_to_db_format_one: "
+ "value '%s' not an integer\n",
+ value->bv_val );
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ /* Check it's within configured bounds */
+ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ if ( signed_value < definition->ti_int.lower.i ||
+ signed_value > definition->ti_int.upper.i ) {
+ Debug( LDAP_DEBUG_ANY, "transform_to_db_format_one: "
+ "value '%s' doesn't fit configured constraints\n",
+ value->bv_val );
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ } else {
+ if ( unsigned_value < definition->ti_int.lower.u ||
+ unsigned_value > definition->ti_int.upper.u ) {
+ Debug( LDAP_DEBUG_ANY, "transform_to_db_format_one: "
+ "value '%s' doesn't fit configured constraints\n",
+ value->bv_val );
+ return LDAP_CONSTRAINT_VIOLATION;
+ }
+ }
+
+ db_value.bv_len = definition->ti_int.size;
+ switch ( definition->ti_int.size ) {
+ case 1: {
+ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ buf.be8 = (unsigned char)((char)signed_value);
+ } else {
+ buf.be8 = unsigned_value;
+ }
+ break;
+ }
+ case 2: {
+ uint16_t h16;
+ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ h16 = signed_value;
+ } else {
+ h16 = unsigned_value;
+ }
+ buf.be16 = htobe16( h16 );
+ break;
+ }
+ case 4: {
+ uint32_t h32;
+ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ h32 = signed_value;
+ } else {
+ h32 = unsigned_value;
+ }
+ buf.be32 = htobe32( h32 );
+ break;
+ }
+ case 8: {
+ uint64_t h64;
+ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ h64 = signed_value;
+ } else {
+ h64 = unsigned_value;
+ }
+ buf.be64 = htobe64( h64 );
+ break;
+ }
+ }
+ ber_dupbv( outval, &db_value );
+ assert( outval->bv_val );
+ break;
+ }
+
+ default:
+ assert(0);
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+transform_to_db_format(
+ Operation *op,
+ transformation_info *definition,
+ BerVarray values,
+ int numvals,
+ BerVarray *out )
+{
+ struct berval *value;
+ int i, rc = LDAP_SUCCESS;
+
+ if ( numvals == 0 ) {
+ for ( value = values; value; value++, numvals++ )
+ ; /* Count them */
+ }
+
+ assert( out );
+ *out = ch_calloc( numvals + 1, sizeof(struct berval) );
+
+ for ( i = 0; i < numvals; i++ ) {
+ rc = transform_to_db_format_one(
+ op, definition, &values[i], &(*out)[i] );
+ if ( rc ) {
+ break;
+ }
+ }
+
+ if ( rc ) {
+ for ( ; i >= 0; i-- ) {
+ ch_free((*out)[i].bv_val);
+ }
+ ch_free(*out);
+ }
+
+ return rc;
+}
+
+static int
+transform_from_db_format_one(
+ Operation *op,
+ transformation_info *definition,
+ struct berval *value,
+ struct berval *outval )
+{
+ switch ( definition->type ) {
+ case DATAMORPH_ENUM: {
+ uint8_t index = value->bv_val[0];
+ struct berval *val = &definition->info.maps.from_db[index];
+
+ if ( !BER_BVISNULL( val ) ) {
+ ber_dupbv( outval, val );
+ assert( outval->bv_val );
+ } else {
+ Debug( LDAP_DEBUG_ANY, "transform_from_db_format_one: "
+ "DB value %d has no mapping!\n",
+ index );
+ /* FIXME: probably still need to return an error */
+ BER_BVZERO( outval );
+ }
+ break;
+ }
+
+ case DATAMORPH_INT: {
+ char buf[24];
+ struct berval wire_value = { .bv_val = buf };
+ union lens_t {
+ uint8_t be8;
+ uint16_t be16;
+ uint32_t be32;
+ uint64_t be64;
+ } *lens = (union lens_t *)value->bv_val;
+ uint64_t unsigned_value;
+ int64_t signed_value;
+
+ if ( value->bv_len != definition->ti_int.size ) {
+ Debug( LDAP_DEBUG_ANY, "transform_from_db_format_one(%s): "
+ "unexpected DB value of length %lu when configured "
+ "for %u!\n",
+ definition->attr->ad_cname.bv_val, value->bv_len,
+ definition->ti_int.size );
+ /* FIXME: probably still need to return an error */
+ BER_BVZERO( outval );
+ break;
+ }
+
+ assert( definition->ti_int.size == 1 ||
+ definition->ti_int.size == 2 ||
+ definition->ti_int.size == 4 ||
+ definition->ti_int.size == 8 );
+
+ switch ( definition->ti_int.size ) {
+ case 1: {
+ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ signed_value = (int8_t)lens->be8;
+ } else {
+ unsigned_value = (uint8_t)lens->be8;
+ }
+ break;
+ }
+ case 2: {
+ uint16_t h16 = be16toh( lens->be16 );
+ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ signed_value = (int16_t)h16;
+ } else {
+ unsigned_value = (uint16_t)h16;
+ }
+ break;
+ }
+ case 4: {
+ uint32_t h32 = be32toh( lens->be32 );
+ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ signed_value = (int32_t)h32;
+ } else {
+ unsigned_value = (uint32_t)h32;
+ }
+ break;
+ }
+ case 8: {
+ uint64_t h64 = be64toh( lens->be64 );
+ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ signed_value = (int64_t)h64;
+ } else {
+ unsigned_value = (uint64_t)h64;
+ }
+ break;
+ }
+ }
+ if ( definition->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ wire_value.bv_len = sprintf( buf, "%" PRId64, signed_value );
+ } else {
+ wire_value.bv_len = sprintf( buf, "%" PRIu64, unsigned_value );
+ }
+ ber_dupbv( outval, &wire_value );
+ assert( outval->bv_val );
+ break;
+ }
+
+ default:
+ assert(0);
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+transform_from_db_format(
+ Operation *op,
+ transformation_info *definition,
+ BerVarray values,
+ int numvals,
+ BerVarray *out )
+{
+ struct berval *value;
+ int i, rc = LDAP_SUCCESS;
+
+ if ( numvals == 0 ) {
+ for ( value = values; value; value++, numvals++ )
+ ; /* Count them */
+ }
+
+ assert( out );
+ *out = ch_calloc( numvals + 1, sizeof(struct berval) );
+
+ for ( i = 0; i < numvals; i++ ) {
+ struct berval bv;
+ rc = transform_from_db_format_one( op, definition, &values[i], &bv );
+ if ( !BER_BVISNULL( &bv ) ) {
+ ber_bvarray_add( out, &bv );
+ }
+ if ( rc ) {
+ break;
+ }
+ }
+
+ if ( rc ) {
+ for ( ; i >= 0; i-- ) {
+ ch_free( (*out)[i].bv_val );
+ }
+ ch_free( *out );
+ }
+
+ return rc;
+}
+
+static int
+datamorph_filter( Operation *op, datamorph_info *ov, Filter *f )
+{
+ switch ( f->f_choice ) {
+ case LDAP_FILTER_PRESENT:
+ /* The matching rules are not in place,
+ * so the filter will be ignored */
+ case LDAP_FILTER_APPROX:
+ case LDAP_FILTER_SUBSTRINGS:
+ default:
+ break;
+ return LDAP_SUCCESS;
+
+ case LDAP_FILTER_AND:
+ case LDAP_FILTER_OR: {
+ for ( f = f->f_and; f; f = f->f_next ) {
+ int rc = datamorph_filter( op, ov, f );
+ if ( rc != LDAP_SUCCESS ) {
+ return rc;
+ }
+ }
+ } break;
+
+ case LDAP_FILTER_NOT:
+ return datamorph_filter( op, ov, f->f_not );
+
+ case LDAP_FILTER_EQUALITY:
+ case LDAP_FILTER_GE:
+ case LDAP_FILTER_LE: {
+ transformation_info *t, needle = { .attr = f->f_ava->aa_desc };
+
+ t = ldap_avl_find(
+ ov->transformations, &needle, transformation_info_cmp );
+ if ( t ) {
+ struct berval new_val;
+ int rc = transform_to_db_format_one(
+ op, t, &f->f_ava->aa_value, &new_val );
+ ch_free( f->f_ava->aa_value.bv_val );
+
+ if ( rc != LDAP_SUCCESS ) {
+ f->f_choice = SLAPD_FILTER_COMPUTED;
+ f->f_result = SLAPD_COMPARE_UNDEFINED;
+ } else {
+ f->f_ava->aa_value = new_val;
+ }
+ }
+ } break;
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+datamorph_op_add( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ datamorph_info *ov = on->on_bi.bi_private;
+ Entry *e = op->ora_e;
+ Attribute *a, *next;
+ AttributeDescription *stop = NULL;
+ int rc = LDAP_SUCCESS;
+
+ if ( !BER_BVISNULL( &e->e_nname ) && !BER_BVISEMPTY( &e->e_nname ) ) {
+ LDAPRDN rDN;
+ const char *p;
+ int i;
+
+ rc = ldap_bv2rdn_x( &e->e_nname, &rDN, (char **)&p, LDAP_DN_FORMAT_LDAP,
+ op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "datamorph_op_add: "
+ "can't parse rdn: dn=%s\n",
+ op->o_req_ndn.bv_val );
+ return SLAP_CB_CONTINUE;
+ }
+
+ for ( i = 0; rDN[i]; i++ ) {
+ transformation_info needle = {};
+
+ /* If we can't resolve the attribute, ignore it */
+ if ( slap_bv2ad( &rDN[i]->la_attr, &needle.attr, &p ) ) {
+ continue;
+ }
+
+ if ( ldap_avl_find( ov->transformations, &needle,
+ transformation_info_cmp ) ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ Debug( LDAP_DEBUG_TRACE, "datamorph_op_add: "
+ "attempted to add transformed attribute in RDN\n" );
+ break;
+ }
+ }
+
+ ldap_rdnfree_x( rDN, op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ send_ldap_error( op, rs, rc,
+ "datamorph: trying to add transformed attribute in RDN" );
+ return rc;
+ }
+ }
+
+ for ( a = e->e_attrs; a && a->a_desc != stop; a = next ) {
+ transformation_info *t, needle = { .attr = a->a_desc };
+ BerVarray new_vals;
+
+ next = a->a_next;
+
+ t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp );
+ if ( !t ) continue;
+
+ rc = transform_to_db_format(
+ op, t, a->a_vals, a->a_numvals, &new_vals );
+ if ( rc != LDAP_SUCCESS ) {
+ goto fail;
+ }
+
+ (void)attr_delete( &e->e_attrs, needle.attr );
+
+ rc = attr_merge( e, needle.attr, new_vals, NULL );
+ ber_bvarray_free( new_vals );
+ if ( rc != LDAP_SUCCESS ) {
+ goto fail;
+ }
+ if ( !stop ) {
+ stop = needle.attr;
+ }
+ }
+
+ return SLAP_CB_CONTINUE;
+
+fail:
+ send_ldap_error(
+ op, rs, rc, "datamorph: trying to add values outside definitions" );
+ return rc;
+}
+
+static int
+datamorph_op_compare( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ datamorph_info *ov = on->on_bi.bi_private;
+ transformation_info *t, needle = { .attr = op->orc_ava->aa_desc };
+ int rc = SLAP_CB_CONTINUE;
+
+ t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp );
+ if ( t ) {
+ struct berval new_val;
+
+ rc = transform_to_db_format_one(
+ op, t, &op->orc_ava->aa_value, &new_val );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_TRACE, "datamorph_op_compare: "
+ "transformation failed for '%s', rc=%d\n",
+ op->orc_ava->aa_value.bv_val, rc );
+ rs->sr_err = rc = LDAP_COMPARE_FALSE;
+ send_ldap_result( op, rs );
+ return rc;
+ }
+ ch_free( op->orc_ava->aa_value.bv_val );
+ op->orc_ava->aa_value = new_val;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+datamorph_op_mod( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ datamorph_info *ov = on->on_bi.bi_private;
+ Modifications *mod;
+ int rc = SLAP_CB_CONTINUE;
+
+ for ( mod = op->orm_modlist; mod; mod = mod->sml_next ) {
+ transformation_info *t, needle = { .attr = mod->sml_desc };
+ BerVarray new_vals = NULL;
+
+ if ( mod->sml_numvals == 0 ) continue; /* Nothing to transform */
+
+ t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp );
+ if ( !t ) continue;
+
+ assert( !mod->sml_nvalues );
+ rc = transform_to_db_format(
+ op, t, mod->sml_values, mod->sml_numvals, &new_vals );
+ if ( rc != LDAP_SUCCESS ) {
+ goto fail;
+ }
+ ber_bvarray_free( mod->sml_values );
+ mod->sml_values = new_vals;
+ }
+
+ return SLAP_CB_CONTINUE;
+
+fail:
+ Debug( LDAP_DEBUG_TRACE, "datamorph_op_mod: "
+ "dn=%s failed rc=%d\n",
+ op->o_req_ndn.bv_val, rc );
+ send_ldap_error( op, rs, rc,
+ "datamorph: trying to operate on values outside definitions" );
+ return rc;
+}
+
+static int
+datamorph_op_modrdn( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ datamorph_info *ov = on->on_bi.bi_private;
+ LDAPRDN rDN;
+ const char *p;
+ int i, rc;
+
+ rc = ldap_bv2rdn_x( &op->orr_nnewrdn, &rDN, (char **)&p,
+ LDAP_DN_FORMAT_LDAP, op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ Debug( LDAP_DEBUG_ANY, "datamorph_op_modrdn: "
+ "can't parse rdn for dn=%s\n",
+ op->o_req_ndn.bv_val );
+ return SLAP_CB_CONTINUE;
+ }
+
+ for ( i = 0; rDN[i]; i++ ) {
+ transformation_info needle = {};
+
+ /* If we can't resolve the attribute, ignore it */
+ if ( slap_bv2ad( &rDN[i]->la_attr, &needle.attr, &p ) ) {
+ continue;
+ }
+
+ if ( ldap_avl_find(
+ ov->transformations, &needle, transformation_info_cmp ) ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ Debug( LDAP_DEBUG_TRACE, "datamorph_op_modrdn: "
+ "attempted to add transformed values in RDN\n" );
+ break;
+ }
+ }
+
+ ldap_rdnfree_x( rDN, op->o_tmpmemctx );
+ if ( rc != LDAP_SUCCESS ) {
+ send_ldap_error( op, rs, rc,
+ "datamorph: trying to put transformed values in RDN" );
+ return rc;
+ }
+
+ return SLAP_CB_CONTINUE;
+}
+
+static int
+datamorph_response( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ datamorph_info *ov = on->on_bi.bi_private;
+ Entry *e = NULL, *e_orig = rs->sr_entry;
+ AttributeDescription *stop = NULL;
+ Attribute *a, *next = NULL;
+ int rc = SLAP_CB_CONTINUE;
+
+ if ( rs->sr_type != REP_SEARCH ) {
+ return rc;
+ }
+
+ for ( a = e_orig->e_attrs; a && a->a_desc != stop; a = next ) {
+ transformation_info *t, needle = { .attr = a->a_desc };
+ BerVarray new_vals;
+
+ next = a->a_next;
+
+ t = ldap_avl_find( ov->transformations, &needle, transformation_info_cmp );
+ if ( !t ) continue;
+
+ rc = transform_from_db_format(
+ op, t, a->a_vals, a->a_numvals, &new_vals );
+ if ( rc != LDAP_SUCCESS ) {
+ break;
+ }
+ if ( !e ) {
+ if ( rs->sr_flags & REP_ENTRY_MODIFIABLE ) {
+ e = e_orig;
+ } else {
+ e = entry_dup( e_orig );
+ }
+ }
+
+ (void)attr_delete( &e->e_attrs, needle.attr );
+
+ rc = attr_merge( e, needle.attr, new_vals, NULL );
+ ber_bvarray_free( new_vals );
+ if ( rc != LDAP_SUCCESS ) {
+ break;
+ }
+ if ( !stop ) {
+ stop = needle.attr;
+ }
+ }
+
+ if ( rc == LDAP_SUCCESS ) {
+ rc = SLAP_CB_CONTINUE;
+ if ( e && e != e_orig ) {
+ rs_replace_entry( op, rs, on, e );
+ rs->sr_flags &= ~REP_ENTRY_MASK;
+ rs->sr_flags |= REP_ENTRY_MODIFIABLE | REP_ENTRY_MUSTBEFREED;
+ }
+ } else if ( e && e != e_orig ) {
+ entry_free( e );
+ }
+
+ return rc;
+}
+
+static int
+datamorph_op_search( Operation *op, SlapReply *rs )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ datamorph_info *ov = on->on_bi.bi_private;
+ int rc = SLAP_CB_CONTINUE;
+
+ /*
+ * 1. check all requested attributes -> register callback if one matches
+ * 2. check filter: parse filter, traverse, for configured attributes:
+ * - presence -> do not touch
+ * - ava -> replace assertion value with db value if possible, assertion with undefined otherwise
+ * - inequality -> ???
+ * - anything else -> undefined
+ * - might just check for equality and leave the rest to syntax?
+ * 3. unparse filter
+ */
+ if ( datamorph_filter( op, ov, op->ors_filter ) ) {
+ send_ldap_error(
+ op, rs, LDAP_OTHER, "datamorph: failed to process filter" );
+ return LDAP_OTHER;
+ }
+
+ return rc;
+}
+
+static int
+datamorph_entry_release_rw( Operation *op, Entry *e, int rw )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ int rc = LDAP_SUCCESS;
+
+ if ( on->on_next ) {
+ rc = overlay_entry_release_ov( op, e, rw, on->on_next );
+ } else if ( on->on_info->oi_orig->bi_entry_release_rw ) {
+ /* FIXME: there should be a better way */
+ rc = on->on_info->oi_orig->bi_entry_release_rw( op, e, rw );
+ } else {
+ entry_free( e );
+ }
+
+ return rc;
+}
+
+static int
+datamorph_entry_get_rw(
+ Operation *op,
+ struct berval *ndn,
+ ObjectClass *oc,
+ AttributeDescription *at,
+ int rw,
+ Entry **ep )
+{
+ slap_overinst *on = (slap_overinst *)op->o_bd->bd_info;
+ datamorph_info *ov = on->on_bi.bi_private;
+ Entry *e_orig, *e = NULL;
+ int rc;
+
+ if ( on->on_next ) {
+ rc = overlay_entry_get_ov( op, ndn, oc, at, rw, ep, on->on_next );
+ } else {
+ /* FIXME: there should be a better way */
+ rc = on->on_info->oi_orig->bi_entry_get_rw( op, ndn, oc, at, rw, ep );
+ }
+ e_orig = *ep;
+
+ if ( rc == LDAP_SUCCESS && e_orig ) {
+ AttributeDescription *stop = NULL;
+ Attribute *a;
+
+ for ( a = e_orig->e_attrs; a; a = a->a_next ) {
+ transformation_info *t, needle = { .attr = a->a_desc };
+ BerVarray new_vals;
+
+ t = ldap_avl_find(
+ ov->transformations, &needle, transformation_info_cmp );
+ if ( !t ) continue;
+
+ rc = transform_from_db_format(
+ op, t, a->a_vals, a->a_numvals, &new_vals );
+ if ( rc != LDAP_SUCCESS ) {
+ goto fail;
+ }
+ if ( !e ) {
+ e = entry_dup( e_orig );
+ }
+
+ (void)attr_delete( &e->e_attrs, needle.attr );
+
+ rc = attr_merge( e, needle.attr, new_vals, NULL );
+ ber_bvarray_free( new_vals );
+ if ( rc != LDAP_SUCCESS ) {
+ goto fail;
+ }
+ if ( !stop ) {
+ stop = needle.attr;
+ }
+ }
+ }
+ if ( e ) {
+ datamorph_entry_release_rw( op, e_orig, rw );
+ *ep = e;
+ }
+
+ return rc;
+
+fail:
+ if ( e ) {
+ entry_free( e );
+ }
+ (void)datamorph_entry_release_rw( op, *ep, rw );
+ return rc;
+}
+
+/* Schema */
+
+static int
+datamorphBlobValidate( Syntax *syntax, struct berval *in )
+{
+ /* any value allowed */
+ return LDAP_SUCCESS;
+}
+
+int
+datamorphBinarySignedOrderingMatch( int *matchp,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *value,
+ void *assertedValue )
+{
+ struct berval *asserted = assertedValue;
+ ber_len_t v_len = value->bv_len;
+ ber_len_t av_len = asserted->bv_len;
+
+ /* Ordering:
+ * 1. Negative always before non-negative
+ * 2. Shorter before longer
+ * 3. Rest ordered by memory contents (they are big-endian numbers)
+ */
+ int match = ( *value->bv_val >= 0 ) - ( *asserted->bv_val >= 0 );
+
+ if ( match == 0 ) match = (int)v_len - (int)av_len;
+
+ if ( match == 0 ) match = memcmp( value->bv_val, asserted->bv_val, v_len );
+
+ /* If used in extensible match filter, match if value < asserted */
+ if ( flags & SLAP_MR_EXT ) match = ( match >= 0 );
+
+ *matchp = match;
+ return LDAP_SUCCESS;
+}
+
+/* Index generation function: Ordered index */
+int
+datamorphUnsignedIndexer( slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ BerVarray values,
+ BerVarray *keysp,
+ void *ctx )
+{
+ int i;
+ BerVarray keys;
+
+ for ( i = 0; values[i].bv_val != NULL; i++ ) {
+ /* just count them */
+ }
+
+ /* we should have at least one value at this point */
+ assert( i > 0 );
+
+ keys = slap_sl_malloc( sizeof(struct berval) * ( i + 1 ), ctx );
+
+ for ( i = 0; values[i].bv_val != NULL; i++ ) {
+ ber_dupbv_x( &keys[i], &values[i], ctx );
+ }
+
+ BER_BVZERO( &keys[i] );
+
+ *keysp = keys;
+
+ return LDAP_SUCCESS;
+}
+
+/* Index generation function: Ordered index */
+int
+datamorphUnsignedFilter(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ void *assertedValue,
+ BerVarray *keysp,
+ void *ctx )
+{
+ BerVarray keys;
+ BerValue *value = assertedValue;
+
+ keys = slap_sl_malloc( sizeof(struct berval) * 2, ctx );
+ ber_dupbv_x( &keys[0], value, ctx );
+
+ BER_BVZERO( &keys[1] );
+
+ *keysp = keys;
+
+ return LDAP_SUCCESS;
+}
+
+/* Index generation function: Ordered index */
+int
+datamorphSignedIndexer(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ BerVarray values,
+ BerVarray *keysp,
+ void *ctx )
+{
+ int i;
+ BerVarray keys;
+
+ for ( i = 0; values[i].bv_val != NULL; i++ ) {
+ /* just count them */
+ }
+
+ /* we should have at least one value at this point */
+ assert( i > 0 );
+
+ keys = slap_sl_malloc( sizeof(struct berval) * ( i + 1 ), ctx );
+
+ for ( i = 0; values[i].bv_val != NULL; i++ ) {
+ keys[i].bv_len = values[i].bv_len + 1;
+ keys[i].bv_val = slap_sl_malloc( keys[i].bv_len, ctx );
+
+ /* if positive (highest bit is not set), note that in the first byte */
+ *keys[i].bv_val = ~( *values[i].bv_val & 0x80 );
+ AC_MEMCPY( keys[i].bv_val + 1, values[i].bv_val, values[i].bv_len );
+ }
+
+ BER_BVZERO( &keys[i] );
+
+ *keysp = keys;
+
+ return LDAP_SUCCESS;
+}
+
+/* Index generation function: Ordered index */
+int
+datamorphSignedFilter(
+ slap_mask_t use,
+ slap_mask_t flags,
+ Syntax *syntax,
+ MatchingRule *mr,
+ struct berval *prefix,
+ void *assertedValue,
+ BerVarray *keysp,
+ void *ctx )
+{
+ BerVarray keys;
+ BerValue *value = assertedValue;
+
+ keys = slap_sl_malloc( sizeof(struct berval) * 2, ctx );
+
+ keys[0].bv_len = value->bv_len + 1;
+ keys[0].bv_val = slap_sl_malloc( keys[0].bv_len, ctx );
+
+ /* if positive (highest bit is not set), note that in the first byte */
+ *keys[0].bv_val = ~( *value->bv_val & 0x80 );
+ AC_MEMCPY( keys[0].bv_val + 1, value->bv_val, value->bv_len );
+
+ BER_BVZERO( &keys[1] );
+
+ *keysp = keys;
+
+ return LDAP_SUCCESS;
+}
+
+#define DATAMORPH_ARC "1.3.6.1.4.1.4203.666.11.12"
+
+#define DATAMORPH_SYNTAXES DATAMORPH_ARC ".1"
+#define DATAMORPH_SYNTAX_BASE DATAMORPH_SYNTAXES ".1"
+#define DATAMORPH_SYNTAX_ENUM DATAMORPH_SYNTAXES ".2"
+#define DATAMORPH_SYNTAX_INT DATAMORPH_SYNTAXES ".3"
+#define DATAMORPH_SYNTAX_SIGNED_INT DATAMORPH_SYNTAXES ".4"
+
+#define DATAMORPH_MATCHES DATAMORPH_ARC ".2"
+#define DATAMORPH_MATCH_EQUALITY DATAMORPH_MATCHES ".1"
+#define DATAMORPH_MATCH_SIGNED_EQUALITY DATAMORPH_MATCHES ".2"
+#define DATAMORPH_MATCH_ORDERING DATAMORPH_MATCHES ".3"
+#define DATAMORPH_MATCH_SIGNED_ORDERING DATAMORPH_MATCHES ".4"
+
+static char *datamorph_sups[] = {
+ DATAMORPH_SYNTAX_BASE,
+ NULL
+};
+
+static char *datamorphSyntaxes[] = {
+ DATAMORPH_SYNTAX_SIGNED_INT,
+ DATAMORPH_SYNTAX_ENUM,
+ DATAMORPH_SYNTAX_INT,
+
+ NULL
+};
+
+static slap_syntax_defs_rec datamorph_syntax_defs[] = {
+ { "( " DATAMORPH_SYNTAX_BASE " DESC 'Fixed size value' )",
+ 0, NULL, NULL, NULL
+ },
+ { "( " DATAMORPH_SYNTAX_ENUM " DESC 'Enumerated value' )",
+ 0, datamorph_sups, datamorphBlobValidate, NULL
+ },
+ { "( " DATAMORPH_SYNTAX_INT " DESC 'Fixed-size integer' )",
+ 0, datamorph_sups, datamorphBlobValidate, NULL
+ },
+ { "( " DATAMORPH_SYNTAX_SIGNED_INT " DESC 'Fixed-size signed integer' )",
+ 0, datamorph_sups, datamorphBlobValidate, NULL
+ },
+
+ { NULL, 0, NULL, NULL, NULL }
+};
+
+static Syntax *datamorph_base_syntax;
+
+static slap_mrule_defs_rec datamorph_mrule_defs[] = {
+ { "( " DATAMORPH_MATCH_EQUALITY
+ " NAME 'fixedSizeIntegerMatch'"
+ " SYNTAX " DATAMORPH_SYNTAX_INT " )",
+ SLAP_MR_EQUALITY|SLAP_MR_EXT|SLAP_MR_ORDERED_INDEX,
+ datamorphSyntaxes + 1,
+ NULL, NULL, octetStringOrderingMatch,
+ datamorphUnsignedIndexer, datamorphUnsignedFilter,
+ NULL
+ },
+
+ { "( " DATAMORPH_MATCH_SIGNED_EQUALITY
+ " NAME 'fixedSizeSignedIntegerMatch'"
+ " SYNTAX " DATAMORPH_SYNTAX_SIGNED_INT " )",
+ SLAP_MR_EQUALITY|SLAP_MR_EXT|SLAP_MR_ORDERED_INDEX,
+ NULL,
+ NULL, NULL, datamorphBinarySignedOrderingMatch,
+ datamorphSignedIndexer, datamorphSignedFilter,
+ NULL
+ },
+
+ { "( " DATAMORPH_MATCH_ORDERING
+ " NAME 'fixedSizeIntegerOrderingMatch'"
+ " SYNTAX " DATAMORPH_SYNTAX_INT " )",
+ SLAP_MR_ORDERING|SLAP_MR_EXT|SLAP_MR_ORDERED_INDEX,
+ datamorphSyntaxes + 1,
+ NULL, NULL, octetStringOrderingMatch,
+ datamorphUnsignedIndexer, datamorphUnsignedFilter,
+ "octetStringMatch" },
+
+ { "( " DATAMORPH_MATCH_SIGNED_ORDERING
+ " NAME 'fixedSizeSignedIntegerOrderingMatch'"
+ " SYNTAX " DATAMORPH_SYNTAX_SIGNED_INT " )",
+ SLAP_MR_ORDERING|SLAP_MR_EXT|SLAP_MR_ORDERED_INDEX,
+ NULL,
+ NULL, NULL, datamorphBinarySignedOrderingMatch,
+ datamorphSignedIndexer, datamorphSignedFilter,
+ "octetStringMatch" },
+
+ { NULL, SLAP_MR_NONE, NULL, NULL, NULL, NULL, NULL, NULL, NULL }
+};
+
+/* Configuration */
+
+static ConfigLDAPadd datamorph_ldadd_enum;
+static ConfigLDAPadd datamorph_ldadd_interval;
+static ConfigLDAPadd datamorph_ldadd_mapping;
+
+static ConfigDriver datamorph_set_attribute;
+static ConfigDriver datamorph_set_size;
+static ConfigDriver datamorph_set_signed;
+static ConfigDriver datamorph_set_bounds;
+static ConfigDriver datamorph_set_index;
+static ConfigDriver datamorph_set_value;
+static ConfigDriver datamorph_add_mapping;
+static ConfigDriver datamorph_add_transformation;
+
+static ConfigCfAdd datamorph_cfadd;
+
+enum {
+ DATAMORPH_INT_SIZE = 1,
+ DATAMORPH_INT_SIGNED,
+ DATAMORPH_INT_LOWER,
+ DATAMORPH_INT_UPPER,
+
+ DATAMORPH_INT_LAST,
+};
+
+static ConfigTable datamorph_cfg[] = {
+ { "datamorph_attribute", "attr", 2, 2, 0,
+ ARG_STRING|ARG_QUOTE|ARG_MAGIC,
+ datamorph_set_attribute,
+ "( OLcfgCtAt:7.1 NAME 'olcDatamorphAttribute' "
+ "DESC 'Attribute to transform' "
+ "EQUALITY caseIgnoreMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "datamorph_size", "<1|2|4|8>", 2, 2, 0,
+ ARG_INT|ARG_MAGIC|DATAMORPH_INT_SIZE,
+ datamorph_set_size,
+ "( OLcfgCtAt:7.2 NAME 'olcDatamorphIntegerBytes' "
+ "DESC 'Integer size in bytes' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "datamorph_signed", "TRUE|FALSE", 2, 2, 0,
+ ARG_ON_OFF|ARG_MAGIC|DATAMORPH_INT_SIGNED,
+ datamorph_set_signed,
+ "( OLcfgCtAt:7.3 NAME 'olcDatamorphIntegerSigned' "
+ "DESC 'Whether integers maintain sign' "
+ "EQUALITY booleanMatch "
+ "SYNTAX OMsBoolean "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "datamorph_lower_bound", "int", 2, 2, 0,
+ ARG_BERVAL|ARG_MAGIC|DATAMORPH_INT_LOWER,
+ datamorph_set_bounds,
+ "( OLcfgCtAt:7.4 NAME 'olcDatamorphIntegerLowerBound' "
+ "DESC 'Lowest valid value for the attribute' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "datamorph_upper_bound", "int", 2, 2, 0,
+ ARG_BERVAL|ARG_MAGIC|DATAMORPH_INT_UPPER,
+ datamorph_set_bounds,
+ "( OLcfgCtAt:7.5 NAME 'olcDatamorphIntegerUpperBound' "
+ "DESC 'Highest valid value for the attribute' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+
+ /* These have no equivalent in slapd.conf */
+ { "", NULL, 2, 2, 0,
+ ARG_INT|ARG_MAGIC,
+ datamorph_set_index,
+ "( OLcfgCtAt:7.6 NAME 'olcDatamorphIndex' "
+ "DESC 'Internal DB value' "
+ "EQUALITY integerMatch "
+ "SYNTAX OMsInteger "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+ { "", NULL, 2, 2, 0,
+ ARG_BERVAL|ARG_QUOTE|ARG_MAGIC,
+ datamorph_set_value,
+ "( OLcfgCtAt:7.7 NAME 'olcDatamorphValue' "
+ "DESC 'Wire value' "
+ "EQUALITY caseExactMatch "
+ "SYNTAX OMsDirectoryString "
+ "SINGLE-VALUE )",
+ NULL, NULL
+ },
+
+ /* slapd.conf alternative for the two above */
+ { "datamorph_value", "int> <name", 3, 3, 0,
+ ARG_QUOTE|ARG_MAGIC,
+ datamorph_add_mapping,
+ NULL, NULL, NULL
+ },
+
+ /* slapd.conf alternative for objectclasses below */
+ { "datamorph", "enum|int> <attr", 3, 3, 0,
+ ARG_QUOTE|ARG_MAGIC,
+ datamorph_add_transformation,
+ NULL, NULL, NULL
+ },
+
+ { NULL, NULL, 0, 0, 0, ARG_IGNORED }
+};
+
+static ConfigOCs datamorph_ocs[] = {
+ { "( OLcfgCtOc:7.1 "
+ "NAME 'olcDatamorphConfig' "
+ "DESC 'Datamorph overlay configuration' "
+ "SUP olcOverlayConfig )",
+ Cft_Overlay, datamorph_cfg, NULL, datamorph_cfadd },
+ { "( OLcfgCtOc:7.2 "
+ "NAME 'olcTransformation' "
+ "DESC 'Transformation configuration' "
+ "MUST ( olcDatamorphAttribute ) "
+ "SUP top "
+ "ABSTRACT )",
+ Cft_Misc, datamorph_cfg, NULL },
+ { "( OLcfgCtOc:7.3 "
+ "NAME 'olcDatamorphEnum' "
+ "DESC 'Configuration for an enumerated attribute' "
+ "SUP olcTransformation "
+ "STRUCTURAL )",
+ Cft_Misc, datamorph_cfg, datamorph_ldadd_enum },
+ { "( OLcfgCtOc:7.4 "
+ "NAME 'olcDatamorphInteger' "
+ "DESC 'Configuration for a compact integer attribute' "
+ "MUST ( olcDatamorphIntegerBytes ) "
+ "MAY ( olcDatamorphIntegerLowerBound $ "
+ "olcDatamorphIntegerUpperBound $ "
+ "olcDatamorphIntegerSigned "
+ ") "
+ "SUP olcTransformation "
+ "STRUCTURAL )",
+ Cft_Misc, datamorph_cfg, datamorph_ldadd_interval },
+ { "( OLcfgCtOc:7.5 "
+ "NAME 'olcDatamorphEnumValue' "
+ "DESC 'Configuration for an enumerated attribute' "
+ "MUST ( olcDatamorphIndex $ "
+ "olcDatamorphValue "
+ ") "
+ "STRUCTURAL )",
+ Cft_Misc, datamorph_cfg, datamorph_ldadd_mapping },
+
+ { NULL, 0, NULL }
+};
+
+static void
+datamorph_mapping_free( void *arg )
+{
+ datamorph_enum_mapping *mapping = arg;
+
+ ch_free( mapping->wire_value.bv_val );
+ ch_free( mapping );
+}
+
+static void
+datamorph_info_free( void *arg )
+{
+ transformation_info *info = arg;
+
+ if ( info->type == DATAMORPH_ENUM ) {
+ ldap_avl_free( info->ti_enum.to_db, datamorph_mapping_free );
+ }
+ ch_free( info );
+}
+
+static int
+datamorph_set_attribute( ConfigArgs *ca )
+{
+ transformation_info needle = {}, *info = ca->ca_private;
+ slap_overinst *on = (slap_overinst *)ca->bi;
+ datamorph_info *ov = on->on_bi.bi_private;
+ char *s = ca->value_string;
+ const char *text;
+ int rc = LDAP_SUCCESS;
+
+ if ( ca->op == SLAP_CONFIG_EMIT ) {
+ ca->value_string = info->attr->ad_cname.bv_val;
+ return LDAP_SUCCESS;
+ } else if ( ca->op == LDAP_MOD_DELETE ) {
+ info = ldap_avl_delete( &ov->transformations, info,
+ transformation_info_cmp );
+ assert( info );
+
+ info->attr = NULL;
+ return LDAP_SUCCESS;
+ }
+
+ if ( *s == '{' ) {
+ s = strchr( s, '}' );
+ if ( !s ) {
+ rc = LDAP_UNDEFINED_TYPE;
+ goto done;
+ }
+ s += 1;
+ }
+
+ rc = slap_str2ad( s, &info->attr, &text );
+ ch_free( ca->value_string );
+ if ( rc ) {
+ goto done;
+ }
+
+ /* The type has to be set appropriately */
+ if ( !info->attr->ad_type->sat_syntax->ssyn_sups ||
+ info->attr->ad_type->sat_syntax->ssyn_sups[0] !=
+ datamorph_base_syntax ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "improper syntax for attribute %s",
+ info->attr->ad_cname.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+ needle.attr = info->attr;
+ if ( ldap_avl_find( ov->transformations, &needle, transformation_info_cmp ) ) {
+ rc = LDAP_CONSTRAINT_VIOLATION;
+ goto done;
+ }
+
+done:
+ if ( rc ) {
+ ca->reply.err = rc;
+ }
+ return rc;
+}
+
+static int
+datamorph_set_size( ConfigArgs *ca )
+{
+ transformation_info *info = ca->ca_private;
+
+ if ( !info ) {
+ slap_overinst *on = (slap_overinst *)ca->bi;
+ datamorph_info *ov = on->on_bi.bi_private;
+ info = ov->wip_transformation;
+ assert( ca->op == SLAP_CONFIG_ADD );
+ }
+
+ if ( ca->op == SLAP_CONFIG_EMIT ) {
+ ca->value_int = info->ti_int.size;
+ return LDAP_SUCCESS;
+ } else if ( ca->op == LDAP_MOD_DELETE ) {
+ info->ti_int.size = 0;
+ return LDAP_SUCCESS;
+ }
+
+ if ( ca->value_int != 1 &&
+ ca->value_int != 2 &&
+ ca->value_int != 4 &&
+ ca->value_int != 8 ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg), "invalid size %d",
+ ca->value_int );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ ca->reply.err = LDAP_CONSTRAINT_VIOLATION;
+ return ca->reply.err;
+ }
+ info->ti_int.size = ca->value_int;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+datamorph_set_signed( ConfigArgs *ca )
+{
+ transformation_info *info = ca->ca_private;
+
+ if ( !info ) {
+ slap_overinst *on = (slap_overinst *)ca->bi;
+ datamorph_info *ov = on->on_bi.bi_private;
+ info = ov->wip_transformation;
+ assert( ca->op == SLAP_CONFIG_ADD );
+ }
+
+ if ( ca->op == SLAP_CONFIG_EMIT ) {
+ ca->value_int = info->ti_int.flags & DATAMORPH_FLAG_SIGNED;
+ return LDAP_SUCCESS;
+ } else if ( ca->op == LDAP_MOD_DELETE ) {
+ info->ti_int.flags &= ~DATAMORPH_FLAG_SIGNED;
+ return LDAP_SUCCESS;
+ }
+
+ info->ti_int.flags &= ~DATAMORPH_FLAG_SIGNED;
+ if ( ca->value_int ) {
+ info->ti_int.flags |= DATAMORPH_FLAG_SIGNED;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+datamorph_set_bounds( ConfigArgs *ca )
+{
+ transformation_info *info = ca->ca_private;
+ datamorph_interval_bound *bound;
+ uint64_t unsigned_bound;
+ int64_t signed_bound;
+ char *ptr = ca->value_bv.bv_val + ca->value_bv.bv_len;
+ int flag;
+
+ if ( !info ) {
+ slap_overinst *on = (slap_overinst *)ca->bi;
+ datamorph_info *ov = on->on_bi.bi_private;
+ info = ov->wip_transformation;
+ assert( ca->op == SLAP_CONFIG_ADD );
+ }
+
+ switch ( ca->type ) {
+ case DATAMORPH_INT_LOWER:
+ bound = &info->ti_int.lower;
+ flag = DATAMORPH_FLAG_LOWER;
+ break;
+ case DATAMORPH_INT_UPPER:
+ bound = &info->ti_int.upper;
+ flag = DATAMORPH_FLAG_UPPER;
+ break;
+ default:
+ assert(0);
+ }
+
+ if ( ca->op == SLAP_CONFIG_EMIT ) {
+ char buf[24];
+ struct berval bv = { .bv_val = buf };
+
+ if ( !(info->ti_int.flags & flag) ) {
+ /* Bound not set, do not emit */
+ return LDAP_SUCCESS;
+ }
+ if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ bv.bv_len = sprintf( buf, "%" PRId64, bound->i );
+ } else {
+ bv.bv_len = sprintf( buf, "%" PRIu64, bound->u );
+ }
+ ber_dupbv_x( &ca->value_bv, &bv, ca->ca_op->o_tmpmemctx );
+
+ return LDAP_SUCCESS;
+ } else if ( ca->op == LDAP_MOD_DELETE ) {
+ info->ti_int.flags &= ~flag;
+ if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ bound->i = (flag == DATAMORPH_FLAG_LOWER) ? INT64_MIN : INT64_MAX;
+ } else {
+ bound->u = (flag == DATAMORPH_FLAG_LOWER) ? 0 : UINT64_MAX;
+ }
+ return LDAP_SUCCESS;
+ }
+
+ /* FIXME: if attributes in the Add operation come in the wrong order
+ * (signed=true after the bound definition), we can't check the interval
+ * sanity. */
+ /*
+ if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ signed_bound = strtoll( ca->value_bv.bv_val, &ptr, 10 );
+ } else {
+ unsigned_bound = strtoull( ca->value_bv.bv_val, &ptr, 10 );
+ }
+ */
+ /* Also, no idea what happens in the case of big-endian, hopefully,
+ * it behaves the same */
+ unsigned_bound = strtoull( ca->value_bv.bv_val, &ptr, 10 );
+ signed_bound = (int64_t)unsigned_bound;
+
+ if ( *ca->value_bv.bv_val == '\0' || *ptr != '\0' ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "failed to parse '%s' as integer",
+ ca->value_bv.bv_val );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ ca->reply.err = LDAP_CONSTRAINT_VIOLATION;
+ return ca->reply.err;
+ }
+ ch_free( ca->value_bv.bv_val );
+
+ info->ti_int.flags |= flag;
+ switch ( info->ti_int.size ) {
+ case 1:
+ if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ /* See FIXME above
+ if ( signed_bound < INT8_MIN || signed_bound > INT8_MAX ) {
+ goto fail;
+ }
+ */
+ } else {
+ /* See FIXME above
+ if ( unsigned_bound > UINT8_MAX ) {
+ goto fail;
+ }
+ */
+ }
+ break;
+ case 2:
+ if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ /* See FIXME above
+ if ( signed_bound < INT16_MIN || signed_bound > INT16_MAX ) {
+ goto fail;
+ }
+ */
+ } else {
+ /* See FIXME above
+ if ( unsigned_bound > UINT16_MAX ) {
+ goto fail;
+ }
+ */
+ }
+ break;
+ case 4:
+ if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ /* See FIXME above
+ if ( signed_bound < INT32_MIN || signed_bound > INT32_MAX ) {
+ goto fail;
+ }
+ */
+ } else {
+ /* See FIXME above
+ if ( unsigned_bound > UINT32_MAX ) {
+ goto fail;
+ }
+ */
+ }
+ break;
+ case 8:
+ break;
+ default:
+ /* Should only happen in these two cases:
+ * 1. datamorph_size not yet encountered for this one (when
+ * processing slapd.conf)
+ * 2. When someone runs a fun modification on the config entry
+ * messing with more attributes at once
+ *
+ * The error message is expected to be helpful only for the former,
+ * so use the slapd.conf name.
+ */
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "datamorph_size has to be set first!" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ ca->reply.err = LDAP_CONSTRAINT_VIOLATION;
+ return ca->reply.err;
+ }
+ if ( info->ti_int.flags & DATAMORPH_FLAG_SIGNED ) {
+ bound->i = signed_bound;
+ } else {
+ bound->u = unsigned_bound;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+datamorph_set_value( ConfigArgs *ca )
+{
+ datamorph_enum_mapping *mapping = ca->ca_private;
+ char *s = ca->value_bv.bv_val;
+
+ if ( ca->op == SLAP_CONFIG_EMIT ) {
+ /* We generate the value as part of the RDN, don't add anything */
+ return LDAP_SUCCESS;
+ } else if ( ca->op == LDAP_MOD_DELETE ) {
+ ch_free( mapping->wire_value.bv_val );
+ BER_BVZERO( &mapping->wire_value );
+ /* TODO: remove from info->ti_enum.to_db? */
+ return LDAP_SUCCESS;
+ }
+
+ /* As long as this attribute can be in the RDN,
+ * we have to expect the '{n}' prefix */
+ if ( *s == '{' ) {
+ ber_len_t len;
+ s = memchr( s, '}', ca->value_bv.bv_len );
+ if ( !s ) {
+ ca->reply.err = LDAP_UNDEFINED_TYPE;
+ return ca->reply.err;
+ }
+ s += 1;
+
+ len = ca->value_bv.bv_len - ( s - ca->value_bv.bv_val );
+ ber_str2bv( s, len, 1, &mapping->wire_value );
+ ch_free( ca->value_bv.bv_val );
+ } else {
+ mapping->wire_value = ca->value_bv;
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+datamorph_set_index( ConfigArgs *ca )
+{
+ datamorph_enum_mapping *mapping = ca->ca_private;
+ struct berval *from_db = mapping->transformation->ti_enum.from_db;
+
+ if ( ca->op == SLAP_CONFIG_EMIT ) {
+ ca->value_int = mapping->db_value;
+ return LDAP_SUCCESS;
+ } else if ( ca->op == LDAP_MOD_DELETE ) {
+ BER_BVZERO( &from_db[mapping->db_value] );
+ return LDAP_SUCCESS;
+ }
+
+ if ( ca->value_int < 0 || ca->value_int >= 256 ) {
+ ca->reply.err = LDAP_CONSTRAINT_VIOLATION;
+ return ca->reply.err;
+ } else if ( !BER_BVISNULL( &from_db[ca->value_int] ) ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg), "duplicate index %d",
+ ca->value_int );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ ca->reply.err = LDAP_CONSTRAINT_VIOLATION;
+ return ca->reply.err;
+ }
+ mapping->db_value = ca->value_int;
+ from_db[ca->value_int] = mapping->wire_value;
+
+ return LDAP_SUCCESS;
+}
+
+/* Called when processing slapd.conf only,
+ * cn=config uses the objectclass to decide which type we're dealing with.
+ */
+static int
+datamorph_add_transformation( ConfigArgs *ca )
+{
+ slap_overinst *on = (slap_overinst *)ca->bi;
+ datamorph_info *ov = on->on_bi.bi_private;
+ transformation_info *info;
+
+ if ( ov->wip_transformation ) {
+ /* We checked everything as were processing the lines */
+ int rc = ldap_avl_insert( &ov->transformations, ov->wip_transformation,
+ transformation_info_cmp, ldap_avl_dup_error );
+ assert( rc == LDAP_SUCCESS );
+ }
+
+ info = ch_calloc( 1, sizeof(transformation_info) );
+ ov->wip_transformation = ca->ca_private = info;
+
+ if ( !strcasecmp( ca->argv[1], "enum" ) ) {
+ info->type = DATAMORPH_ENUM;
+ } else if ( !strcasecmp( ca->argv[1], "int" ) ) {
+ info->type = DATAMORPH_INT;
+ } else {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg),
+ "unknown transformation type '%s'", ca->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ ca->reply.err = LDAP_CONSTRAINT_VIOLATION;
+ return ca->reply.err;
+ }
+
+ ca->value_string = strdup( ca->argv[2] );
+
+ return datamorph_set_attribute( ca );
+}
+
+static int
+datamorph_add_mapping( ConfigArgs *ca )
+{
+ slap_overinst *on = (slap_overinst *)ca->bi;
+ datamorph_info *ov = on->on_bi.bi_private;
+ transformation_info *info = ov->wip_transformation;
+ datamorph_enum_mapping *mapping;
+ int rc = LDAP_CONSTRAINT_VIOLATION;
+
+ if ( !info ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg), "no attribute configured" );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ goto done;
+ }
+
+ mapping = ch_calloc( 1, sizeof(datamorph_enum_mapping) );
+ mapping->transformation = info;
+ ca->ca_private = mapping;
+
+ ber_str2bv( ca->argv[2], 0, 1, &ca->value_bv );
+ rc = datamorph_set_value( ca );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+ rc = lutil_atoix( &ca->value_int, ca->argv[1], 0 );
+ if ( rc != LDAP_SUCCESS ) {
+ snprintf( ca->cr_msg, sizeof(ca->cr_msg), "invalid integer %s",
+ ca->argv[1] );
+ Debug( LDAP_DEBUG_ANY, "%s: %s\n", ca->log, ca->cr_msg );
+ goto done;
+ }
+
+ rc = datamorph_set_index( ca );
+ if ( rc != LDAP_SUCCESS ) {
+ goto done;
+ }
+
+done:
+ if ( rc == LDAP_SUCCESS ) {
+ rc = ldap_avl_insert( &info->ti_enum.to_db, mapping,
+ transformation_mapping_cmp, ldap_avl_dup_error );
+ }
+ if ( rc ) {
+ ca->reply.err = rc;
+ }
+
+ return rc;
+}
+
+static int
+datamorph_ldadd_info_cleanup( ConfigArgs *ca )
+{
+ slap_overinst *on = (slap_overinst *)ca->bi;
+ datamorph_info *ov = on->on_bi.bi_private;
+ transformation_info *info = ca->ca_private;
+
+ if ( ca->reply.err != LDAP_SUCCESS ) {
+ /* Not reached since cleanup is only called on success */
+fail:
+ ch_free( info );
+ return LDAP_SUCCESS;
+ }
+
+ if ( ldap_avl_insert( &ov->transformations, info, transformation_info_cmp,
+ ldap_avl_dup_error ) ) {
+ goto fail;
+ }
+ return LDAP_SUCCESS;
+}
+
+static int
+datamorph_ldadd_transformation(
+ CfEntryInfo *cei,
+ Entry *e,
+ ConfigArgs *ca,
+ datamorph_type type )
+{
+ transformation_info *info;
+
+ if ( cei->ce_type != Cft_Overlay || !cei->ce_bi ||
+ cei->ce_bi->bi_cf_ocs != datamorph_ocs )
+ return LDAP_CONSTRAINT_VIOLATION;
+
+ info = ch_calloc( 1, sizeof(transformation_info) );
+ info->type = type;
+
+ ca->bi = cei->ce_bi;
+ ca->ca_private = info;
+ config_push_cleanup( ca, datamorph_ldadd_info_cleanup );
+ /* config_push_cleanup is only run in the case of online config but we use it to
+ * enable the new config when done with the entry */
+ ca->lineno = 0;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+datamorph_ldadd_enum( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
+{
+ return datamorph_ldadd_transformation( cei, e, ca, DATAMORPH_ENUM );
+}
+
+static int
+datamorph_ldadd_interval( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
+{
+ return datamorph_ldadd_transformation( cei, e, ca, DATAMORPH_INT );
+}
+
+static int
+datamorph_ldadd_mapping_cleanup( ConfigArgs *ca )
+{
+ datamorph_enum_mapping *mapping = ca->ca_private;
+ transformation_info *info = mapping->transformation;
+
+ if ( ca->reply.err != LDAP_SUCCESS ) {
+ /* Not reached since cleanup is only called on success */
+fail:
+ datamorph_mapping_free( mapping );
+ return LDAP_SUCCESS;
+ }
+
+ if ( ldap_avl_insert( &info->ti_enum.to_db, mapping, transformation_mapping_cmp,
+ ldap_avl_dup_error ) ) {
+ goto fail;
+ }
+ info->ti_enum.from_db[mapping->db_value] = mapping->wire_value;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+datamorph_ldadd_mapping( CfEntryInfo *cei, Entry *e, ConfigArgs *ca )
+{
+ transformation_info *info;
+ datamorph_enum_mapping *mapping;
+ CfEntryInfo *parent = cei->ce_parent;
+
+ if ( cei->ce_type != Cft_Misc || !parent || !parent->ce_bi ||
+ parent->ce_bi->bi_cf_ocs != datamorph_ocs )
+ return LDAP_CONSTRAINT_VIOLATION;
+
+ info = cei->ce_private;
+
+ mapping = ch_calloc( 1, sizeof(datamorph_enum_mapping) );
+ mapping->transformation = info;
+
+ ca->ca_private = mapping;
+ config_push_cleanup( ca, datamorph_ldadd_mapping_cleanup );
+ /* config_push_cleanup is only run in the case of online config but we use it to
+ * enable the new config when done with the entry */
+ ca->lineno = 0;
+
+ return LDAP_SUCCESS;
+}
+
+struct datamorph_cfadd_args {
+ Operation *op;
+ SlapReply *rs;
+ Entry *p;
+ ConfigArgs *ca;
+ int index;
+};
+
+static int
+datamorph_config_build_enum( void *item, void *arg )
+{
+ datamorph_enum_mapping *mapping = item;
+ struct datamorph_cfadd_args *args = arg;
+ struct berval rdn;
+ Entry *e;
+ char *p;
+ ber_len_t index;
+
+ rdn.bv_len = snprintf( args->ca->cr_msg, sizeof(args->ca->cr_msg),
+ "olcDatamorphValue={%d}", args->index++ );
+ rdn.bv_val = args->ca->cr_msg;
+ p = rdn.bv_val + rdn.bv_len;
+
+ rdn.bv_len += mapping->wire_value.bv_len;
+ for ( index = 0; index < mapping->wire_value.bv_len; index++ ) {
+ if ( RDN_NEEDSESCAPE(mapping->wire_value.bv_val[index]) ) {
+ rdn.bv_len++;
+ *p++ = '\\';
+ }
+ *p++ = mapping->wire_value.bv_val[index];
+ }
+ *p = '\0';
+
+ args->ca->ca_private = mapping;
+ args->ca->ca_op = args->op;
+ e = config_build_entry( args->op, args->rs, args->p->e_private, args->ca,
+ &rdn, &datamorph_ocs[4], NULL );
+ assert( e );
+
+ return LDAP_SUCCESS;
+}
+
+static int
+datamorph_config_build_attr( void *item, void *arg )
+{
+ transformation_info *info = item;
+ struct datamorph_cfadd_args *args = arg;
+ struct berval rdn;
+ ConfigOCs *oc;
+ Entry *e;
+
+ rdn.bv_len = snprintf( args->ca->cr_msg, sizeof(args->ca->cr_msg),
+ "olcDatamorphAttribute={%d}%s", args->index++,
+ info->attr->ad_cname.bv_val );
+ rdn.bv_val = args->ca->cr_msg;
+
+ switch ( info->type ) {
+ case DATAMORPH_ENUM:
+ oc = &datamorph_ocs[2];
+ break;
+ case DATAMORPH_INT:
+ oc = &datamorph_ocs[3];
+ break;
+ default:
+ assert(0);
+ break;
+ }
+
+ args->ca->ca_private = info;
+ args->ca->ca_op = args->op;
+ e = config_build_entry(
+ args->op, args->rs, args->p->e_private, args->ca, &rdn, oc, NULL );
+ assert( e );
+
+ if ( info->type == DATAMORPH_ENUM ) {
+ struct datamorph_cfadd_args new_args = *args;
+ new_args.p = e;
+ new_args.index = 0;
+
+ return ldap_avl_apply( info->ti_enum.to_db, datamorph_config_build_enum,
+ &new_args, 1, AVL_PREORDER );
+ }
+
+ return LDAP_SUCCESS;
+}
+
+static int
+datamorph_cfadd( Operation *op, SlapReply *rs, Entry *p, ConfigArgs *ca )
+{
+ slap_overinst *on = (slap_overinst *)ca->bi;
+ datamorph_info *ov = on->on_bi.bi_private;
+ struct datamorph_cfadd_args args = {
+ .op = op,
+ .rs = rs,
+ .p = p,
+ .ca = ca,
+ .index = 0,
+ };
+
+ if ( ov->wip_transformation ) {
+ /* There is one last item that is unfinished */
+ int rc = ldap_avl_insert( &ov->transformations, ov->wip_transformation,
+ transformation_info_cmp, ldap_avl_dup_error );
+ assert( rc == LDAP_SUCCESS );
+ }
+
+ return ldap_avl_apply( ov->transformations, &datamorph_config_build_attr, &args,
+ 1, AVL_PREORDER );
+}
+
+static slap_overinst datamorph;
+
+static int
+datamorph_db_init( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ datamorph_info *ov;
+
+ /* TODO: can this be global? */
+ if ( SLAP_ISGLOBALOVERLAY(be) ) {
+ Debug( LDAP_DEBUG_ANY, "datamorph overlay must be instantiated "
+ "within a database.\n" );
+ return 1;
+ }
+
+ ov = ch_calloc( 1, sizeof(datamorph_info) );
+ on->on_bi.bi_private = ov;
+
+ return LDAP_SUCCESS;
+}
+
+static int
+datamorph_db_destroy( BackendDB *be, ConfigReply *cr )
+{
+ slap_overinst *on = (slap_overinst *)be->bd_info;
+ datamorph_info *ov = on->on_bi.bi_private;
+
+ if ( ov ) {
+ ldap_avl_free( ov->transformations, datamorph_info_free );
+ }
+ ch_free( ov );
+
+ return LDAP_SUCCESS;
+}
+
+int
+datamorph_initialize()
+{
+ int rc, i;
+
+ datamorph.on_bi.bi_type = "datamorph";
+ datamorph.on_bi.bi_db_init = datamorph_db_init;
+ datamorph.on_bi.bi_db_destroy = datamorph_db_destroy;
+
+ datamorph.on_bi.bi_op_add = datamorph_op_add;
+ datamorph.on_bi.bi_op_compare = datamorph_op_compare;
+ datamorph.on_bi.bi_op_modify = datamorph_op_mod;
+ datamorph.on_bi.bi_op_modrdn = datamorph_op_modrdn;
+ datamorph.on_bi.bi_op_search = datamorph_op_search;
+ datamorph.on_response = datamorph_response;
+
+ datamorph.on_bi.bi_entry_release_rw = datamorph_entry_release_rw;
+ datamorph.on_bi.bi_entry_get_rw = datamorph_entry_get_rw;
+
+ datamorph.on_bi.bi_cf_ocs = datamorph_ocs;
+
+ for ( i = 0; datamorph_syntax_defs[i].sd_desc != NULL; i++ ) {
+ rc = register_syntax( &datamorph_syntax_defs[i] );
+
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "datamorph_initialize: "
+ "error registering syntax %s\n",
+ datamorph_syntax_defs[i].sd_desc );
+ return rc;
+ }
+ }
+
+ datamorph_base_syntax = syn_find( DATAMORPH_SYNTAX_BASE );
+ assert( datamorph_base_syntax );
+
+ for ( i = 0; datamorph_mrule_defs[i].mrd_desc != NULL; i++ ) {
+ rc = register_matching_rule( &datamorph_mrule_defs[i] );
+
+ if ( rc ) {
+ Debug( LDAP_DEBUG_ANY, "datamorph_initialize: "
+ "error registering matching rule %s\n",
+ datamorph_mrule_defs[i].mrd_desc );
+ return rc;
+ }
+ }
+
+ rc = config_register_schema( datamorph_cfg, datamorph_ocs );
+ if ( rc ) return rc;
+
+ return overlay_register( &datamorph );
+}
+
+#if SLAPD_OVER_DATAMORPH == SLAPD_MOD_DYNAMIC
+int
+init_module( int argc, char *argv[] )
+{
+ return datamorph_initialize();
+}
+#endif
+
+#endif /* SLAPD_OVER_DATAMORPH */
diff --git a/contrib/slapd-modules/datamorph/slapo-datamorph.5 b/contrib/slapd-modules/datamorph/slapo-datamorph.5
new file mode 100644
index 0000000..0ce0c6a
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/slapo-datamorph.5
@@ -0,0 +1,338 @@
+.TH SLAPO-DATAMORPH 5 "RELEASEDATE" "OpenLDAP"
+.\" Copyright 2016-2017 Symas Corp. All Rights Reserved.
+.\" Copying restrictions apply. See LICENSE.
+.SH NAME
+slapo\-datamorph \- store enumerated values and fixed size integers
+.SH SYNOPSIS
+olcOverlay=datamorph
+.SH DESCRIPTION
+The
+.B datamorph
+overlay to
+.BR slapd (8)
+allows attributes with a few pre-defined values to be saved more
+space-efficiently as well as signed or unsigned integer attributes.
+
+.LP
+The overlay operates on configured attributes that must have their syntax
+compatible with
+.BR 1.3.6.1.4.1.4203.666.11.12.1.1 ,
+there are three such syntaxes defined by the overlay:
+.B 1.3.6.1.4.1.4203.666.11.12.1.2
+(Enumerated value),
+.B 1.3.6.1.4.1.4203.666.11.12.1.3
+(Fixed-size integer), and
+.B 1.3.6.1.4.1.4203.666.11.12.1.4
+(Fixed-size signed integer).
+.LP
+
+While transforming the request, if a value for an attribute is not permitted by the configuration, the behaviour depends on the operation:
+
+.RS
+.TP
+.B Search
+The affected value assertions in a
+.B Search
+request filter are replaced by a filter returning
+.B Undefined .
+.TP
+.B Compare
+Request returns
+.B Compare
+.BR False .
+.TP
+.B Add, Modify
+Requests are rejected with a
+.B Constraint
+.BR Violation .
+.RE
+
+The supported allowed matching rules for the attribute types above are:
+
+.RS
+.TP
+.B EQUALITY fixedSizeIntegerMatch
+Appropriate for syntaxes
+.B 1.3.6.1.4.1.4203.666.11.12.1.2
+(Enumerated value), and
+.B 1.3.6.1.4.1.4203.666.11.12.1.3
+(Fixed-size integer).
+.TP
+.B EQUALITY fixedSizeSignedIntegerMatch
+Appropriate for syntax
+.B 1.3.6.1.4.1.4203.666.11.12.1.4
+(Fixed-size signed integer) only.
+.TP
+.B ORDERING fixedSizeIntegerOrderingMatch
+Appropriate for syntaxes
+.B 1.3.6.1.4.1.4203.666.11.12.1.2
+(Enumerated value), and
+.B 1.3.6.1.4.1.4203.666.11.12.1.3
+(Fixed-size integer). Enumerated value attributes are compared according to
+their stored database value.
+.TP
+.B ORDERING fixedSizeSignedIntegerOrderingMatch
+Appropriate for syntax
+.B 1.3.6.1.4.1.4203.666.11.12.1.4
+(Fixed-size signed integer) only.
+
+
+.SH CONFIGURATION LAYOUT
+
+The overlay has to be instantiated under a database adding an entry of
+.B olcOverlay=datamorph
+with objectClass of
+.BR olcDatamorphConfig.
+
+The overlay configuration subtree consists of the following levels:
+.RS
+.TP
+.B objectClass=olcDatamorphConfig
+Main overlay configuration. Created directly under the database
+configuration entry.
+.TP
+.B objectClass=olcDatamorphInteger
+Specifies a
+.B fixed-size integer
+attribute and must be a child of an entry with
+.BR objectClass=olcDatamorphConfig .
+There may be as many such entries as necessary provided they all specify a
+different attribute in the
+.B olcDatamorphAttribute
+attribute.
+.TP
+.B objectClass=olcDatamorphEnum
+Specifies an
+.B enumerated
+attribute and must be a child of an entry with
+.BR objectClass=olcDatamorphConfig .
+There may be as many such entries as necessary provided they all specify a
+different attribute in the
+.B olcDatamorphAttribute
+attribute.
+.TP
+.B objectClass=olcDatamorphEnumValue
+Specifies a permitted value for the enumerated attribute and its database
+representation. Must be a child of an entry with
+.BR objectClass=olcDatamorphEnum .
+There may be as many such entries as necessary provided they all specify a
+different value and index in the corresponding fields.
+.RE
+
+In the case of
+.BR slapd.conf (5),
+the attribute definition is delimited by the keyword
+.B datamorph
+to define an integer or enumerated attribute followed by an arbitrary number of
+.B datamorph_value
+lines in the case of an enumerated one. Each new
+.B datamorph
+line starts configuring a new attribute.
+
+.SH ENUMERATED ATTRIBUTE CONFIGURATION ENTRY
+
+The enumerated attribute entry configuration
+.RB ( olcDatamorphEnum )
+only has the following option available:
+
+.RS
+.TP
+.B olcDatamorphAttribute: <attribute>
+Mandatory attribute, indicates that the named attribute is to be handled by the
+overlay. The
+.BR slapd.conf (5)
+equivalent is
+.B datamorph "int"
+.BR <attribute> .
+.RE
+
+The children of this entry then define how the string values map to the
+database values. They use the objectclass
+.BR olcDatamorphEnumValue ,
+which asks for the following attributes:
+
+.RS
+.TP
+.B olcDatamorphValue: <value>
+A permitted value for the attribute being configured.
+.TP
+.B olcDatamorphIndex: <0-255>
+The corresponding database value.
+.RE
+
+The
+.BR slapd.conf (5)
+equivalent of the above two is
+.B datamorph_value <0-255> <value>
+.RB .
+
+.SH FIXED-WIDTH INTEGER CONFIGURATION ENTRY
+
+The fixed-width integer configuration entry
+.RB ( olcDatamorphInteger )
+has the following options available:
+
+.RS
+.TP
+.B olcDatamorphAttribute: <attribute>
+Mandatory attribute, indicates that the named attribute is to be handled by the
+overlay. The
+.BR slapd.conf (5)
+equivalent is
+.B datamorph "int"
+.BR <attribute> .
+.TP
+.B olcDatamorphIntegerBytes: <1|2|4|8>
+Size of the integer as stored in the backend. The
+.BR slapd.conf (5)
+equivalent is
+.B datamorph_size
+.BR <1|2|4|8> .
+.TP
+.B olcDatamorphIntegerSigned: <TRUE|FALSE>
+Whether the integer is to be treated as signed. Note that the overlay will not
+enforce consistency between this option and the attribute's syntax. The
+.BR slapd.conf (5)
+equivalent is
+.B datamorph_signed
+.BR <TRUE|FALSE> .
+.TP
+.B olcDatamorphIntegerLowerBound: <number>
+The lowest value that the configured attribute will be allowed to have. This
+affects all operations where values are mentioned. The
+.BR slapd.conf (5)
+equivalent is
+.B datamorph_lower_bound
+.BR <number> .
+.TP
+.B olcDatamorphIntegerUpperBound: <number>
+The highest value that the configured attribute will be allowed to have. This
+affects all operations where values are mentioned. The
+.BR slapd.conf (5)
+equivalent is
+.B datamorph_upper_bound
+.BR <number> .
+.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}datamorph,$DATABASE
+objectClass: olcDatamorphConfig
+olcOverlay: datamorph
+
+# to handle attribute 'enumeratedAttribute'
+dn: olcDatamorphAttribute=enumeratedAttribute,olcOverlay={x}datamorph,$DATABASE
+objectClass: olcDatamorphEnum
+
+# value 'value1' corresponds to 'AQ==' (0x01)
+dn: olcDatamorphValue=value1,olcDatamorphAttribute={0}enumeratedAttribute,olcOv
+ erlay={x}datamorph,$DATABASE
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 1
+
+# value 'value11' corresponds to 'Cw==' (0x0B)
+dn: olcDatamorphValue=value11,olcDatamorphAttribute={0}enumeratedAttribute,olcO
+ verlay={x}datamorph,$DATABASE
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 11
+
+# handle attribute 'signedInteger' as a 2-byte signed integer with values
+# between -20000 and 30000 (inclusive on both sides)
+dn: olcDatamorphAttribute=signedInteger,olcOverlay={x}datamorph,$DATABASE
+objectclass: olcDatamorphInteger
+olcDatamorphIntegerBytes: 2
+olcDatamorphIntegerSigned: TRUE
+olcDatamorphIntegerLowerBound: -20000
+olcDatamorphIntegerUpperBound: 30000
+
+# handle attribute 'shortInteger' as a 1-byte unsigned integer with only values
+# 0 and 1 allowed (effectively a true/false)
+dn: olcDatamorphAttribute=shortInteger,olcOverlay={x}datamorph,$DATABASE
+objectclass: olcDatamorphInteger
+olcDatamorphIntegerBytes: 1
+olcDatamorphIntegerUpperBound: 1
+olcDatamorphIntegerSigned: FALSE
+.fi
+
+The
+.BR slapd.conf (5)
+equivalent of the above follows:
+
+.nf
+overlay datamorph
+
+datamorph enum enumeratedAttribute
+datamorph_value 1 value1
+datamorph_value 11 value11
+
+datamorph int signedInteger
+datamorph_size 2
+datamorph_signed TRUE
+datamorph_lower_bound -20000
+datamorph_upper_bound 30000
+
+datamorph int shortInteger
+datamorph_size 1
+datamorph_signed no
+datamorph_upper_bound 1
+.fi
+
+.SH REPLICATION
+
+Given that there are syntaxes and matching rules provided by the overlay, it
+should be configured on each replica to guarantee consistency.
+
+.SH BUGS AND LIMITATIONS
+Due to the fact that overlays are not active in the
+.BR slapcat (8)
+nor
+.BR slapadd (8)
+processes, backups of the database will be made exactly as stored. This means
+that backups made using
+.BR ldapsearch (1)
+cannot be used by
+.BR slapadd (8)
+nor can backups made using
+.BR slapcat (8)
+be loaded using
+.BR ldapadd (8).
+
+Value based ACLs that involve values of the transformed attributes are not
+supported.
+
+The overlay will refuse operations that add or rename entries with any of the
+configured attributes in their RDN.
+
+No controls are explicitly handled in the overlay, attaching any controls that
+reference configured attributes might lead to unexpected behaviour and is
+therefore discouraged.
+
+Increment modification of the configured attributes is not supported either.
+
+If a transformation is configured to be signed yet the attribute's schema uses the
+unsigned syntax and matching rules, inequality matching will not work as
+intended and will treat negative numbers as higher than positive numbers.
+
+.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 slapd (8),
+.BR slapcat (8),
+.BR slapadd (8)
+.SH ACKNOWLEDGEMENTS
+This module was developed in 2016 by Ondřej Kuzník for Symas Corp.
diff --git a/contrib/slapd-modules/datamorph/tests/Rules.mk b/contrib/slapd-modules/datamorph/tests/Rules.mk
new file mode 100644
index 0000000..c25c1d2
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/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/datamorph/tests/data/config.ldif b/contrib/slapd-modules/datamorph/tests/data/config.ldif
new file mode 100644
index 0000000..91f2e60
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/config.ldif
@@ -0,0 +1,108 @@
+dn: cn=datamorph,cn=schema,cn=config
+changetype: add
+objectClass: olcSchemaConfig
+olcAttributeTypes: ( 1.3.6.1.4.1.4203.666.11.12.123.1
+ NAME 'enumerated'
+ DESC 'Enumerated attribute'
+ EQUALITY fixedSizeIntegerMatch
+ ORDERING fixedSizeIntegerOrderingMatch
+ SYNTAX 1.3.6.1.4.1.4203.666.11.12.1.2 )
+olcAttributeTypes: ( 1.3.6.1.4.1.4203.666.11.12.123.2
+ NAME 'number'
+ DESC 'Integer attribute'
+ EQUALITY fixedSizeIntegerMatch
+ ORDERING fixedSizeIntegerOrderingMatch
+ SYNTAX 1.3.6.1.4.1.4203.666.11.12.1.3 )
+olcAttributeTypes: ( 1.3.6.1.4.1.4203.666.11.12.123.3
+ NAME 'signed'
+ DESC 'Signed integer attribute'
+ EQUALITY fixedSizeSignedIntegerMatch
+ ORDERING fixedSizeSignedIntegerOrderingMatch
+ SYNTAX 1.3.6.1.4.1.4203.666.11.12.1.4 )
+olcObjectClasses: ( 1.3.6.1.4.1.4203.666.11.12.123.4
+ NAME 'transformedObject'
+ DESC 'Testing objectclass'
+ SUP top AUXILIARY
+ MAY ( enumerated $ number $ signed ) )
+
+dn: olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectClass: olcOverlayConfig
+objectclass: olcDatamorphConfig
+
+# a basic enum
+dn: olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnum
+
+dn: olcDatamorphValue=bjensen,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 1
+
+dn: olcDatamorphValue=bjorn,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 11
+
+dn: olcDatamorphValue=dots,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 12
+
+dn: olcDatamorphValue=jaj,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 13
+
+dn: olcDatamorphValue=jjones,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 14
+
+dn: olcDatamorphValue=jdoe,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 10
+
+dn: olcDatamorphValue=jen,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 101
+
+dn: olcDatamorphValue=johnd,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 20
+
+dn: olcDatamorphValue=melliot,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 51
+
+dn: olcDatamorphValue=uham,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 31
+
+dn: olcDatamorphValue=\5Cno \22name\22,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 200
+
+# an interval
+dn: olcDatamorphAttribute=signed,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphInteger
+olcDatamorphIntegerBytes: 2
+olcDatamorphIntegerSigned: TRUE
+olcDatamorphIntegerLowerBound: -20000
+olcDatamorphIntegerUpperBound: 30000
+
+# an number interval (essentially TRUE/FALSE)
+dn: olcDatamorphAttribute=number,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphInteger
+olcDatamorphIntegerBytes: 1
+olcDatamorphIntegerUpperBound: 1
+olcDatamorphIntegerSigned: FALSE
diff --git a/contrib/slapd-modules/datamorph/tests/data/datamorph.conf b/contrib/slapd-modules/datamorph/tests/data/datamorph.conf
new file mode 100644
index 0000000..7cc4899
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/datamorph.conf
@@ -0,0 +1,49 @@
+overlay datamorph
+
+# they depend on the syntaxes defined by the overlay
+attributetype ( 1.3.6.1.4.1.4203.666.11.12.123.1 NAME 'enumerated'
+ DESC 'Enumerated attribute'
+ EQUALITY fixedSizeIntegerMatch
+ ORDERING fixedSizeIntegerOrderingMatch
+ SYNTAX 1.3.6.1.4.1.4203.666.11.12.1.2 )
+
+attributetype ( 1.3.6.1.4.1.4203.666.11.12.123.2 NAME 'number'
+ DESC 'Integer attribute'
+ EQUALITY fixedSizeIntegerMatch
+ ORDERING fixedSizeIntegerOrderingMatch
+ SYNTAX 1.3.6.1.4.1.4203.666.11.12.1.3 )
+
+attributetype ( 1.3.6.1.4.1.4203.666.11.12.123.3 NAME 'signed'
+ DESC 'Signed integer attribute'
+ EQUALITY fixedSizeSignedIntegerMatch
+ ORDERING fixedSizeSignedIntegerOrderingMatch
+ SYNTAX 1.3.6.1.4.1.4203.666.11.12.1.4 )
+
+objectclass ( 1.3.6.1.4.1.4203.666.11.12.123.4 NAME 'transformedObject'
+ DESC 'Testing objectclass'
+ SUP top AUXILIARY
+ MAY ( enumerated $ number $ signed ) )
+
+datamorph eNuM enumerated
+datamorph_value 1 bjensen
+datamorph_value 11 bjorn
+datamorph_value 12 dots
+datamorph_value "13" jaj
+datamorph_value 14 jjones
+datamorph_value 10 jdoe
+datamorph_value 101 jen
+datamorph_value 20 johnd
+datamorph_value 51 "melliot"
+datamorph_value 31 uham
+datamorph_value 200 "\\no \"name\""
+
+datamorph int signed
+datamorph_size 2
+datamorph_signed TRUE
+datamorph_lower_bound -20000
+datamorph_upper_bound 30000
+
+datamorph iNT number
+datamorph_size 1
+datamorph_signed no
+datamorph_upper_bound 1
diff --git a/contrib/slapd-modules/datamorph/tests/data/test.ldif b/contrib/slapd-modules/datamorph/tests/data/test.ldif
new file mode 100644
index 0000000..67971f3
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test.ldif
@@ -0,0 +1,434 @@
+#LEAD COMMENT
+dn: dc=example,dc=com
+#EMBEDDED COMMENT
+objectClass: top
+objectClass: organization
+objectClass: domainRelatedObject
+objectClass: dcobject
+dc: example
+l: Anytown, Michigan
+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
+
+dn: ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: extensibleObject
+ou: People
+uidNumber: 0
+gidNumber: 0
+signed:: sm4=
+number:: AA==
+
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Groups
+
+dn: ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Alumni Association
+
+dn: ou=Information Technology Division,ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Information Technology Division
+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=All Staff,ou=Groups,dc=example,dc=com
+member: cn=Manager,dc=example,dc=com
+member: cn=Barbara Jensen,ou=Information Technology Division,ou=People,dc=exam
+ ple,dc=com
+member: cn=Jane Doe,ou=Alumni Association,ou=People,dc=example,dc=com
+member: cn=John Doe,ou=Information Technology Division,ou=People,dc=example,dc
+ =com
+member: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+member: cn=James A Jones 1,ou=Alumni Association,ou=People,dc=example,dc=com
+member: cn=James A Jones 2,ou=Information Technology Division,ou=People,dc=exa
+ mple,dc=com
+member: cn=Jennifer Smith,ou=Alumni Association,ou=People,dc=example,dc=com
+member: cn=Dorothy Stevens,ou=Alumni Association,ou=People,dc=example,dc=com
+member: cn=Ursula Hampster,ou=Alumni Association,ou=People,dc=example,dc=com
+member: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,dc=exampl
+ e,dc=com
+owner: cn=Manager,dc=example,dc=com
+cn: All Staff
+description: Everyone in the sample data
+objectClass: groupofnames
+
+dn: cn=Alumni Assoc Staff,ou=Groups,dc=example,dc=com
+member: cn=Manager,dc=example,dc=com
+member: cn=Dorothy Stevens,ou=Alumni Association,ou=People,dc=example,dc=com
+member: cn=James A Jones 1,ou=Alumni Association,ou=People,dc=example,dc=com
+member: cn=Jane Doe,ou=Alumni Association,ou=People,dc=example,dc=com
+member: cn=Jennifer Smith,ou=Alumni Association,ou=People,dc=example,dc=com
+member: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+member: cn=Ursula Hampster,ou=Alumni Association,ou=People,dc=example,dc=com
+owner: cn=Manager,dc=example,dc=com
+description: All Alumni Assoc Staff
+cn: Alumni Assoc Staff
+objectClass: groupofnames
+
+dn: cn=Barbara Jensen,ou=Information Technology Division,ou=People,dc=example,
+ dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: Barbara Jensen
+cn: Babs Jensen
+sn:: IEplbnNlbiA=
+uid: bjensen
+title: Mythical Manager, Research Systems
+postalAddress: ITD Prod Dev & Deployment $ 535 W. William St. Room 4212 $ Anyt
+ own, MI 48103-4943
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+userPassword:: YmplbnNlbg==
+mail: bjensen@mailgw.example.com
+homePostalAddress: 123 Wesley $ Anytown, MI 48103
+description: Mythical manager of the rsdd unix project
+drink: water
+homePhone: +1 313 555 2333
+pager: +1 313 555 3233
+facsimileTelephoneNumber: +1 313 555 2274
+telephoneNumber: +1 313 555 9022
+enumerated:: AQ==
+
+dn: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,dc=example,dc
+ =com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+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
+enumerated:: Cw==
+
+dn: cn=Dorothy Stevens,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: Dorothy Stevens
+cn: Dot Stevens
+sn: Stevens
+uid: dots
+title: Secretary, UM Alumni Association
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+drink: Lemonade
+homePostalAddress: 377 White St. Apt. 3 $ Anytown, MI 48104
+description: Very tall
+facsimileTelephoneNumber: +1 313 555 3223
+telephoneNumber: +1 313 555 3664
+mail: dots@mail.alumni.example.com
+homePhone: +1 313 555 0454
+enumerated:: DA==
+
+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
+
+dn: cn=James A Jones 1,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: James A Jones 1
+cn: James Jones
+cn: Jim Jones
+sn: Jones
+uid: jaj
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+userPassword:: amFq
+homePostalAddress: 3882 Beverly Rd. $ Anytown, MI 48105
+homePhone: +1 313 555 4772
+description: Outstanding
+title: Mad Cow Researcher, UM Alumni Association
+pager: +1 313 555 3923
+mail: jaj@mail.alumni.example.com
+facsimileTelephoneNumber: +1 313 555 4332
+telephoneNumber: +1 313 555 0895
+enumerated:: DQ==
+
+dn: cn=James A Jones 2,ou=Information Technology Division,ou=People,dc=example
+ ,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: James A Jones 2
+cn: James Jones
+cn: Jim Jones
+sn: Doe
+uid: jjones
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 933 Brooks $ Anytown, MI 48104
+homePhone: +1 313 555 8838
+title: Senior Manager, Information Technology Division
+description: Not around very much
+mail: jjones@mailgw.example.com
+postalAddress: Info Tech Division $ 535 W William $ Anytown, MI 48103
+pager: +1 313 555 2833
+facsimileTelephoneNumber: +1 313 555 8688
+telephoneNumber: +1 313 555 7334
+enumerated:: Dg==
+
+dn: cn=Jane Doe,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: Jane Doe
+cn: Jane Alverson
+sn: Doe
+uid: jdoe
+title: Programmer Analyst, UM Alumni Association
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 123 Anystreet $ Anytown, MI 48104
+drink: diet coke
+description: Enthusiastic
+mail: jdoe@woof.net
+homePhone: +1 313 555 5445
+pager: +1 313 555 1220
+facsimileTelephoneNumber: +1 313 555 2311
+telephoneNumber: +1 313 555 4774
+enumerated:: Cg==
+
+dn: cn=Jennifer Smith,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: Jennifer Smith
+cn: Jen Smith
+sn: Smith
+uid: jen
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+drink: Sam Adams
+homePostalAddress: 1000 Maple #44 $ Anytown, MI 48103
+title: Telemarketer, UM Alumni Association
+mail: jen@mail.alumni.example.com
+homePhone: +1 313 555 2333
+pager: +1 313 555 6442
+facsimileTelephoneNumber: +1 313 555 2756
+telephoneNumber: +1 313 555 8232
+enumerated:: ZQ==
+
+dn: cn=John Doe,ou=Information Technology Division,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: John Doe
+cn: Jonathon Doe
+sn: Doe
+uid: johnd
+postalAddress: ITD $ 535 W. William $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 912 East Bllvd $ Anytown, MI 48104
+title: System Administrator, Information Technology Division
+description: overworked!
+mail: johnd@mailgw.example.com
+homePhone: +1 313 555 3774
+pager: +1 313 555 6573
+facsimileTelephoneNumber: +1 313 555 4544
+telephoneNumber: +1 313 555 9394
+enumerated:: FA==
+
+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
+
+dn: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+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
+enumerated:: Mw==
+
+dn: cn=Ursula Hampster,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: Ursula Hampster
+sn: Hampster
+uid: uham
+title: Secretary, UM Alumni Association
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 123 Anystreet $ Anytown, MI 48104
+mail: uham@mail.alumni.example.com
+homePhone: +1 313 555 8421
+pager: +1 313 555 2844
+facsimileTelephoneNumber: +1 313 555 9700
+telephoneNumber: +1 313 555 5331
+enumerated:: Hw==
+
diff --git a/contrib/slapd-modules/datamorph/tests/data/test001-01-same-attr.ldif b/contrib/slapd-modules/datamorph/tests/data/test001-01-same-attr.ldif
new file mode 100644
index 0000000..b9ba88f
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test001-01-same-attr.ldif
@@ -0,0 +1,3 @@
+dn: olcDatamorphAttribute=enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnum
diff --git a/contrib/slapd-modules/datamorph/tests/data/test001-02-same-index.ldif b/contrib/slapd-modules/datamorph/tests/data/test001-02-same-index.ldif
new file mode 100644
index 0000000..1dac5dc
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test001-02-same-index.ldif
@@ -0,0 +1,4 @@
+dn: olcDatamorphValue=nope,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 1
diff --git a/contrib/slapd-modules/datamorph/tests/data/test001-02a-same-index.ldif b/contrib/slapd-modules/datamorph/tests/data/test001-02a-same-index.ldif
new file mode 100644
index 0000000..f31ab0a
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test001-02a-same-index.ldif
@@ -0,0 +1,4 @@
+dn: olcDatamorphValue={0}bjensen,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcDatamorphIndex
+olcDatamorphIndex: 11
diff --git a/contrib/slapd-modules/datamorph/tests/data/test001-03-invalid-attr.ldif b/contrib/slapd-modules/datamorph/tests/data/test001-03-invalid-attr.ldif
new file mode 100644
index 0000000..01f21c2
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test001-03-invalid-attr.ldif
@@ -0,0 +1,3 @@
+dn: olcDatamorphAttribute=uid,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnum
diff --git a/contrib/slapd-modules/datamorph/tests/data/test002-config.ldif b/contrib/slapd-modules/datamorph/tests/data/test002-config.ldif
new file mode 100644
index 0000000..2aed906
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test002-config.ldif
@@ -0,0 +1,9 @@
+dn: olcDatamorphValue=gjensen,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: add
+objectclass: olcDatamorphEnumValue
+olcDatamorphIndex: 55
+
+dn: olcDatamorphAttribute={1}signed,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+delete: olcDatamorphIntegerUpperBound
+olcDatamorphIntegerUpperBound: 30000
diff --git a/contrib/slapd-modules/datamorph/tests/data/test002-entry.ldif b/contrib/slapd-modules/datamorph/tests/data/test002-entry.ldif
new file mode 100644
index 0000000..0df14c4
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test002-entry.ldif
@@ -0,0 +1,31 @@
+dn: cn=Gern Jensen,ou=Information Technology Division,ou=People,dc=example,dc=
+ com
+changetype: add
+objectClass: testPerson
+objectClass: transformedObject
+cn: Gern Jensen
+sn: Jensen
+uid: gjensen
+title: Chief Investigator, ITD
+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
+enumerated: gjensen
+
+dn: ou=New Unit,dc=example,dc=com
+changetype: add
+objectClass: organizationalUnit
+objectClass: extensibleObject
+ou: New Unit
+uidNumber: 32345
+gidNumber: 1
+signed: 32345
+number: 1
+
diff --git a/contrib/slapd-modules/datamorph/tests/data/test002-fail.ldif b/contrib/slapd-modules/datamorph/tests/data/test002-fail.ldif
new file mode 100644
index 0000000..f834997
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test002-fail.ldif
@@ -0,0 +1,23 @@
+dn: uid=bjensen+cn=Barbara Jensen+enumerated=bjensen,ou=Information Technology Division,ou=People,dc=example,
+ dc=com
+changetype: add
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: Barbara Jensen
+cn: Babs Jensen
+sn:: IEplbnNlbiA=
+uid: bjensen
+title: Mythical Manager, Research Systems
+postalAddress: ITD Prod Dev & Deployment $ 535 W. William St. Room 4212 $ Anyt
+ own, MI 48103-4943
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+userPassword:: YmplbnNlbg==
+mail: bjensen@mailgw.example.com
+homePostalAddress: 123 Wesley $ Anytown, MI 48103
+description: Mythical manager of the rsdd unix project
+drink: water
+homePhone: +1 313 555 2333
+pager: +1 313 555 3233
+facsimileTelephoneNumber: +1 313 555 2274
+telephoneNumber: +1 313 555 9022
+
diff --git a/contrib/slapd-modules/datamorph/tests/data/test002-transformed-rdn.ldif b/contrib/slapd-modules/datamorph/tests/data/test002-transformed-rdn.ldif
new file mode 100644
index 0000000..cbcb14a
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test002-transformed-rdn.ldif
@@ -0,0 +1,5 @@
+dn: ou=New Unit,dc=www+number=1,dc=example,dc=com
+changetype: add
+objectClass: organizationalUnit
+ou: New Unit
+
diff --git a/contrib/slapd-modules/datamorph/tests/data/test003-config.ldif b/contrib/slapd-modules/datamorph/tests/data/test003-config.ldif
new file mode 100644
index 0000000..f74717c
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test003-config.ldif
@@ -0,0 +1,30 @@
+dn: olcDatamorphValue={2}dots,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcDatamorphIndex
+olcDatamorphIndex: 110
+
+dn: olcDatamorphValue={3}jaj,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcDatamorphIndex
+olcDatamorphIndex: 12
+
+dn: olcDatamorphValue={4}jjones,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcDatamorphIndex
+olcDatamorphIndex: 13
+
+dn: olcDatamorphValue={2}dots,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcDatamorphIndex
+olcDatamorphIndex: 14
+
+dn: olcDatamorphAttribute={1}signed,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcDatamorphIntegerSigned
+olcDatamorphIntegerSigned: FALSE
+-
+replace: olcDatamorphIntegerUpperBound
+olcDatamorphIntegerUpperBound: 50000
+-
+replace: olcDatamorphIntegerLowerBound
+olcDatamorphIntegerLowerBound: 50
diff --git a/contrib/slapd-modules/datamorph/tests/data/test003-out.ldif b/contrib/slapd-modules/datamorph/tests/data/test003-out.ldif
new file mode 100644
index 0000000..3dbd8bc
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test003-out.ldif
@@ -0,0 +1,125 @@
+# List regular entries
+dn: dc=example,dc=com
+objectClass: top
+objectClass: organization
+objectClass: domainRelatedObject
+objectClass: dcobject
+dc: example
+l: Anytown, Michigan
+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
+
+dn: ou=Groups,dc=example,dc=com
+objectClass: organizationalUnit
+ou: Groups
+
+
+# List entries with transformed attributes
+dn: cn=John Doe,ou=Information Technology Division,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: John Doe
+cn: Jonathon Doe
+sn: Doe
+uid: johnd
+postalAddress: ITD $ 535 W. William $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 912 East Bllvd $ Anytown, MI 48104
+title: System Administrator, Information Technology Division
+description: overworked!
+mail: johnd@mailgw.example.com
+homePhone: +1 313 555 3774
+pager: +1 313 555 6573
+facsimileTelephoneNumber: +1 313 555 4544
+telephoneNumber: +1 313 555 9394
+enumerated: johnd
+
+dn: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,dc=example,dc
+ =com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+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
+enumerated: bjorn
+
+dn: cn=Barbara Jensen,ou=Information Technology Division,ou=People,dc=example,
+ dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: Barbara Jensen
+cn: Babs Jensen
+sn:: IEplbnNlbiA=
+uid: bjensen
+title: Mythical Manager, Research Systems
+postalAddress: ITD Prod Dev & Deployment $ 535 W. William St. Room 4212 $ Anyt
+ own, MI 48103-4943
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+userPassword:: YmplbnNlbg==
+mail: bjensen@mailgw.example.com
+homePostalAddress: 123 Wesley $ Anytown, MI 48103
+description: Mythical manager of the rsdd unix project
+drink: water
+homePhone: +1 313 555 2333
+pager: +1 313 555 3233
+facsimileTelephoneNumber: +1 313 555 2274
+telephoneNumber: +1 313 555 9022
+enumerated: bjensen
+
+dn: cn=James A Jones 2,ou=Information Technology Division,ou=People,dc=example
+ ,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: James A Jones 2
+cn: James Jones
+cn: Jim Jones
+sn: Doe
+uid: jjones
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 933 Brooks $ Anytown, MI 48104
+homePhone: +1 313 555 8838
+title: Senior Manager, Information Technology Division
+description: Not around very much
+mail: jjones@mailgw.example.com
+postalAddress: Info Tech Division $ 535 W William $ Anytown, MI 48103
+pager: +1 313 555 2833
+facsimileTelephoneNumber: +1 313 555 8688
+telephoneNumber: +1 313 555 7334
+enumerated: jjones
+
+
+# Search for transformed attributes listing only those
+dn: ou=People,dc=example,dc=com
+signed: -19858
+
+dn: cn=Barbara Jensen,ou=Information Technology Division,ou=People,dc=example,
+ dc=com
+enumerated: bjensen
+
+
+# Search for transformed attributes after reconfiguring mapping
+dn: ou=People,dc=example,dc=com
+signed: 45678
+
+dn: cn=Dorothy Stevens,ou=Alumni Association,ou=People,dc=example,dc=com
+enumerated: jaj
+
diff --git a/contrib/slapd-modules/datamorph/tests/data/test005-01-fail.ldif b/contrib/slapd-modules/datamorph/tests/data/test005-01-fail.ldif
new file mode 100644
index 0000000..694aacc
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test005-01-fail.ldif
@@ -0,0 +1,5 @@
+# invalid enum value
+dn: cn=Dorothy Stevens,ou=Alumni Association,ou=People,dc=example,dc=com
+changetype: modify
+replace: enumerated
+enumerated: 2dots
diff --git a/contrib/slapd-modules/datamorph/tests/data/test005-02-fail.ldif b/contrib/slapd-modules/datamorph/tests/data/test005-02-fail.ldif
new file mode 100644
index 0000000..1ce687f
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test005-02-fail.ldif
@@ -0,0 +1,5 @@
+# enums are case sensitive
+dn: cn=Dorothy Stevens,ou=Alumni Association,ou=People,dc=example,dc=com
+changetype: modify
+replace: enumerated
+enumerated: Dots
diff --git a/contrib/slapd-modules/datamorph/tests/data/test005-03-fail.ldif b/contrib/slapd-modules/datamorph/tests/data/test005-03-fail.ldif
new file mode 100644
index 0000000..54bb9b5
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test005-03-fail.ldif
@@ -0,0 +1,5 @@
+# value does not exist in entry
+dn: cn=Dorothy Stevens,ou=Alumni Association,ou=People,dc=example,dc=com
+changetype: modify
+delete: enumerated
+enumerated: uham
diff --git a/contrib/slapd-modules/datamorph/tests/data/test005-03a-fail.ldif b/contrib/slapd-modules/datamorph/tests/data/test005-03a-fail.ldif
new file mode 100644
index 0000000..601d895
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test005-03a-fail.ldif
@@ -0,0 +1,5 @@
+# value does not exist in entry
+dn: ou=People,dc=example,dc=com
+changetype: modify
+delete: signed
+signed: 2
diff --git a/contrib/slapd-modules/datamorph/tests/data/test005-04-fail.ldif b/contrib/slapd-modules/datamorph/tests/data/test005-04-fail.ldif
new file mode 100644
index 0000000..d97effc
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test005-04-fail.ldif
@@ -0,0 +1,10 @@
+# a value outside the bounds
+dn: ou=People,dc=example,dc=com
+changetype: modify
+replace: signed
+signed: 2
+-
+replace: number
+number: -1
+-
+
diff --git a/contrib/slapd-modules/datamorph/tests/data/test005-04a-fail.ldif b/contrib/slapd-modules/datamorph/tests/data/test005-04a-fail.ldif
new file mode 100644
index 0000000..63b3263
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test005-04a-fail.ldif
@@ -0,0 +1,6 @@
+# a value outside the bounds
+dn: ou=People,dc=example,dc=com
+changetype: modify
+replace: signed
+signed: 32000
+-
diff --git a/contrib/slapd-modules/datamorph/tests/data/test005-changes.ldif b/contrib/slapd-modules/datamorph/tests/data/test005-changes.ldif
new file mode 100644
index 0000000..17a72da
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test005-changes.ldif
@@ -0,0 +1,30 @@
+dn: ou=People,dc=example,dc=com
+changetype: modify
+replace: signed
+signed: -1
+-
+replace: number
+number: 1
+-
+
+dn: cn=Dorothy Stevens,ou=Alumni Association,ou=People,dc=example,dc=com
+changetype: modify
+replace: enumerated
+enumerated: jaj
+-
+
+dn: cn=Barbara Jensen,ou=Information Technology Division,ou=People,dc=example,dc=com
+changetype: modify
+add: enumerated
+enumerated: bjorn
+enumerated: uham
+-
+
+dn: cn=John Doe,ou=Information Technology Division,ou=People,dc=example,dc=com
+changetype: modify
+delete: enumerated
+enumerated: johnd
+-
+add: enumerated
+enumerated: melliot
+-
diff --git a/contrib/slapd-modules/datamorph/tests/data/test005-out.ldif b/contrib/slapd-modules/datamorph/tests/data/test005-out.ldif
new file mode 100644
index 0000000..4c8c360
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test005-out.ldif
@@ -0,0 +1,212 @@
+# Test1: list entries that should have been changed by ldapmodify
+dn: ou=People,dc=example,dc=com
+objectClass: organizationalUnit
+objectClass: extensibleObject
+ou: People
+uidNumber: 0
+gidNumber: 0
+signed: -1
+number: 1
+
+dn: cn=Barbara Jensen,ou=Information Technology Division,ou=People,dc=example,
+ dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: Barbara Jensen
+cn: Babs Jensen
+sn:: IEplbnNlbiA=
+uid: bjensen
+title: Mythical Manager, Research Systems
+postalAddress: ITD Prod Dev & Deployment $ 535 W. William St. Room 4212 $ Anyt
+ own, MI 48103-4943
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+userPassword:: YmplbnNlbg==
+mail: bjensen@mailgw.example.com
+homePostalAddress: 123 Wesley $ Anytown, MI 48103
+description: Mythical manager of the rsdd unix project
+drink: water
+homePhone: +1 313 555 2333
+pager: +1 313 555 3233
+facsimileTelephoneNumber: +1 313 555 2274
+telephoneNumber: +1 313 555 9022
+enumerated: bjensen
+enumerated: bjorn
+enumerated: uham
+
+dn: cn=Bjorn Jensen,ou=Information Technology Division,ou=People,dc=example,dc
+ =com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+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
+enumerated: bjorn
+
+dn: cn=Dorothy Stevens,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: Dorothy Stevens
+cn: Dot Stevens
+sn: Stevens
+uid: dots
+title: Secretary, UM Alumni Association
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+drink: Lemonade
+homePostalAddress: 377 White St. Apt. 3 $ Anytown, MI 48104
+description: Very tall
+facsimileTelephoneNumber: +1 313 555 3223
+telephoneNumber: +1 313 555 3664
+mail: dots@mail.alumni.example.com
+homePhone: +1 313 555 0454
+enumerated: jaj
+
+dn: cn=James A Jones 1,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: James A Jones 1
+cn: James Jones
+cn: Jim Jones
+sn: Jones
+uid: jaj
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+userPassword:: amFq
+homePostalAddress: 3882 Beverly Rd. $ Anytown, MI 48105
+homePhone: +1 313 555 4772
+description: Outstanding
+title: Mad Cow Researcher, UM Alumni Association
+pager: +1 313 555 3923
+mail: jaj@mail.alumni.example.com
+facsimileTelephoneNumber: +1 313 555 4332
+telephoneNumber: +1 313 555 0895
+enumerated: jaj
+
+dn: cn=James A Jones 2,ou=Information Technology Division,ou=People,dc=example
+ ,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: James A Jones 2
+cn: James Jones
+cn: Jim Jones
+sn: Doe
+uid: jjones
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 933 Brooks $ Anytown, MI 48104
+homePhone: +1 313 555 8838
+title: Senior Manager, Information Technology Division
+description: Not around very much
+mail: jjones@mailgw.example.com
+postalAddress: Info Tech Division $ 535 W William $ Anytown, MI 48103
+pager: +1 313 555 2833
+facsimileTelephoneNumber: +1 313 555 8688
+telephoneNumber: +1 313 555 7334
+enumerated: jjones
+
+dn: cn=Jane Doe,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: Jane Doe
+cn: Jane Alverson
+sn: Doe
+uid: jdoe
+title: Programmer Analyst, UM Alumni Association
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 123 Anystreet $ Anytown, MI 48104
+drink: diet coke
+description: Enthusiastic
+mail: jdoe@woof.net
+homePhone: +1 313 555 5445
+pager: +1 313 555 1220
+facsimileTelephoneNumber: +1 313 555 2311
+telephoneNumber: +1 313 555 4774
+enumerated: jdoe
+
+dn: cn=Jennifer Smith,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: Jennifer Smith
+cn: Jen Smith
+sn: Smith
+uid: jen
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+drink: Sam Adams
+homePostalAddress: 1000 Maple #44 $ Anytown, MI 48103
+title: Telemarketer, UM Alumni Association
+mail: jen@mail.alumni.example.com
+homePhone: +1 313 555 2333
+pager: +1 313 555 6442
+facsimileTelephoneNumber: +1 313 555 2756
+telephoneNumber: +1 313 555 8232
+enumerated: jen
+
+dn: cn=John Doe,ou=Information Technology Division,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: John Doe
+cn: Jonathon Doe
+sn: Doe
+uid: johnd
+postalAddress: ITD $ 535 W. William $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 912 East Bllvd $ Anytown, MI 48104
+title: System Administrator, Information Technology Division
+description: overworked!
+mail: johnd@mailgw.example.com
+homePhone: +1 313 555 3774
+pager: +1 313 555 6573
+facsimileTelephoneNumber: +1 313 555 4544
+telephoneNumber: +1 313 555 9394
+enumerated: melliot
+
+dn: cn=Mark Elliot,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+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
+enumerated: melliot
+
+dn: cn=Ursula Hampster,ou=Alumni Association,ou=People,dc=example,dc=com
+objectClass: OpenLDAPperson
+objectClass: transformedObject
+cn: Ursula Hampster
+sn: Hampster
+uid: uham
+title: Secretary, UM Alumni Association
+postalAddress: Alumni Association $ 111 Maple St $ Anytown, MI 48109
+seeAlso: cn=All Staff,ou=Groups,dc=example,dc=com
+homePostalAddress: 123 Anystreet $ Anytown, MI 48104
+mail: uham@mail.alumni.example.com
+homePhone: +1 313 555 8421
+pager: +1 313 555 2844
+facsimileTelephoneNumber: +1 313 555 9700
+telephoneNumber: +1 313 555 5331
+enumerated: uham
+
diff --git a/contrib/slapd-modules/datamorph/tests/data/test007-config.ldif b/contrib/slapd-modules/datamorph/tests/data/test007-config.ldif
new file mode 100644
index 0000000..3820831
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/data/test007-config.ldif
@@ -0,0 +1,30 @@
+dn: olcDatamorphValue={2}dots,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcDatamorphIndex
+olcDatamorphIndex: 110
+
+dn: olcDatamorphValue={4}jjones,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcDatamorphIndex
+olcDatamorphIndex: 14
+
+dn: olcDatamorphValue={3}jaj,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcDatamorphIndex
+olcDatamorphIndex: 13
+
+dn: olcDatamorphValue={2}dots,olcDatamorphAttribute={0}enumerated,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcDatamorphIndex
+olcDatamorphIndex: 12
+
+dn: olcDatamorphAttribute={1}signed,olcOverlay={0}datamorph,olcDatabase={1}@BACKEND@,cn=config
+changetype: modify
+replace: olcDatamorphIntegerSigned
+olcDatamorphIntegerSigned: TRUE
+-
+replace: olcDatamorphIntegerLowerBound
+olcDatamorphIntegerLowerBound: -20000
+-
+replace: olcDatamorphIntegerUpperBound
+olcDatamorphIntegerUpperBound: 30000
diff --git a/contrib/slapd-modules/datamorph/tests/run b/contrib/slapd-modules/datamorph/tests/run
new file mode 100755
index 0000000..6a38431
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/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/datamorph/tests/scripts/all b/contrib/slapd-modules/datamorph/tests/scripts/all
new file mode 100755
index 0000000..d6d6dc7
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/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/datamorph/tests/scripts/common.sh b/contrib/slapd-modules/datamorph/tests/scripts/common.sh
new file mode 100755
index 0000000..a468732
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/scripts/common.sh
@@ -0,0 +1,152 @@
+#! /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.
+
+OVERLAY_CONFIG=${OVERLAY_CONFIG-data/config.ldif}
+
+mkdir -p $TESTDIR $DBDIR1
+
+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
+
+$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`/../datamorph.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`/../datamorph.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 datamorph 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
+
+if test $INDEXDB = indexdb ; then
+ echo "Configure indexing for transformed attributes..."
+ $LDAPMODIFY -v -D cn=config -H $URI1 -y $CONFIGPWF \
+ >> $TESTOUT 2>&1 <<EOMOD
+dn: olcDatabase={1}$BACKEND,cn=config
+changetype: modify
+add: olcDbIndex
+olcDbIndex: enumerated pres,eq
+olcDbIndex: number pres,eq
+olcDbIndex: signed pres,eq
+EOMOD
+ RC=$?
+ if test $RC != 0 ; then
+ echo "ldapmodify failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+ fi
+else
+ echo "Skipping indexing setup for this database"
+fi
+
+echo "Stopping slapd on TCP/IP port $PORT1..."
+kill -HUP $KILLPIDS
+KILLPIDS=""
+sleep $SLEEP0
+
+echo "Running slapadd to build slapd database..."
+$SLAPADD -F $TESTDIR/confdir -l data/test.ldif
+RC=$?
+if test $RC != 0 ; then
+ echo "slapadd failed ($RC)!"
+ exit $RC
+fi
+
+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
diff --git a/contrib/slapd-modules/datamorph/tests/scripts/test001-config b/contrib/slapd-modules/datamorph/tests/scripts/test001-config
new file mode 100755
index 0000000..c4bfdf0
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/scripts/test001-config
@@ -0,0 +1,248 @@
+#! /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..." >> $SERVER6OUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+ >> $SERVER6OUT 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 ../datamorph.la' \
+ -e '/database.*monitor/i\
+include data/datamorph.conf' \
+ > $CONF2
+echo "database config" >>$CONF2
+echo "rootpw `$SLAPPASSWD -T $CONFIGPWF`" >>$CONF2
+
+$SLAPADD -f $CONF2 -l data/test.ldif
+RC=$?
+if test $RC != 0 ; then
+ echo "slapadd failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Starting slapd on TCP/IP port $PORT2..."
+$SLAPD -f $CONF2 -h $URI2 -d $LVL >> $LOG2 2>&1 &
+PID=$!
+if test $WAIT != 0 ; then
+ echo PID $PID
+ read foo
+fi
+
+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 "# search output from server running 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
+
+echo "Stopping slapd on TCP/IP port $PORT2..."
+kill -HUP $PID
+
+$SLAPD -Tt -f $CONF2 -F $TESTDIR/conftest -d $LVL >> $LOG3 2>&1
+
+echo "Starting slapd on TCP/IP port $PORT2..."
+$SLAPD -F $TESTDIR/conftest -h $URI2 -d $LVL >> $LOG3 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}datamorph,olcDatabase={1}$BACKEND,cn=config" \
+ | sed -e "s/ {[0-9]*}/ /" -e "s/={[0-9]*}/=/g" \
+ >> $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..." >> $SERVER3OUT
+$LDAPSEARCH -D cn=config -H $URI2 -y $CONFIGPWF \
+ -b "olcOverlay={0}datamorph,olcDatabase={1}$BACKEND,cn=config" \
+ | sed -e "s/ {[0-9]*}/ /" -e "s/={[0-9]*}/=/g" \
+ >> $SERVER3OUT 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 e < $SERVER3OUT > $SERVER3FLT
+echo "Filtering expected entries..."
+$LDIFFILTER -s e < $SERVER1OUT > $SERVER1FLT
+echo "Comparing filter output..."
+$CMP $SERVER3FLT $SERVER1FLT > $CMPOUT
+
+if test $? != 0 ; then
+ echo "Comparison failed"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit 1
+fi
+
+rm $SERVER1OUT $SERVER3OUT
+
+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..." >> $SERVER3OUT
+$LDAPSEARCH -b "$BASEDN" -H $URI2 \
+ >> $SERVER3OUT 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
+$LDIFFILTER -s e < $SERVER3OUT > $SERVER3FLT
+echo "Filtering expected entries..."
+$LDIFFILTER -s e < $SERVER6OUT > $SERVER6FLT
+echo "Comparing filter output..."
+$CMP $SERVER6FLT $SERVER1FLT > $CMPOUT && \
+$CMP $SERVER6FLT $SERVER2FLT > $CMPOUT && \
+$CMP $SERVER6FLT $SERVER3FLT > $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/datamorph/tests/scripts/test002-add-delete b/contrib/slapd-modules/datamorph/tests/scripts/test002-add-delete
new file mode 100755
index 0000000..f947d09
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/scripts/test002-add-delete
@@ -0,0 +1,147 @@
+#! /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 entries (should fail this time)..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+ -c -f data/test002-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 other entries (should fail)..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+ -f data/test002-fail.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
+
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+ -f data/test002-transformed-rdn.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 "Configuring new value..."
+. $CONFFILTER $BACKEND $MONITORDB < data/test002-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 "Adding some of the entries again..."
+$LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+ -f data/test002-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 "Saving search output..."
+$LDAPSEARCH -H $URI1 -b "$BASEDN" \
+ "(|(cn=Gern Jensen)(ou=New Unit))" \
+ >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapsearch 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" \
+ "ou=New Unit,$BASEDN" \
+ >> $TESTOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapdelete failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+LDIF=data/test002-entry.ldif
+
+echo "Filtering ldapsearch results..."
+$LDIFFILTER -s ae < $SEARCHOUT > $SEARCHFLT
+echo "Filtering expected entries..."
+$LDIFFILTER -s ae < $LDIF | grep -v '^changetype:' > $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/datamorph/tests/scripts/test003-search b/contrib/slapd-modules/datamorph/tests/scripts/test003-search
new file mode 100755
index 0000000..9afe677
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/scripts/test003-search
@@ -0,0 +1,106 @@
+#! /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 "(|(ou=Groups)(st=*))" \
+ >> $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 transformed attributes..."
+echo >> $SEARCHOUT
+echo "# Testing searches listing transformed attributes..." >> $SEARCHOUT
+$LDAPSEARCH -b "ou=Information Technology Division,ou=People,$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 "Testing searches filtering on transformed attributes..."
+echo >> $SEARCHOUT
+echo "# Testing searches filtering on transformed attributes..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+ "(|(enumerated=bjensen)(&(signed=-19858)(signed<=0)(signed>=-20000)))" \
+ enumerated signed \
+ >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapsearch failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Reconfiguring transformation definition..."
+. $CONFFILTER $BACKEND $MONITORDB < data/test003-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 "Testing searches filtering on the new values..."
+echo >> $SEARCHOUT
+echo "# Testing searches filtering on the new values..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+ "(|(enumerated=not a value)(enumerated=jaj)(&(signed=45678)(!(signed>=50000))(signed>=44444)))" \
+ enumerated signed \
+ >> $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/datamorph/tests/scripts/test004-compare b/contrib/slapd-modules/datamorph/tests/scripts/test004-compare
new file mode 100755
index 0000000..d4b535b
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/scripts/test004-compare
@@ -0,0 +1,62 @@
+#! /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 transformed enum entry..."
+$LDAPCOMPARE -H $URI1 \
+ "cn=Jane Doe,ou=Alumni Association,ou=People,$BASEDN" \
+ "enumerated:jdoe" >> $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 transformed interval entry..."
+$LDAPCOMPARE -H $URI1 "ou=People,$BASEDN" \
+ "signed:-19858" >> $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/datamorph/tests/scripts/test005-modify b/contrib/slapd-modules/datamorph/tests/scripts/test005-modify
new file mode 100755
index 0000000..94cf1c0
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/scripts/test005-modify
@@ -0,0 +1,89 @@
+#! /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
+
+echo "Applying invalid changes (should fail)..."
+for CHANGE in data/test005-*fail.ldif; do
+ echo "... $CHANGE"
+ $LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+ -f $CHANGE >> $TESTOUT 2>&1
+ RC=$?
+ case $RC in
+ 0)
+ echo "ldapmodify should have failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit 1
+ ;;
+ 16|19)
+ echo "ldapmodify failed ($RC)"
+ ;;
+ *)
+ echo "ldapmodify failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+ ;;
+ esac
+done
+
+echo "Reading affected entries back..."
+echo "# Reading affected entries back..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+ '(|(objectClass=OpenLDAPperson)(ou=people))' \
+ >> $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/datamorph/tests/scripts/test006-modrdn b/contrib/slapd-modules/datamorph/tests/scripts/test006-modrdn
new file mode 100755
index 0000000..56e2f2a
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/scripts/test006-modrdn
@@ -0,0 +1,52 @@
+#! /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 "Renaming an entry to add new value (should fail)..."
+$LDAPMODRDN -D $MANAGERDN -H $URI1 -w $PASSWD \
+ "cn=Mark Elliot,ou=Alumni Association,ou=People,$BASEDN" \
+ "cn=Mark Elliot+enumerated=melliot" \
+ >> $TESTOUT 2>&1
+RC=$?
+case $RC in
+0)
+ echo "ldapmodrdn should have failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit 1
+ ;;
+19)
+ echo "ldapmodrdn failed ($RC)"
+ ;;
+*)
+ echo "ldapmodrdn failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+ ;;
+esac
+
+test $KILLSERVERS != no && kill -HUP $KILLPIDS
+
+echo ">>>>> Test succeeded"
+
+test $KILLSERVERS != no && wait
+
+exit 0
diff --git a/contrib/slapd-modules/datamorph/tests/scripts/test007-transformed-replication b/contrib/slapd-modules/datamorph/tests/scripts/test007-transformed-replication
new file mode 100755
index 0000000..5b2ea4d
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/scripts/test007-transformed-replication
@@ -0,0 +1,296 @@
+#! /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,cn=config
+changetype: add
+objectClass: olcModuleList
+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
+
+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 overlay on consumer..."
+$LDAPMODIFY -v -D cn=config -H $URI4 -y $CONFIGPWF \
+ > $TESTOUT 2>&1 <<EOMOD
+dn: cn=module,cn=config
+changetype: add
+objectClass: olcModuleList
+olcModuleLoad: `pwd`/../datamorph.la
+EOMOD
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapmodify failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Configuring syncprov on 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
+
+. $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 replicated entries..."
+echo "# Testing searches against regular replicated entries..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI4 "(|(ou=Groups)(st=*))" \
+ >> $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 transformed attributes..."
+echo >> $SEARCHOUT
+echo "# Testing searches listing replicated transformed attributes..." >> $SEARCHOUT
+$LDAPSEARCH -b "ou=Information Technology Division,ou=People,$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 "Testing searches filtering on replicated transformed attributes..."
+echo >> $SEARCHOUT
+echo "# Testing searches filtering on replicated transformed attributes..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI4 \
+ "(|(enumerated=bjensen)(&(signed=-19858)(signed<=0)(signed>=-20000)))" \
+ enumerated signed \
+ >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapsearch failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Reconfiguring transformation definition..."
+. $CONFFILTER $BACKEND $MONITORDB < data/test003-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
+
+. $CONFFILTER $BACKEND $MONITORDB < data/test003-config.ldif | \
+$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
+
+echo "Testing searches filtering on the new replicated values..."
+echo >> $SEARCHOUT
+echo "# Testing searches filtering on the new replicated values..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI4 \
+ "(|(enumerated=not a value)(enumerated=jaj)(&(signed=45678)(!(signed>=50000))(signed>=44444)))" \
+ enumerated signed \
+ >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapsearch failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+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"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit 1
+fi
+
+rm $SEARCHOUT
+
+echo "Reverting part of the above configuration for remainder of the test..."
+. $CONFFILTER $BACKEND $MONITORDB < data/test007-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
+
+. $CONFFILTER $BACKEND $MONITORDB < data/test007-config.ldif | \
+$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
+
+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
+
+echo "Applying invalid changes (should fail)..."
+for CHANGE in data/test005-*fail.ldif; do
+ echo "... $CHANGE"
+ $LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+ -f $CHANGE >> $TESTOUT 2>&1
+ RC=$?
+ case $RC in
+ 0)
+ echo "ldapmodify should have failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit 1
+ ;;
+ 16|19)
+ echo "ldapmodify failed ($RC)"
+ ;;
+ *)
+ echo "ldapmodify failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+ ;;
+ esac
+done
+
+echo "Waiting ${SLEEP1} seconds for consumer to finish replicating..."
+sleep ${SLEEP1}
+
+echo "Reading affected entries back..."
+echo "# Reading affected entries back..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+ '(|(objectClass=OpenLDAPperson)(ou=people))' \
+ >> $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/datamorph/tests/scripts/test008-ignored-replication b/contrib/slapd-modules/datamorph/tests/scripts/test008-ignored-replication
new file mode 100755
index 0000000..a1fcb71
--- /dev/null
+++ b/contrib/slapd-modules/datamorph/tests/scripts/test008-ignored-replication
@@ -0,0 +1,299 @@
+#! /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,cn=config
+changetype: add
+objectClass: olcModuleList
+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
+
+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 overlay on consumer..."
+$LDAPMODIFY -v -D cn=config -H $URI4 -y $CONFIGPWF \
+ > $TESTOUT 2>&1 <<EOMOD
+dn: cn=module,cn=config
+changetype: add
+objectClass: olcModuleList
+olcModuleLoad: `pwd`/../datamorph.la
+EOMOD
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapmodify failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Configuring syncprov on 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
+
+. $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 replicated entries..."
+echo "# Testing searches against regular replicated entries..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI4 "(|(ou=Groups)(st=*))" \
+ >> $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 transformed attributes..."
+echo >> $SEARCHOUT
+echo "# Testing searches listing replicated transformed attributes..." >> $SEARCHOUT
+$LDAPSEARCH -b "ou=Information Technology Division,ou=People,$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 "Testing searches filtering on replicated transformed attributes..."
+echo >> $SEARCHOUT
+echo "# Testing searches filtering on replicated transformed attributes..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI4 \
+ "(|(enumerated=bjensen)(&(signed=-19858)(signed<=0)(signed>=-20000)))" \
+ enumerated signed \
+ >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapsearch failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+echo "Reconfiguring transformation definition..."
+. $CONFFILTER $BACKEND $MONITORDB < data/test003-config.ldif | \
+sed 's/{0}datamorph/{1}datamorph/' | \
+$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
+
+. $CONFFILTER $BACKEND $MONITORDB < data/test003-config.ldif | \
+$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
+
+echo "Testing searches filtering on the new replicated values..."
+echo >> $SEARCHOUT
+echo "# Testing searches filtering on the new replicated values..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI4 \
+ "(|(enumerated=not a value)(enumerated=jaj)(&(signed=45678)(!(signed>=50000))(signed>=44444)))" \
+ enumerated signed \
+ >> $SEARCHOUT 2>&1
+RC=$?
+if test $RC != 0 ; then
+ echo "ldapsearch failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+fi
+
+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"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit 1
+fi
+
+rm $SEARCHOUT
+
+echo "Reverting part of the above configuration for remainder of the test..."
+. $CONFFILTER $BACKEND $MONITORDB < data/test007-config.ldif | \
+sed 's/{0}datamorph/{1}datamorph/' | \
+$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
+
+. $CONFFILTER $BACKEND $MONITORDB < data/test007-config.ldif | \
+$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
+
+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
+
+echo "Applying invalid changes (should fail)..."
+for CHANGE in data/test005-*fail.ldif; do
+ echo "... $CHANGE"
+ $LDAPMODIFY -D $MANAGERDN -H $URI1 -w $PASSWD \
+ -f $CHANGE >> $TESTOUT 2>&1
+ RC=$?
+ case $RC in
+ 0)
+ echo "ldapmodify should have failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit 1
+ ;;
+ 16|19)
+ echo "ldapmodify failed ($RC)"
+ ;;
+ *)
+ echo "ldapmodify failed ($RC)!"
+ test $KILLSERVERS != no && kill -HUP $KILLPIDS
+ exit $RC
+ ;;
+ esac
+done
+
+echo "Waiting ${SLEEP1} seconds for consumer to finish replicating..."
+sleep ${SLEEP1}
+
+echo "Reading affected entries back..."
+echo "# Reading affected entries back..." >> $SEARCHOUT
+$LDAPSEARCH -b "$BASEDN" -H $URI1 \
+ '(|(objectClass=OpenLDAPperson)(ou=people))' \
+ >> $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