diff --git a/.389-ds-base.metadata b/.389-ds-base.metadata
index 35477bb..9fb7a7b 100644
--- a/.389-ds-base.metadata
+++ b/.389-ds-base.metadata
@@ -1 +1,2 @@
-5ea563775de60788f87373327d90c09ce37a574b SOURCES/389-ds-base-1.3.10.2.tar.bz2
+ba6a0490a0a944d3499a7af5dccb4d929869d1c5 SOURCES/389-ds-base-1.3.11.1.tar.bz2
+da11897b8eb16e7d0c52c9a3aa5db471b1e91d94 SOURCES/vendor-1.3.11.1-1.tar.gz
diff --git a/.gitignore b/.gitignore
index af808cb..37b18c3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-SOURCES/389-ds-base-1.3.10.2.tar.bz2
+SOURCES/389-ds-base-1.3.11.1.tar.bz2
+SOURCES/vendor-1.3.11.1-1.tar.gz
diff --git a/SOURCES/0041-Issue-5565-Change-default-password-storage-scheme-to.patch b/SOURCES/0041-Issue-5565-Change-default-password-storage-scheme-to.patch
new file mode 100644
index 0000000..7521060
--- /dev/null
+++ b/SOURCES/0041-Issue-5565-Change-default-password-storage-scheme-to.patch
@@ -0,0 +1,6530 @@
+From 46c23a6cff91671459cefed2878ef2f46a3afc95 Mon Sep 17 00:00:00 2001
+From: Simon Pichugin <spichugi@redhat.com>
+Date: Tue, 24 Jan 2023 16:44:25 -0800
+Subject: [PATCH 1/5] Issue 5565 - Change default password storage scheme to
+ PBKDF2-SHA512 (#5616)
+
+Description: 389-ds-base-1.3.x does not support the Rust password hashers.
+If you replicate between 389-ds-1.4.x (or higher) and 1.3.x then all authentication
+will start failing on 1.3.x.
+
+Backport Rust PBKDF2 implementation to 1.3.10 and change the default password storage
+scheme to PBKDF2-SHA512.
+
+Related: https://github.com/389ds/389-ds-base/issues/5565
+
+Reviwed by: @Firstyear, @tbordaz, @mreynolds389 (Thanks!)
+---
+ .cargo/config.in                         |   6 +
+ .gitignore                               |   4 +
+ Makefile.am                              | 211 ++++-
+ configure.ac                             |  43 ++
+ ldap/ldif/template-dse-minimal.ldif.in   |  52 ++
+ ldap/ldif/template-dse.ldif.in           |  52 ++
+ ldap/servers/slapd/entry.c               |  12 +
+ ldap/servers/slapd/fedse.c               | 102 ++-
+ ldap/servers/slapd/libglobs.c            |   9 +
+ rpm.mk                                   |  19 +-
+ rpm/389-ds-base.spec.in                  |  15 +-
+ src/Cargo.lock                           | 833 ++++++++++++++++++++
+ src/Cargo.toml                           |  15 +
+ src/librnsslapd/Cargo.toml               |  25 +
+ src/librnsslapd/README.md                |   4 +
+ src/librnsslapd/build.rs                 |  16 +
+ src/librnsslapd/src/lib.rs               |  68 ++
+ src/librslapd/Cargo.lock                 | 331 ++++++++
+ src/librslapd/Cargo.toml                 |  22 +
+ src/librslapd/README.md                  |   3 +
+ src/librslapd/build.rs                   |  16 +
+ src/librslapd/src/cache.rs               | 204 +++++
+ src/librslapd/src/lib.rs                 |  55 ++
+ src/plugins/pwdchan/Cargo.toml           |  23 +
+ src/plugins/pwdchan/src/lib.rs           | 264 +++++++
+ src/plugins/pwdchan/src/pbkdf2.rs        |  44 ++
+ src/plugins/pwdchan/src/pbkdf2_sha1.rs   |  44 ++
+ src/plugins/pwdchan/src/pbkdf2_sha256.rs |  43 ++
+ src/plugins/pwdchan/src/pbkdf2_sha512.rs |  43 ++
+ src/slapd/Cargo.toml                     |  10 +
+ src/slapd/src/error.rs                   |   6 +
+ src/slapd/src/fernet.rs                  |  30 +
+ src/slapd/src/lib.rs                     |   2 +
+ src/slapi_r_plugin/Cargo.toml            |  22 +
+ src/slapi_r_plugin/README.md             | 216 ++++++
+ src/slapi_r_plugin/build.rs              |   9 +
+ src/slapi_r_plugin/src/backend.rs        |  72 ++
+ src/slapi_r_plugin/src/ber.rs            |  92 +++
+ src/slapi_r_plugin/src/charray.rs        |  32 +
+ src/slapi_r_plugin/src/constants.rs      | 212 +++++
+ src/slapi_r_plugin/src/dn.rs             | 108 +++
+ src/slapi_r_plugin/src/entry.rs          | 100 +++
+ src/slapi_r_plugin/src/error.rs          |  66 ++
+ src/slapi_r_plugin/src/init.c            |   8 +
+ src/slapi_r_plugin/src/lib.rs            |  41 +
+ src/slapi_r_plugin/src/log.rs            |  87 +++
+ src/slapi_r_plugin/src/macros.rs         | 940 +++++++++++++++++++++++
+ src/slapi_r_plugin/src/modify.rs         | 117 +++
+ src/slapi_r_plugin/src/pblock.rs         | 323 ++++++++
+ src/slapi_r_plugin/src/plugin.rs         | 133 ++++
+ src/slapi_r_plugin/src/search.rs         | 127 +++
+ src/slapi_r_plugin/src/syntax_plugin.rs  | 142 ++++
+ src/slapi_r_plugin/src/task.rs           | 148 ++++
+ src/slapi_r_plugin/src/value.rs          | 247 ++++++
+ 54 files changed, 5859 insertions(+), 9 deletions(-)
+ create mode 100644 .cargo/config.in
+ create mode 100644 src/Cargo.lock
+ create mode 100644 src/Cargo.toml
+ create mode 100644 src/librnsslapd/Cargo.toml
+ create mode 100644 src/librnsslapd/README.md
+ create mode 100644 src/librnsslapd/build.rs
+ create mode 100644 src/librnsslapd/src/lib.rs
+ create mode 100644 src/librslapd/Cargo.lock
+ create mode 100644 src/librslapd/Cargo.toml
+ create mode 100644 src/librslapd/README.md
+ create mode 100644 src/librslapd/build.rs
+ create mode 100644 src/librslapd/src/cache.rs
+ create mode 100644 src/librslapd/src/lib.rs
+ create mode 100644 src/plugins/pwdchan/Cargo.toml
+ create mode 100644 src/plugins/pwdchan/src/lib.rs
+ create mode 100644 src/plugins/pwdchan/src/pbkdf2.rs
+ create mode 100644 src/plugins/pwdchan/src/pbkdf2_sha1.rs
+ create mode 100644 src/plugins/pwdchan/src/pbkdf2_sha256.rs
+ create mode 100644 src/plugins/pwdchan/src/pbkdf2_sha512.rs
+ create mode 100644 src/slapd/Cargo.toml
+ create mode 100644 src/slapd/src/error.rs
+ create mode 100644 src/slapd/src/fernet.rs
+ create mode 100644 src/slapd/src/lib.rs
+ create mode 100644 src/slapi_r_plugin/Cargo.toml
+ create mode 100644 src/slapi_r_plugin/README.md
+ create mode 100644 src/slapi_r_plugin/build.rs
+ create mode 100644 src/slapi_r_plugin/src/backend.rs
+ create mode 100644 src/slapi_r_plugin/src/ber.rs
+ create mode 100644 src/slapi_r_plugin/src/charray.rs
+ create mode 100644 src/slapi_r_plugin/src/constants.rs
+ create mode 100644 src/slapi_r_plugin/src/dn.rs
+ create mode 100644 src/slapi_r_plugin/src/entry.rs
+ create mode 100644 src/slapi_r_plugin/src/error.rs
+ create mode 100644 src/slapi_r_plugin/src/init.c
+ create mode 100644 src/slapi_r_plugin/src/lib.rs
+ create mode 100644 src/slapi_r_plugin/src/log.rs
+ create mode 100644 src/slapi_r_plugin/src/macros.rs
+ create mode 100644 src/slapi_r_plugin/src/modify.rs
+ create mode 100644 src/slapi_r_plugin/src/pblock.rs
+ create mode 100644 src/slapi_r_plugin/src/plugin.rs
+ create mode 100644 src/slapi_r_plugin/src/search.rs
+ create mode 100644 src/slapi_r_plugin/src/syntax_plugin.rs
+ create mode 100644 src/slapi_r_plugin/src/task.rs
+ create mode 100644 src/slapi_r_plugin/src/value.rs
+
+diff --git a/.cargo/config.in b/.cargo/config.in
+new file mode 100644
+index 000000000..d7d8ff4d4
+--- /dev/null
++++ b/.cargo/config.in
+@@ -0,0 +1,6 @@
++[source.crates-io]
++registry = "https://github.com/rust-lang/crates.io-index"
++@rust_vendor_sources@
++
++[source.vendored-sources]
++directory = "./vendor"
+diff --git a/.gitignore b/.gitignore
+index b8bff61fb..bb89b159d 100644
+--- a/.gitignore
++++ b/.gitignore
+@@ -216,3 +216,7 @@ wrappers/ds_systemd_ask_password_acl
+ docs/slapi.doxy
+ man/man3/
+ html/
++src/librslapd/target/
++ldap/servers/slapd/rust-slapi-private.h
++vendor
++vendor.tar.gz
+diff --git a/Makefile.am b/Makefile.am
+index 5d570aa02..f631619ca 100644
+--- a/Makefile.am
++++ b/Makefile.am
+@@ -26,6 +26,29 @@ CMOCKA_INCLUDES = @cmocka_inc@
+ PROFILING_DEFINES = @profiling_defs@
+ NSPR_INCLUDES = @nspr_inc@
+ 
++# Rust inclusions.
++if RUST_ENABLE
++# Rust enabled
++RUST_ON = 1
++CARGO_FLAGS = @cargo_defs@
++RUSTC_FLAGS = @asan_rust_defs@ @debug_rust_defs@
++# -L@abs_top_builddir@/rs/@rust_target_dir@
++RUST_LDFLAGS = -ldl -lpthread -lgcc_s -lc -lm -lrt -lutil
++RUST_DEFINES = -DRUST_ENABLE
++if RUST_ENABLE_OFFLINE
++RUST_OFFLINE = --locked --offline
++else
++RUST_OFFLINE =
++endif
++else
++# Rust disabled
++RUST_ON = 0
++CARGO_FLAGS =
++RUSTC_FLAGS =
++RUST_LDFLAGS =
++RUST_DEFINES =
++endif
++
+ SVRCORE_INCLUDES = @svrcore_inc@
+ SASL_INCLUDES = @sasl_inc@
+ EVENT_INCLUDES = @event_inc@
+@@ -80,7 +103,7 @@ PATH_DEFINES = -DLOCALSTATEDIR="\"$(localstatedir)\"" -DSYSCONFDIR="\"$(sysconfd
+ # Now that we have all our defines in place, setup the CPPFLAGS
+ 
+ # These flags are the "must have" for all components
+-AM_CPPFLAGS = $(DEBUG_DEFINES) $(GCCSEC_DEFINES) $(ASAN_DEFINES) $(PROFILING_DEFINES)
++AM_CPPFLAGS = $(DEBUG_DEFINES) $(GCCSEC_DEFINES) $(ASAN_DEFINES) $(PROFILING_DEFINES) $(RUST_DEFINES)
+ # Flags for Directory Server
+ # WARNING: This needs a clean up, because slap.h is a horrible mess and is publically exposed!
+ DSPLUGIN_CPPFLAGS = $(DS_DEFINES) $(DS_INCLUDES) $(PATH_DEFINES) $(SYSTEMD_DEFINES) $(NUNCSTANS_INCLUDES) @openldap_inc@ @ldapsdk_inc@ @nss_inc@ $(NSPR_INCLUDES) @systemd_inc@
+@@ -138,7 +161,7 @@ AM_LDFLAGS = -lpthread
+ else
+ #AM_LDFLAGS = -Wl,-z,defs
+ # Provide the tcmalloc links if needed
+-AM_LDFLAGS = $(ASAN_DEFINES) $(PROFILING_LINKS) $(TCMALLOC_LINK)
++AM_LDFLAGS = $(RUST_LDFLAGS) $(ASAN_DEFINES) $(PROFILING_LINKS) $(TCMALLOC_LINK)
+ endif #end hpux
+ 
+ # https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html#Updating-version-info
+@@ -174,6 +197,10 @@ SLAPD_LDFLAGS = -version-info 1:0:1
+ BUILT_SOURCES = dberrstrs.h \
+ 	$(POLICY_FC)
+ 
++if RUST_ENABLE
++BUILT_SOURCES += rust-slapi-private.h rust-nsslapd-private.h
++endif
++
+ if enable_posix_winsync
+ LIBPOSIX_WINSYNC_PLUGIN = libposix-winsync-plugin.la
+ endif
+@@ -232,10 +259,17 @@ CLEANFILES =  dberrstrs.h ns-slapd.properties \
+ 	doxyfile.stamp ldap/admin/src/scripts/dbmon.sh \
+ 	$(NULL)
+ 
++if RUST_ENABLE
++CLEANFILES += rust-slapi-private.h
++endif
++
+ clean-local:
+ 	-rm -rf dist
+ 	-rm -rf $(abs_top_builddir)/html
+ 	-rm -rf $(abs_top_builddir)/man
++if RUST_ENABLE
++	CARGO_TARGET_DIR=$(abs_top_builddir)/rs cargo clean --manifest-path=$(srcdir)/src/Cargo.toml
++endif
+ 
+ dberrstrs.h: Makefile
+ 	perl $(srcdir)/ldap/servers/slapd/mkDBErrStrs.pl -i @db_incdir@ -o .
+@@ -337,6 +371,10 @@ serverplugin_LTLIBRARIES = libacl-plugin.la \
+ 	$(LIBPAM_PASSTHRU_PLUGIN) $(LIBDNA_PLUGIN) \
+ 	$(LIBBITWISE_PLUGIN) $(LIBPRESENCE_PLUGIN) $(LIBPOSIX_WINSYNC_PLUGIN)
+ 
++if RUST_ENABLE
++serverplugin_LTLIBRARIES += libpwdchan-plugin.la
++endif
++
+ noinst_LIBRARIES = libavl.a
+ 
+ dist_noinst_HEADERS = \
+@@ -1087,6 +1125,144 @@ libnunc_stans_la_LDFLAGS = $(AM_LDFLAGS) $(NUNCSTANS_LDFLAGS)
+ libnunc_stans_la_LIBADD = libsds.la
+ libnunc_stans_la_DEPENDENCIES = libsds.la
+ 
++if RUST_ENABLE
++
++noinst_LTLIBRARIES = librslapd.la librnsslapd.la libpwdchan.la
++
++### Why does this exist?
++#
++# Both cargo and autotools are really opinionated. It's really hard to make this work. :(
++#
++# https://people.gnome.org/~federico/blog/librsvg-build-infrastructure.html
++# https://gitlab.gnome.org/GNOME/librsvg/blob/master/Makefile.am
++
++### Rust datastructures
++
++### Rust lib slapd components
++RSLAPD_LIB = @abs_top_builddir@/rs/@rust_target_dir@/librslapd.a
++
++librslapd_la_SOURCES = \
++	src/librslapd/Cargo.toml \
++	src/librslapd/build.rs \
++	src/librslapd/src/cache.rs \
++	src/librslapd/src/lib.rs
++
++librslapd_la_EXTRA = src/librslapd/Cargo.lock
++
++@abs_top_builddir@/rs/@rust_target_dir@/librslapd.a: $(librslapd_la_SOURCES)
++	RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \
++	CARGO_TARGET_DIR=$(abs_top_builddir)/rs \
++	SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \
++	SLAPD_HEADER_DIR=$(abs_top_builddir)/ \
++		cargo rustc $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/librslapd/Cargo.toml \
++		$(CARGO_FLAGS) --verbose -- $(RUSTC_FLAGS)
++
++# The header needs the lib build first.
++rust-slapi-private.h: @abs_top_builddir@/rs/@rust_target_dir@/librslapd.a
++
++# Build rust ns-slapd components as a library.
++RNSSLAPD_LIB = @abs_top_builddir@/rs/@rust_target_dir@/librnsslapd.a
++
++librnsslapd_la_SOURCES = \
++	src/librnsslapd/Cargo.toml \
++	src/librnsslapd/build.rs \
++	src/librnsslapd/src/lib.rs
++
++librnsslapd_la_EXTRA = src/librnsslapd/Cargo.lock
++
++@abs_top_builddir@/rs/@rust_target_dir@/librnsslapd.a: $(librnsslapd_la_SOURCES)
++	RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \
++	CARGO_TARGET_DIR=$(abs_top_builddir)/rs \
++	SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \
++	SLAPD_HEADER_DIR=$(abs_top_builddir)/ \
++		cargo rustc $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/librnsslapd/Cargo.toml \
++		$(CARGO_FLAGS) --verbose -- $(RUSTC_FLAGS)
++
++# The header needs the lib build first.
++rust-nsslapd-private.h: @abs_top_builddir@/rs/@rust_target_dir@/librnsslapd.a
++
++libslapi_r_plugin_SOURCES = \
++	src/slapi_r_plugin/src/backend.rs \
++	src/slapi_r_plugin/src/ber.rs \
++	src/slapi_r_plugin/src/charray.rs \
++	src/slapi_r_plugin/src/constants.rs \
++	src/slapi_r_plugin/src/dn.rs \
++	src/slapi_r_plugin/src/entry.rs \
++	src/slapi_r_plugin/src/error.rs \
++	src/slapi_r_plugin/src/log.rs \
++	src/slapi_r_plugin/src/macros.rs \
++	src/slapi_r_plugin/src/pblock.rs \
++	src/slapi_r_plugin/src/plugin.rs \
++	src/slapi_r_plugin/src/search.rs \
++	src/slapi_r_plugin/src/syntax_plugin.rs \
++	src/slapi_r_plugin/src/task.rs \
++	src/slapi_r_plugin/src/value.rs \
++	src/slapi_r_plugin/src/lib.rs
++
++# == pwdchan
++
++PWDCHAN_LIB = @abs_top_builddir@/rs/@rust_target_dir@/libpwdchan.a
++
++libpwdchan_la_SOURCES = \
++	src/plugins/pwdchan/Cargo.toml \
++	src/plugins/pwdchan/src/lib.rs \
++	$(libslapi_r_plugin_SOURCES)
++
++libpwdchan_la_EXTRA = src/plugin/pwdchan/Cargo.lock
++
++@abs_top_builddir@/rs/@rust_target_dir@/libpwdchan.a: $(libpwdchan_la_SOURCES) libslapd.la libpwdchan.la
++	RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \
++	CARGO_TARGET_DIR=$(abs_top_builddir)/rs \
++	SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \
++	SLAPD_HEADER_DIR=$(abs_top_builddir)/ \
++		cargo rustc $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/plugins/pwdchan/Cargo.toml \
++		$(CARGO_FLAGS) --verbose -- $(RUSTC_FLAGS)
++	cp $(PWDCHAN_LIB) @abs_top_builddir@/.libs/libpwdchan.a
++
++# == pwdchan
++
++EXTRA_DIST = $(librslapd_la_SOURCES) $(librslapd_la_EXTRA) \
++			$(libpwdchan_la_SOURCES) $(libpwdchan_la_EXTRA) \
++			$(librnsslapd_la_SOURCES) $(librnsslapd_la_EXTRA)
++
++## Run rust tests
++# cargo does not support offline tests :(
++if RUST_ENABLE_OFFLINE
++else
++check-local:
++	for thing in "librslapd" "librnsslapd" ; do \
++		echo LD_LIBRARY_PATH=$(abs_top_builddir)/.libs \
++		RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \
++		CARGO_TARGET_DIR=$(abs_top_builddir)/rs \
++		SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \
++		SLAPD_HEADER_DIR=$(abs_top_builddir)/ \
++			cargo test $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/$${thing}/Cargo.toml ; \
++		LD_LIBRARY_PATH=$(abs_top_builddir)/.libs \
++		RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \
++		CARGO_TARGET_DIR=$(abs_top_builddir)/rs \
++		SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \
++		SLAPD_HEADER_DIR=$(abs_top_builddir)/ \
++			cargo test $(RUST_OFFLINE) --manifest-path=$(srcdir)/src/$${thing}/Cargo.toml ; \
++	done
++# Plugin tests are a little different
++	for thing in "plugins/pwdchan" ; do \
++		echo LD_LIBRARY_PATH=$(abs_top_builddir)/.libs \
++		RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \
++		CARGO_TARGET_DIR=$(abs_top_builddir)/rs \
++		SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \
++		SLAPD_HEADER_DIR=$(abs_top_builddir)/ \
++			cargo test $(RUST_OFFLINE) --features=slapi_r_plugin/test_log_direct \
++			--manifest-path=$(srcdir)/src/$${thing}/Cargo.toml -- --nocapture ; \
++		LD_LIBRARY_PATH=$(abs_top_builddir)/.libs \
++		RUST_BACKTRACE=1 RUSTC_BOOTSTRAP=1 \
++		CARGO_TARGET_DIR=$(abs_top_builddir)/rs \
++		SLAPD_DYLIB_DIR=$(abs_top_builddir)/ \
++		SLAPD_HEADER_DIR=$(abs_top_builddir)/ \
++			cargo test $(RUST_OFFLINE) --features=slapi_r_plugin/test_log_direct \
++			--manifest-path=$(srcdir)/src/$${thing}/Cargo.toml -- --nocapture ; \
++	done
++endif
++endif
+ 
+ #------------------------
+ # libns-dshttpd
+@@ -1246,6 +1422,10 @@ libslapd_la_CPPFLAGS = $(AM_CPPFLAGS) $(DSPLUGIN_CPPFLAGS) $(SASL_INCLUDES) @db_
+ libslapd_la_LIBADD = $(LDAPSDK_LINK) $(SASL_LINK) $(SVRCORE_LINK) $(NSS_LINK) $(NSPR_LINK) $(KERBEROS_LINK) $(PCRE_LINK) $(THREADLIB) $(SYSTEMD_LINK) libsds.la
+ libslapd_la_LDFLAGS = $(AM_LDFLAGS) $(SLAPD_LDFLAGS)
+ 
++if RUST_ENABLE
++libslapd_la_LIBADD += $(RSLAPD_LIB)
++libslapd_la_LDFLAGS += -lssl -lcrypto
++endif
+ 
+ #////////////////////////////////////////////////////////////////
+ #
+@@ -1488,6 +1668,16 @@ libderef_plugin_la_LIBADD = libslapd.la $(LDAPSDK_LINK) $(NSPR_LINK)
+ libderef_plugin_la_DEPENDENCIES = libslapd.la
+ libderef_plugin_la_LDFLAGS = -avoid-version
+ 
++if RUST_ENABLE
++#------------------------
++# libpwdchan-plugin
++#-----------------------
++libpwdchan_plugin_la_SOURCES = src/slapi_r_plugin/src/init.c
++libpwdchan_plugin_la_LIBADD = libslapd.la $(LDAPSDK_LINK) $(NSPR_LINK) -lpwdchan
++libpwdchan_plugin_la_DEPENDENCIES = libslapd.la $(PWDCHAN_LIB)
++libpwdchan_plugin_la_LDFLAGS = -avoid-version
++endif
++
+ #------------------------
+ # libpbe-plugin
+ #-----------------------
+@@ -1627,7 +1817,8 @@ libpwdstorage_plugin_la_SOURCES = ldap/servers/plugins/pwdstorage/clear_pwd.c \
+ 	ldap/servers/plugins/pwdstorage/sha_pwd.c \
+ 	ldap/servers/plugins/pwdstorage/smd5_pwd.c \
+ 	ldap/servers/plugins/pwdstorage/ssha_pwd.c \
+-	ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c
++	ldap/servers/plugins/pwdstorage/pbkdf2_pwd.c \
++	$(NULLSTRING)
+ 
+ libpwdstorage_plugin_la_CPPFLAGS = $(AM_CPPFLAGS) $(DSPLUGIN_CPPFLAGS)
+ libpwdstorage_plugin_la_LIBADD = libslapd.la $(NSS_LINK) $(NSPR_LINK) $(LIBCRYPT)
+@@ -1952,8 +2143,17 @@ ns_slapd_SOURCES = ldap/servers/slapd/abandon.c \
+ 	$(GETSOCKETPEER)
+ 
+ ns_slapd_CPPFLAGS = $(AM_CPPFLAGS) $(DSPLUGIN_CPPFLAGS) $(SASL_INCLUDES) $(SVRCORE_INCLUDES)
+-ns_slapd_LDADD = libnunc-stans.la libslapd.la libldaputil.la $(LDAPSDK_LINK) $(NSS_LINK) $(LIBADD_DL) \
+-	$(NSPR_LINK) $(SASL_LINK) $(SVRCORE_LINK) $(LIBNSL) $(LIBSOCKET) $(THREADLIB) $(SYSTEMD_LINK) $(EVENT_LINK)
++# We need our libraries to come first, then our externals libraries second.
++ns_slapd_LDADD = libnunc-stans.la libslapd.la libldaputil.la
++if RUST_ENABLE
++ns_slapd_LDADD += $(RNSSLAPD_LIB)
++endif
++ns_slapd_LDADD += $(LDAPSDK_LINK) $(NSS_LINK) $(LIBADD_DL) \
++       $(NSPR_LINK) $(SASL_LINK) $(SVRCORE_LINK) $(LIBNSL) $(LIBSOCKET) $(THREADLIB) $(SYSTEMD_LINK) $(EVENT_LINK)
++if RUST_ENABLE
++ns_slapd_LDADD += -lssl -lcrypto
++endif
++
+ ns_slapd_DEPENDENCIES = libslapd.la libnunc-stans.la
+ # We need to link ns-slapd with the C++ compiler on HP-UX since we load
+ # some C++ shared libraries (such as icu).
+@@ -2120,6 +2320,7 @@ fixupcmd = sed \
+ 	-e 's,@enable_presence\@,$(enable_presence),g' \
+ 	-e 's,@enable_asan\@,$(ASAN_ON),g' \
+ 	-e 's,@enable_perl\@,@enable_perl@,g' \
++	-e 's,@enable_rust\@,@enable_rust@,g' \
+ 	-e 's,@ECHO_N\@,$(ECHO_N),g' \
+ 	-e 's,@ECHO_C\@,$(ECHO_C),g' \
+ 	-e 's,@brand\@,$(brand),g' \
+diff --git a/configure.ac b/configure.ac
+index 91d6d398b..1c94fe81d 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -78,29 +78,70 @@ AC_CHECK_FUNCS([clock_gettime], [], AC_MSG_ERROR([unable to locate required symb
+ # This will detect if we need to add the LIBADD_DL value for us.
+ LT_LIB_DLLOAD
+ 
++# Optional rust component support.
++AC_MSG_CHECKING(for --enable-rust-offline)
++AC_ARG_ENABLE(rust_offline, AS_HELP_STRING([--enable-rust-offline], [Enable rust building offline. you MUST have run vendor! (default: no)]),
++              [], [ enable_rust_offline=no ])
++AC_MSG_RESULT($enable_rust_offline)
++AM_CONDITIONAL([RUST_ENABLE_OFFLINE],[test "$enable_rust_offline" = yes])
++
++AS_IF([test "$enable_rust_offline" = yes],
++    [rust_vendor_sources="replace-with = \"vendored-sources\""],
++    [rust_vendor_sources=""])
++AC_SUBST([rust_vendor_sources])
++
++AC_MSG_CHECKING(for --enable-rust)
++AC_ARG_ENABLE(rust, AS_HELP_STRING([--enable-rust], [Enable rust language features (default: no)]),
++              [], [ enable_rust=no ])
++AC_MSG_RESULT($enable_rust)
++if test "$enable_rust" = yes -o "$enable_rust_offline" = yes; then
++    AC_CHECK_PROG(CARGO, [cargo], [yes], [no])
++    AC_CHECK_PROG(RUSTC, [rustc], [yes], [no])
++    # Since fernet uses the openssl lib.
++    PKG_CHECK_MODULES([OPENSSL], [openssl])
++
++    AS_IF([test "$CARGO" != "yes" -o "$RUSTC" != "yes"], [
++      AC_MSG_FAILURE("Rust based plugins cannot be built cargo=$CARGO rustc=$RUSTC")
++    ])
++fi
++AC_SUBST([enable_rust])
++AM_CONDITIONAL([RUST_ENABLE],[test "$enable_rust" = yes -o "$enable_rust_offline" = yes])
++
+ AC_MSG_CHECKING(for --enable-debug)
+ AC_ARG_ENABLE(debug, AS_HELP_STRING([--enable-debug], [Enable debug features (default: no)]),
+ [
+   AC_MSG_RESULT(yes)
+   debug_defs="-g3 -DDEBUG -DMCC_DEBUG -O0"
++  debug_rust_defs="-C debuginfo=2 -Z macro-backtrace"
++  cargo_defs=""
++  rust_target_dir="debug"
+ ],
+ [
+   AC_MSG_RESULT(no)
+   debug_defs=""
++  debug_rust_defs="-C debuginfo=2"
++  cargo_defs="--release"
++  rust_target_dir="release"
+ ])
+ AC_SUBST([debug_defs])
++AC_SUBST([debug_rust_defs])
++AC_SUBST([cargo_defs])
++AC_SUBST([rust_target_dir])
+ 
+ AC_MSG_CHECKING(for --enable-asan)
+ AC_ARG_ENABLE(asan, AS_HELP_STRING([--enable-asan], [Enable gcc address sanitizer options (default: no)]),
+ [
+   AC_MSG_RESULT(yes)
+   asan_defs="-fsanitize=address -fno-omit-frame-pointer"
++  asan_rust_defs="-Z sanitizer=address"
+ ],
+ [
+   AC_MSG_RESULT(no)
+   asan_defs=""
++  asan_rust_defs=""
+ ])
+ AC_SUBST([asan_defs])
++AC_SUBST([asan_rust_defs])
+ AM_CONDITIONAL(enable_asan,test "$enable_asan" = "yes")
+ 
+ if test -z "$enable_perl" ; then
+@@ -798,5 +839,7 @@ AC_CONFIG_FILES([src/pkgconfig/dirsrv.pc src/pkgconfig/nunc-stans.pc src/pkgconf
+ 
+ AC_CONFIG_FILES([Makefile rpm/389-ds-base.spec  ])
+ 
++AC_CONFIG_FILES([.cargo/config])
++
+ AC_OUTPUT
+ 
+diff --git a/ldap/ldif/template-dse-minimal.ldif.in b/ldap/ldif/template-dse-minimal.ldif.in
+index 0be9c171e..e881e6b81 100644
+--- a/ldap/ldif/template-dse-minimal.ldif.in
++++ b/ldap/ldif/template-dse-minimal.ldif.in
+@@ -176,6 +176,58 @@ nsslapd-plugininitfunc: pbkdf2_sha256_pwd_storage_scheme_init
+ nsslapd-plugintype: pwdstoragescheme
+ nsslapd-pluginenabled: on
+ 
++dn: cn=PBKDF2,cn=Password Storage Schemes,cn=plugins,cn=config
++objectclass: top
++objectclass: nsSlapdPlugin
++cn: PBKDF2
++nsslapd-pluginpath: libpwdchan-plugin
++nsslapd-plugininitfunc: pwdchan_pbkdf2_plugin_init
++nsslapd-plugintype: pwdstoragescheme
++nsslapd-pluginenabled: on
++nsslapd-pluginId: PBKDF2
++nsslapd-pluginVersion: none
++nsslapd-pluginVendor: 389 Project
++nsslapd-pluginDescription: PBKDF2
++
++dn: cn=PBKDF2-SHA1,cn=Password Storage Schemes,cn=plugins,cn=config
++objectclass: top
++objectclass: nsSlapdPlugin
++cn: PBKDF2-SHA1
++nsslapd-pluginpath: libpwdchan-plugin
++nsslapd-plugininitfunc: pwdchan_pbkdf2_sha1_plugin_init
++nsslapd-plugintype: pwdstoragescheme
++nsslapd-pluginenabled: on
++nsslapd-pluginId: PBKDF2-SHA1
++nsslapd-pluginVersion: none
++nsslapd-pluginVendor: 389 Project
++nsslapd-pluginDescription: PBKDF2-SHA1\
++
++dn: cn=PBKDF2-SHA256,cn=Password Storage Schemes,cn=plugins,cn=config
++objectclass: top
++objectclass: nsSlapdPlugin
++cn: PBKDF2-SHA256
++nsslapd-pluginpath: libpwdchan-plugin
++nsslapd-plugininitfunc: pwdchan_pbkdf2_sha256_plugin_init
++nsslapd-plugintype: pwdstoragescheme
++nsslapd-pluginenabled: on
++nsslapd-pluginId: PBKDF2-SHA256
++nsslapd-pluginVersion: none
++nsslapd-pluginVendor: 389 Project
++nsslapd-pluginDescription: PBKDF2-SHA256\
++
++dn: cn=PBKDF2-SHA512,cn=Password Storage Schemes,cn=plugins,cn=config
++objectclass: top
++objectclass: nsSlapdPlugin
++cn: PBKDF2-SHA512
++nsslapd-pluginpath: libpwdchan-plugin
++nsslapd-plugininitfunc: pwdchan_pbkdf2_sha512_plugin_init
++nsslapd-plugintype: pwdstoragescheme
++nsslapd-pluginenabled: on
++nsslapd-pluginId: PBKDF2-SHA512
++nsslapd-pluginVersion: none
++nsslapd-pluginVendor: 389 Project
++nsslapd-pluginDescription: PBKDF2-SHA512
++
+ dn: cn=AES,cn=Password Storage Schemes,cn=plugins,cn=config
+ objectclass: top
+ objectclass: nsSlapdPlugin
+diff --git a/ldap/ldif/template-dse.ldif.in b/ldap/ldif/template-dse.ldif.in
+index bffdd300f..0ee769fd6 100644
+--- a/ldap/ldif/template-dse.ldif.in
++++ b/ldap/ldif/template-dse.ldif.in
+@@ -208,6 +208,58 @@ nsslapd-plugininitfunc: pbkdf2_sha256_pwd_storage_scheme_init
+ nsslapd-plugintype: pwdstoragescheme
+ nsslapd-pluginenabled: on
+ 
++dn: cn=PBKDF2,cn=Password Storage Schemes,cn=plugins,cn=config
++objectclass: top
++objectclass: nsSlapdPlugin
++cn: PBKDF2
++nsslapd-pluginpath: libpwdchan-plugin
++nsslapd-plugininitfunc: pwdchan_pbkdf2_plugin_init
++nsslapd-plugintype: pwdstoragescheme
++nsslapd-pluginenabled: on
++nsslapd-pluginId: PBKDF2
++nsslapd-pluginVersion: none
++nsslapd-pluginVendor: 389 Project
++nsslapd-pluginDescription: PBKDF2
++
++dn: cn=PBKDF2-SHA1,cn=Password Storage Schemes,cn=plugins,cn=config
++objectclass: top
++objectclass: nsSlapdPlugin
++cn: PBKDF2-SHA1
++nsslapd-pluginpath: libpwdchan-plugin
++nsslapd-plugininitfunc: pwdchan_pbkdf2_sha1_plugin_init
++nsslapd-plugintype: pwdstoragescheme
++nsslapd-pluginenabled: on
++nsslapd-pluginId: PBKDF2-SHA1
++nsslapd-pluginVersion: none
++nsslapd-pluginVendor: 389 Project
++nsslapd-pluginDescription: PBKDF2-SHA1\
++
++dn: cn=PBKDF2-SHA256,cn=Password Storage Schemes,cn=plugins,cn=config
++objectclass: top
++objectclass: nsSlapdPlugin
++cn: PBKDF2-SHA256
++nsslapd-pluginpath: libpwdchan-plugin
++nsslapd-plugininitfunc: pwdchan_pbkdf2_sha256_plugin_init
++nsslapd-plugintype: pwdstoragescheme
++nsslapd-pluginenabled: on
++nsslapd-pluginId: PBKDF2-SHA256
++nsslapd-pluginVersion: none
++nsslapd-pluginVendor: 389 Project
++nsslapd-pluginDescription: PBKDF2-SHA256\
++
++dn: cn=PBKDF2-SHA512,cn=Password Storage Schemes,cn=plugins,cn=config
++objectclass: top
++objectclass: nsSlapdPlugin
++cn: PBKDF2-SHA512
++nsslapd-pluginpath: libpwdchan-plugin
++nsslapd-plugininitfunc: pwdchan_pbkdf2_sha512_plugin_init
++nsslapd-plugintype: pwdstoragescheme
++nsslapd-pluginenabled: on
++nsslapd-pluginId: PBKDF2-SHA512
++nsslapd-pluginVersion: none
++nsslapd-pluginVendor: 389 Project
++nsslapd-pluginDescription: PBKDF2-SHA512
++
+ dn: cn=AES,cn=Password Storage Schemes,cn=plugins,cn=config
+ objectclass: top
+ objectclass: nsSlapdPlugin
+diff --git a/ldap/servers/slapd/entry.c b/ldap/servers/slapd/entry.c
+index b85e9f5b0..1ce0310c3 100644
+--- a/ldap/servers/slapd/entry.c
++++ b/ldap/servers/slapd/entry.c
+@@ -2882,6 +2882,18 @@ slapi_entry_attr_get_bool(const Slapi_Entry *e, const char *type)
+     return slapi_entry_attr_get_bool_ext(e, type, PR_FALSE);
+ }
+ 
++const struct slapi_value **
++slapi_entry_attr_get_valuearray(const Slapi_Entry *e, const char *attrname)
++{
++    Slapi_Attr *attr;
++
++    if (slapi_entry_attr_find(e, attrname, &attr) != 0) {
++        return NULL;
++    }
++
++    return attr->a_present_values.va;
++}
++
+ void
+ slapi_entry_attr_set_charptr(Slapi_Entry *e, const char *type, const char *value)
+ {
+diff --git a/ldap/servers/slapd/fedse.c b/ldap/servers/slapd/fedse.c
+index f225c402b..b25c5017a 100644
+--- a/ldap/servers/slapd/fedse.c
++++ b/ldap/servers/slapd/fedse.c
+@@ -112,7 +112,107 @@ static const char *internal_entries[] =
+         "objectclass:top\n"
+         "objectclass:nsSNMP\n"
+         "cn:SNMP\n"
+-        "nsSNMPEnabled: on\n"};
++        "nsSNMPEnabled: on\n",
++
++        "dn: cn=Password Storage Schemes,cn=plugins,cn=config\n"
++        "objectclass: top\n"
++        "objectclass: nsContainer\n"
++        "cn: Password Storage Schemes\n",
++
++        "dn: cn=PBKDF2_SHA256,cn=Password Storage Schemes,cn=plugins,cn=config\n"
++        "objectclass: top\n"
++        "objectclass: nsSlapdPlugin\n"
++        "cn: PBKDF2_SHA256\n"
++        "nsslapd-pluginpath: libpwdstorage-plugin\n"
++        "nsslapd-plugininitfunc: pbkdf2_sha256_pwd_storage_scheme_init\n"
++        "nsslapd-plugintype: pwdstoragescheme\n"
++        "nsslapd-pluginenabled: on\n"
++        "nsslapd-pluginId: PBKDF2_SHA256\n"
++        "nsslapd-pluginVersion: none\n"
++        "nsslapd-pluginVendor: 389 Project\n"
++        "nsslapd-pluginDescription: PBKDF2_SHA256\n",
++
++        "dn: cn=CRYPT-MD5,cn=Password Storage Schemes,cn=plugins,cn=config\n"
++        "objectClass: top\n"
++        "objectClass: nsSlapdPlugin\n"
++        "cn: CRYPT-MD5\n"
++        "nsslapd-pluginPath: libpwdstorage-plugin\n"
++        "nsslapd-pluginInitfunc: crypt_md5_pwd_storage_scheme_init\n"
++        "nsslapd-pluginType: pwdstoragescheme\n"
++        "nsslapd-pluginEnabled: on\n"
++        "nsslapd-pluginId: CRYPT-MD5\n"
++        "nsslapd-pluginVersion: none\n"
++        "nsslapd-pluginVendor: 389 Project\n"
++        "nsslapd-pluginDescription: CRYPT-MD5\n",
++
++        "dn: cn=CRYPT-SHA256,cn=Password Storage Schemes,cn=plugins,cn=config\n"
++        "objectClass: top\n"
++        "objectClass: nsSlapdPlugin\n"
++        "cn: CRYPT-SHA256\n"
++        "nsslapd-pluginPath: libpwdstorage-plugin\n"
++        "nsslapd-pluginInitfunc: crypt_sha256_pwd_storage_scheme_init\n"
++        "nsslapd-pluginType: pwdstoragescheme\n"
++        "nsslapd-pluginEnabled: on\n"
++        "nsslapd-pluginId: CRYPT-SHA256\n"
++        "nsslapd-pluginVersion: none\n"
++        "nsslapd-pluginVendor: 389 Project\n"
++        "nsslapd-pluginDescription: CRYPT-SHA256\n",
++
++        "dn: cn=CRYPT-SHA512,cn=Password Storage Schemes,cn=plugins,cn=config\n"
++        "objectClass: top\n"
++        "objectClass: nsSlapdPlugin\n"
++        "cn: CRYPT-SHA512\n"
++        "nsslapd-pluginPath: libpwdstorage-plugin\n"
++        "nsslapd-pluginInitfunc: crypt_sha512_pwd_storage_scheme_init\n"
++        "nsslapd-pluginType: pwdstoragescheme\n"
++        "nsslapd-pluginEnabled: on\n"
++        "nsslapd-pluginId: CRYPT-SHA512\n"
++        "nsslapd-pluginVersion: none\n"
++        "nsslapd-pluginVendor: 389 Project\n"
++        "nsslapd-pluginDescription: CRYPT-SHA512\n",
++
++#ifdef RUST_ENABLE
++        "dn: cn=PBKDF2,cn=Password Storage Schemes,cn=plugins,cn=config\n"
++        "objectclass: top\n"
++        "objectclass: nsSlapdPlugin\n"
++        "cn: PBKDF2\n"
++        "nsslapd-pluginpath: libpwdchan-plugin\n"
++        "nsslapd-plugininitfunc: pwdchan_pbkdf2_plugin_init\n"
++        "nsslapd-plugintype: pwdstoragescheme\n"
++        "nsslapd-pluginenabled: on\n"
++        "nsslapd-pluginId: PBKDF2\n"
++        "nsslapd-pluginVersion: none\n"
++        "nsslapd-pluginVendor: 389 Project\n"
++        "nsslapd-pluginDescription: PBKDF2\n",
++
++        "dn: cn=PBKDF2-SHA1,cn=Password Storage Schemes,cn=plugins,cn=config\n"
++        "objectclass: top\n"
++        "objectclass: nsSlapdPlugin\n"
++        "cn: PBKDF2-SHA1\n"
++        "nsslapd-pluginpath: libpwdchan-plugin\n"
++        "nsslapd-plugininitfunc: pwdchan_pbkdf2_sha1_plugin_init\n"
++        "nsslapd-plugintype: pwdstoragescheme\n"
++        "nsslapd-pluginenabled: on\n"
++        "nsslapd-pluginId: PBKDF2-SHA1\n"
++        "nsslapd-pluginVersion: none\n"
++        "nsslapd-pluginVendor: 389 Project\n"
++        "nsslapd-pluginDescription: PBKDF2-SHA1\n",
++
++        "dn: cn=PBKDF2-SHA256,cn=Password Storage Schemes,cn=plugins,cn=config\n"
++        "objectclass: top\n"
++        "objectclass: nsSlapdPlugin\n"
++        "cn: PBKDF2-SHA256\n"
++        "nsslapd-pluginpath: libpwdchan-plugin\n"
++        "nsslapd-plugininitfunc: pwdchan_pbkdf2_sha256_plugin_init\n"
++        "nsslapd-plugintype: pwdstoragescheme\n"
++        "nsslapd-pluginenabled: on\n"
++        "nsslapd-pluginId: PBKDF2-SHA256\n"
++        "nsslapd-pluginVersion: none\n"
++        "nsslapd-pluginVendor: 389 Project\n"
++        "nsslapd-pluginDescription: PBKDF2-SHA256\n",
++#endif
++};
++
+ 
+ static int NUM_INTERNAL_ENTRIES = sizeof(internal_entries) / sizeof(internal_entries[0]);
+ 
+diff --git a/ldap/servers/slapd/libglobs.c b/ldap/servers/slapd/libglobs.c
+index 89f7e5344..e6fd70fb3 100644
+--- a/ldap/servers/slapd/libglobs.c
++++ b/ldap/servers/slapd/libglobs.c
+@@ -133,6 +133,10 @@
+ #endif
+ #include <sys/resource.h>
+ 
++#ifdef RUST_ENABLE
++#include <rust-slapi-private.h>
++#endif
++
+ #define REMOVE_CHANGELOG_CMD "remove"
+ 
+ int slapd_ldap_debug = SLAPD_DEFAULT_ERRORLOG_LEVEL;
+@@ -1475,6 +1479,11 @@ FrontendConfig_init(void)
+     struct rlimit rlp;
+     int64_t maxdescriptors = SLAPD_DEFAULT_MAXDESCRIPTORS;
+ 
++#ifdef RUST_ENABLE
++    /* prove rust is working */
++    PR_ASSERT(do_nothing_rust() == 0);
++#endif
++
+ #if SLAPI_CFG_USE_RWLOCK == 1
+     /* initialize the read/write configuration lock */
+     if ((cfg->cfg_rwlock = slapi_new_rwlock()) == NULL) {
+diff --git a/rpm.mk b/rpm.mk
+index 378a46960..b0c0f15cc 100644
+--- a/rpm.mk
++++ b/rpm.mk
+@@ -9,11 +9,27 @@ RPM_NAME_VERSION = $(PACKAGE)-$(RPM_VERSION)$(RPM_VERSION_PREREL)
+ NAME_VERSION = $(PACKAGE)-$(RPM_VERSION)$(VERSION_PREREL)
+ TARBALL = $(NAME_VERSION).tar.bz2
+ NUNC_STANS_ON = 1
++GIT_TAG = ${TAG}
+ ASAN_ON = 0
++RUST_ON = 1
+ 
+ clean:
+ 	rm -rf dist
+ 	rm -rf rpmbuild
++	rm -rf vendor
++	rm -f vendor.tar.gz
++
++update-cargo-dependencies:
++	cargo update --manifest-path=./src/Cargo.toml
++
++download-cargo-dependencies:
++	cargo update --manifest-path=./src/Cargo.toml
++	cargo vendor --manifest-path=./src/Cargo.toml
++	cargo fetch --manifest-path=./src/Cargo.toml
++	tar -czf vendor.tar.gz vendor
++
++dist-bz2: download-cargo-dependencies
++	tar cjf $(GIT_TAG).tar.bz2 --transform "s,^,$(GIT_TAG)/," $$(git ls-files) vendor/
+ 
+ local-archive:
+ 	-mkdir -p dist/$(NAME_VERSION)
+@@ -36,6 +52,7 @@ rpmroot:
+ 	-e s/__VERSION_PREREL__/$(VERSION_PREREL)/ \
+ 	-e s/__NUNC_STANS_ON__/$(NUNC_STANS_ON)/ \
+ 	-e s/__ASAN_ON__/$(ASAN_ON)/ \
++	-e s/__RUST_ON__/$(RUST_ON)/ \
+ 	rpm/$(PACKAGE).spec.in > $(RPMBUILD)/SPECS/$(PACKAGE).spec
+ 
+ rpmdistdir:
+@@ -49,7 +66,7 @@ rpmbuildprep:
+ 	cp rpm/$(PACKAGE)-* $(RPMBUILD)/SOURCES/
+ 
+ 
+-srpms: rpmroot srpmdistdir tarballs rpmbuildprep
++srpms: rpmroot srpmdistdir download-cargo-dependencies tarballs rpmbuildprep
+ 	rpmbuild --define "_topdir $(RPMBUILD)" -bs $(RPMBUILD)/SPECS/$(PACKAGE).spec
+ 	cp $(RPMBUILD)/SRPMS/$(RPM_NAME_VERSION)*.src.rpm dist/srpms/
+ 	rm -rf $(RPMBUILD)
+diff --git a/rpm/389-ds-base.spec.in b/rpm/389-ds-base.spec.in
+index 4e2ffb3f4..d2f32b29c 100644
+--- a/rpm/389-ds-base.spec.in
++++ b/rpm/389-ds-base.spec.in
+@@ -24,6 +24,8 @@
+ %else
+ %global use_tcmalloc 0
+ %endif
++# This enables rust in the build.
++%global use_rust __RUST_ON__
+ %endif
+ 
+ # fedora 15 and later uses tmpfiles.d
+@@ -75,6 +77,11 @@ BuildRequires:    systemd-devel
+ %if %{use_asan}
+ BuildRequires:    libasan
+ %endif
++# If rust is enabled
++%if %{use_rust}
++BuildRequires:    cargo
++BuildRequires:    rust
++%endif
+ # Needed to support regeneration of the autotool artifacts.
+ BuildRequires:    autoconf
+ BuildRequires:    automake
+@@ -257,6 +264,9 @@ ASAN_FLAGS="--enable-asan --enable-debug"
+ %if %{use_tcmalloc}
+ TCMALLOC_FLAGS="--enable-tcmalloc"
+ %endif
++%if %{use_rust}
++RUST_FLAGS="--enable-rust --enable-rust-offline"
++%endif
+ 
+ # Rebuild the autotool artifacts now.
+ autoreconf -fiv
+@@ -266,7 +276,7 @@ autoreconf -fiv
+            --with-systemdsystemunitdir=%{_unitdir} \
+            --with-systemdsystemconfdir=%{_sysconfdir}/systemd/system \
+            --with-systemdgroupname=%{groupname} \
+-           $NSSARGS $TCMALLOC_FLAGS $ASAN_FLAGS \
++           $NSSARGS $TCMALLOC_FLAGS $ASAN_FLAGS $RUST_FLAGS \
+            --enable-cmocka
+ 
+ make setup.py
+@@ -362,6 +372,7 @@ sysctl --system &> $output; true
+ 
+ echo looking for instances in %{_sysconfdir}/%{pkgname} > $output 2>&1 || :
+ instbase="%{_sysconfdir}/%{pkgname}"
++ninst=0
+ for dir in $instbase/slapd-* ; do
+     echo dir = $dir >> $output 2>&1 || :
+     if [ ! -d "$dir" ] ; then continue ; fi
+@@ -491,7 +502,7 @@ fi
+ %{_libdir}/%{pkgname}/python
+ %dir %{_libdir}/%{pkgname}/plugins
+ %{_libdir}/%{pkgname}/plugins/*.so
+-# THis has to be hardcoded to /lib - $libdir changes between lib/lib64, but
++# This has to be hardcoded to /lib - $libdir changes between lib/lib64, but
+ # sysctl.d is always in /lib.
+ %{_prefix}/lib/sysctl.d/*
+ %dir %{_localstatedir}/lib/%{pkgname}
+diff --git a/src/Cargo.lock b/src/Cargo.lock
+new file mode 100644
+index 000000000..64bfc7e37
+--- /dev/null
++++ b/src/Cargo.lock
+@@ -0,0 +1,833 @@
++# This file is automatically @generated by Cargo.
++# It is not intended for manual editing.
++version = 3
++
++[[package]]
++name = "ahash"
++version = "0.7.6"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
++dependencies = [
++ "getrandom",
++ "once_cell",
++ "version_check",
++]
++
++[[package]]
++name = "ansi_term"
++version = "0.12.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
++dependencies = [
++ "winapi",
++]
++
++[[package]]
++name = "atty"
++version = "0.2.14"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
++dependencies = [
++ "hermit-abi",
++ "libc",
++ "winapi",
++]
++
++[[package]]
++name = "autocfg"
++version = "1.1.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
++
++[[package]]
++name = "base64"
++version = "0.13.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
++
++[[package]]
++name = "bitflags"
++version = "1.3.2"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
++
++[[package]]
++name = "byteorder"
++version = "1.4.3"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
++
++[[package]]
++name = "cbindgen"
++version = "0.9.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "9daec6140ab4dcd38c3dd57e580b59a621172a526ac79f1527af760a55afeafd"
++dependencies = [
++ "clap",
++ "log",
++ "proc-macro2",
++ "quote",
++ "serde",
++ "serde_json",
++ "syn",
++ "tempfile",
++ "toml",
++]
++
++[[package]]
++name = "cc"
++version = "1.0.78"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
++dependencies = [
++ "jobserver",
++]
++
++[[package]]
++name = "cfg-if"
++version = "1.0.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
++
++[[package]]
++name = "clap"
++version = "2.34.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
++dependencies = [
++ "ansi_term",
++ "atty",
++ "bitflags",
++ "strsim",
++ "textwrap",
++ "unicode-width",
++ "vec_map",
++]
++
++[[package]]
++name = "concread"
++version = "0.2.21"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "dcc9816f5ac93ebd51c37f7f9a6bf2b40dfcd42978ad2aea5d542016e9244cf6"
++dependencies = [
++ "ahash",
++ "crossbeam",
++ "crossbeam-epoch",
++ "crossbeam-utils",
++ "lru",
++ "parking_lot",
++ "rand",
++ "smallvec",
++ "tokio",
++]
++
++[[package]]
++name = "crossbeam"
++version = "0.8.2"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c"
++dependencies = [
++ "cfg-if",
++ "crossbeam-channel",
++ "crossbeam-deque",
++ "crossbeam-epoch",
++ "crossbeam-queue",
++ "crossbeam-utils",
++]
++
++[[package]]
++name = "crossbeam-channel"
++version = "0.5.6"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
++dependencies = [
++ "cfg-if",
++ "crossbeam-utils",
++]
++
++[[package]]
++name = "crossbeam-deque"
++version = "0.8.2"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
++dependencies = [
++ "cfg-if",
++ "crossbeam-epoch",
++ "crossbeam-utils",
++]
++
++[[package]]
++name = "crossbeam-epoch"
++version = "0.9.13"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
++dependencies = [
++ "autocfg",
++ "cfg-if",
++ "crossbeam-utils",
++ "memoffset",
++ "scopeguard",
++]
++
++[[package]]
++name = "crossbeam-queue"
++version = "0.3.8"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
++dependencies = [
++ "cfg-if",
++ "crossbeam-utils",
++]
++
++[[package]]
++name = "crossbeam-utils"
++version = "0.8.14"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
++dependencies = [
++ "cfg-if",
++]
++
++[[package]]
++name = "fastrand"
++version = "1.8.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499"
++dependencies = [
++ "instant",
++]
++
++[[package]]
++name = "fernet"
++version = "0.1.4"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "93804560e638370a8be6d59ce71ed803e55e230abdbf42598e666b41adda9b1f"
++dependencies = [
++ "base64",
++ "byteorder",
++ "getrandom",
++ "openssl",
++ "zeroize",
++]
++
++[[package]]
++name = "foreign-types"
++version = "0.3.2"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
++dependencies = [
++ "foreign-types-shared",
++]
++
++[[package]]
++name = "foreign-types-shared"
++version = "0.1.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
++
++[[package]]
++name = "getrandom"
++version = "0.2.8"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
++dependencies = [
++ "cfg-if",
++ "libc",
++ "wasi",
++]
++
++[[package]]
++name = "hashbrown"
++version = "0.12.3"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
++dependencies = [
++ "ahash",
++]
++
++[[package]]
++name = "hermit-abi"
++version = "0.1.19"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
++dependencies = [
++ "libc",
++]
++
++[[package]]
++name = "instant"
++version = "0.1.12"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
++dependencies = [
++ "cfg-if",
++]
++
++[[package]]
++name = "itoa"
++version = "1.0.5"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
++
++[[package]]
++name = "jobserver"
++version = "0.1.25"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
++dependencies = [
++ "libc",
++]
++
++[[package]]
++name = "libc"
++version = "0.2.139"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
++
++[[package]]
++name = "librnsslapd"
++version = "0.1.0"
++dependencies = [
++ "cbindgen",
++ "libc",
++ "slapd",
++]
++
++[[package]]
++name = "librslapd"
++version = "0.1.0"
++dependencies = [
++ "cbindgen",
++ "concread",
++ "libc",
++ "slapd",
++]
++
++[[package]]
++name = "lock_api"
++version = "0.4.9"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
++dependencies = [
++ "autocfg",
++ "scopeguard",
++]
++
++[[package]]
++name = "log"
++version = "0.4.17"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
++dependencies = [
++ "cfg-if",
++]
++
++[[package]]
++name = "lru"
++version = "0.7.8"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
++dependencies = [
++ "hashbrown",
++]
++
++[[package]]
++name = "memoffset"
++version = "0.7.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
++dependencies = [
++ "autocfg",
++]
++
++[[package]]
++name = "once_cell"
++version = "1.17.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
++
++[[package]]
++name = "openssl"
++version = "0.10.45"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
++dependencies = [
++ "bitflags",
++ "cfg-if",
++ "foreign-types",
++ "libc",
++ "once_cell",
++ "openssl-macros",
++ "openssl-sys",
++]
++
++[[package]]
++name = "openssl-macros"
++version = "0.1.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
++dependencies = [
++ "proc-macro2",
++ "quote",
++ "syn",
++]
++
++[[package]]
++name = "openssl-sys"
++version = "0.9.80"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
++dependencies = [
++ "autocfg",
++ "cc",
++ "libc",
++ "pkg-config",
++ "vcpkg",
++]
++
++[[package]]
++name = "parking_lot"
++version = "0.11.2"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
++dependencies = [
++ "instant",
++ "lock_api",
++ "parking_lot_core",
++]
++
++[[package]]
++name = "parking_lot_core"
++version = "0.8.6"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
++dependencies = [
++ "cfg-if",
++ "instant",
++ "libc",
++ "redox_syscall",
++ "smallvec",
++ "winapi",
++]
++
++[[package]]
++name = "paste"
++version = "0.1.18"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
++dependencies = [
++ "paste-impl",
++ "proc-macro-hack",
++]
++
++[[package]]
++name = "paste-impl"
++version = "0.1.18"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
++dependencies = [
++ "proc-macro-hack",
++]
++
++[[package]]
++name = "pin-project-lite"
++version = "0.2.9"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
++
++[[package]]
++name = "pkg-config"
++version = "0.3.26"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
++
++[[package]]
++name = "ppv-lite86"
++version = "0.2.17"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
++
++[[package]]
++name = "proc-macro-hack"
++version = "0.5.20+deprecated"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
++
++[[package]]
++name = "proc-macro2"
++version = "1.0.50"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
++dependencies = [
++ "unicode-ident",
++]
++
++[[package]]
++name = "pwdchan"
++version = "0.1.0"
++dependencies = [
++ "base64",
++ "cc",
++ "libc",
++ "openssl",
++ "paste",
++ "slapi_r_plugin",
++ "uuid",
++]
++
++[[package]]
++name = "quote"
++version = "1.0.23"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
++dependencies = [
++ "proc-macro2",
++]
++
++[[package]]
++name = "rand"
++version = "0.8.5"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
++dependencies = [
++ "libc",
++ "rand_chacha",
++ "rand_core",
++]
++
++[[package]]
++name = "rand_chacha"
++version = "0.3.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
++dependencies = [
++ "ppv-lite86",
++ "rand_core",
++]
++
++[[package]]
++name = "rand_core"
++version = "0.6.4"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
++dependencies = [
++ "getrandom",
++]
++
++[[package]]
++name = "redox_syscall"
++version = "0.2.16"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
++dependencies = [
++ "bitflags",
++]
++
++[[package]]
++name = "remove_dir_all"
++version = "0.5.3"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
++dependencies = [
++ "winapi",
++]
++
++[[package]]
++name = "ryu"
++version = "1.0.12"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
++
++[[package]]
++name = "scopeguard"
++version = "1.1.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
++
++[[package]]
++name = "serde"
++version = "1.0.152"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
++dependencies = [
++ "serde_derive",
++]
++
++[[package]]
++name = "serde_derive"
++version = "1.0.152"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
++dependencies = [
++ "proc-macro2",
++ "quote",
++ "syn",
++]
++
++[[package]]
++name = "serde_json"
++version = "1.0.91"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883"
++dependencies = [
++ "itoa",
++ "ryu",
++ "serde",
++]
++
++[[package]]
++name = "slapd"
++version = "0.1.0"
++dependencies = [
++ "fernet",
++]
++
++[[package]]
++name = "slapi_r_plugin"
++version = "0.1.0"
++dependencies = [
++ "libc",
++ "paste",
++ "uuid",
++]
++
++[[package]]
++name = "smallvec"
++version = "1.10.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
++
++[[package]]
++name = "strsim"
++version = "0.8.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
++
++[[package]]
++name = "syn"
++version = "1.0.107"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
++dependencies = [
++ "proc-macro2",
++ "quote",
++ "unicode-ident",
++]
++
++[[package]]
++name = "synstructure"
++version = "0.12.6"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
++dependencies = [
++ "proc-macro2",
++ "quote",
++ "syn",
++ "unicode-xid",
++]
++
++[[package]]
++name = "tempfile"
++version = "3.3.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
++dependencies = [
++ "cfg-if",
++ "fastrand",
++ "libc",
++ "redox_syscall",
++ "remove_dir_all",
++ "winapi",
++]
++
++[[package]]
++name = "textwrap"
++version = "0.11.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
++dependencies = [
++ "unicode-width",
++]
++
++[[package]]
++name = "tokio"
++version = "1.24.2"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "597a12a59981d9e3c38d216785b0c37399f6e415e8d0712047620f189371b0bb"
++dependencies = [
++ "autocfg",
++ "pin-project-lite",
++ "tokio-macros",
++ "windows-sys",
++]
++
++[[package]]
++name = "tokio-macros"
++version = "1.8.2"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
++dependencies = [
++ "proc-macro2",
++ "quote",
++ "syn",
++]
++
++[[package]]
++name = "toml"
++version = "0.5.11"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
++dependencies = [
++ "serde",
++]
++
++[[package]]
++name = "unicode-ident"
++version = "1.0.6"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
++
++[[package]]
++name = "unicode-width"
++version = "0.1.10"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
++
++[[package]]
++name = "unicode-xid"
++version = "0.2.4"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
++
++[[package]]
++name = "uuid"
++version = "0.8.2"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
++dependencies = [
++ "getrandom",
++]
++
++[[package]]
++name = "vcpkg"
++version = "0.2.15"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
++
++[[package]]
++name = "vec_map"
++version = "0.8.2"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
++
++[[package]]
++name = "version_check"
++version = "0.9.4"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
++
++[[package]]
++name = "wasi"
++version = "0.11.0+wasi-snapshot-preview1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
++
++[[package]]
++name = "winapi"
++version = "0.3.9"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
++dependencies = [
++ "winapi-i686-pc-windows-gnu",
++ "winapi-x86_64-pc-windows-gnu",
++]
++
++[[package]]
++name = "winapi-i686-pc-windows-gnu"
++version = "0.4.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
++
++[[package]]
++name = "winapi-x86_64-pc-windows-gnu"
++version = "0.4.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
++
++[[package]]
++name = "windows-sys"
++version = "0.42.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
++dependencies = [
++ "windows_aarch64_gnullvm",
++ "windows_aarch64_msvc",
++ "windows_i686_gnu",
++ "windows_i686_msvc",
++ "windows_x86_64_gnu",
++ "windows_x86_64_gnullvm",
++ "windows_x86_64_msvc",
++]
++
++[[package]]
++name = "windows_aarch64_gnullvm"
++version = "0.42.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
++
++[[package]]
++name = "windows_aarch64_msvc"
++version = "0.42.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
++
++[[package]]
++name = "windows_i686_gnu"
++version = "0.42.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
++
++[[package]]
++name = "windows_i686_msvc"
++version = "0.42.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
++
++[[package]]
++name = "windows_x86_64_gnu"
++version = "0.42.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
++
++[[package]]
++name = "windows_x86_64_gnullvm"
++version = "0.42.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
++
++[[package]]
++name = "windows_x86_64_msvc"
++version = "0.42.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
++
++[[package]]
++name = "zeroize"
++version = "1.5.7"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
++dependencies = [
++ "zeroize_derive",
++]
++
++[[package]]
++name = "zeroize_derive"
++version = "1.3.3"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
++dependencies = [
++ "proc-macro2",
++ "quote",
++ "syn",
++ "synstructure",
++]
+diff --git a/src/Cargo.toml b/src/Cargo.toml
+new file mode 100644
+index 000000000..4c918422e
+--- /dev/null
++++ b/src/Cargo.toml
+@@ -0,0 +1,15 @@
++
++[workspace]
++members = [
++    "librslapd",
++    "librnsslapd",
++    "slapd",
++    "slapi_r_plugin",
++    "plugins/pwdchan",
++]
++
++[profile.release]
++panic = "abort"
++lto = true
++
++
+diff --git a/src/librnsslapd/Cargo.toml b/src/librnsslapd/Cargo.toml
+new file mode 100644
+index 000000000..11bb9afe7
+--- /dev/null
++++ b/src/librnsslapd/Cargo.toml
+@@ -0,0 +1,25 @@
++[package]
++name = "librnsslapd"
++version = "0.1.0"
++authors = ["William Brown <william@blackhats.net.au>"]
++edition = "2018"
++build = "build.rs"
++
++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
++
++[lib]
++path = "src/lib.rs"
++name = "rnsslapd"
++crate-type = ["staticlib", "lib"]
++
++# [profile.release]
++# panic = "abort"
++# lto = true
++
++[dependencies]
++slapd = { path = "../slapd" }
++libc = "0.2"
++
++[build-dependencies]
++cbindgen = "0.9"
++
+diff --git a/src/librnsslapd/README.md b/src/librnsslapd/README.md
+new file mode 100644
+index 000000000..83b1eef90
+--- /dev/null
++++ b/src/librnsslapd/README.md
+@@ -0,0 +1,4 @@
++This is the librnsslapd wrapper - it's a rust -> c bindgen stub. It does
++not provide any logic, but exists to resolve linking issues that
++exist between autotools and rust. For all the Rust logic, see ../slapd.
++
+diff --git a/src/librnsslapd/build.rs b/src/librnsslapd/build.rs
+new file mode 100644
+index 000000000..13f6d2e03
+--- /dev/null
++++ b/src/librnsslapd/build.rs
+@@ -0,0 +1,16 @@
++extern crate cbindgen;
++
++use std::env;
++
++fn main() {
++    if let Ok(crate_dir) = env::var("CARGO_MANIFEST_DIR") {
++        if let Ok(out_dir) = env::var("SLAPD_HEADER_DIR") {
++            cbindgen::Builder::new()
++                .with_language(cbindgen::Language::C)
++                .with_crate(crate_dir)
++                .generate()
++                .expect("Unable to generate bindings")
++                .write_to_file(format!("{}/rust-nsslapd-private.h", out_dir));
++        }
++    }
++}
+diff --git a/src/librnsslapd/src/lib.rs b/src/librnsslapd/src/lib.rs
+new file mode 100644
+index 000000000..2085bb7ad
+--- /dev/null
++++ b/src/librnsslapd/src/lib.rs
+@@ -0,0 +1,68 @@
++#![deny(warnings)]
++// It's important that symbol names here are *unique* and do no conflict with symbol
++// names in ../../librslapd/src/lib.rs
++//
++// Remember this is just a c-bindgen stub, all logic should come from slapd!
++
++extern crate libc;
++use libc::c_char;
++use slapd;
++use std::ffi::CStr;
++
++#[no_mangle]
++pub extern "C" fn do_nothing_again_rust() -> usize {
++    0
++}
++
++#[no_mangle]
++pub extern "C" fn fernet_generate_token(dn: *const c_char, raw_key: *const c_char) -> *mut c_char {
++    if dn.is_null() || raw_key.is_null() {
++        return std::ptr::null_mut();
++    }
++    // Given a DN, generate a fernet token, or return NULL on error.
++    let c_str_key = unsafe { CStr::from_ptr(raw_key) };
++    let c_str_dn = unsafe { CStr::from_ptr(dn) };
++    match slapd::fernet::new(c_str_key) {
++        Ok(inst) => {
++            // We have an instance, let's make the token.
++            match slapd::fernet::encrypt(&inst, c_str_dn) {
++                Ok(tok) => {
++                    // We have to move string memory ownership by copying so the system
++                    // allocator has it.
++                    unsafe { libc::strdup(tok.as_c_str().as_ptr()) }
++                }
++                Err(_) => std::ptr::null_mut(),
++            }
++        }
++        Err(_) => std::ptr::null_mut(),
++    }
++}
++
++#[no_mangle]
++pub extern "C" fn fernet_verify_token(
++    dn: *const c_char,
++    token: *const c_char,
++    raw_key: *const c_char,
++    ttl: u64,
++) -> bool {
++    if dn.is_null() || raw_key.is_null() || token.is_null() {
++        return false;
++    }
++
++    let c_str_key = unsafe { CStr::from_ptr(raw_key) };
++    let c_str_dn = unsafe { CStr::from_ptr(dn) };
++    let c_str_token = unsafe { CStr::from_ptr(token) };
++
++    match slapd::fernet::new(c_str_key) {
++        Ok(inst) => {
++            match slapd::fernet::decrypt(&inst, c_str_token, ttl) {
++                Ok(val) => {
++                    // Finally check if the extracted dn is what we expect
++                    val.as_c_str() == c_str_dn
++                }
++                Err(_) => false,
++            }
++        }
++        Err(_) => false,
++    }
++}
+diff --git a/src/librslapd/Cargo.lock b/src/librslapd/Cargo.lock
+new file mode 100644
+index 000000000..eb8b6b70f
+--- /dev/null
++++ b/src/librslapd/Cargo.lock
+@@ -0,0 +1,331 @@
++# This file is automatically @generated by Cargo.
++# It is not intended for manual editing.
++[[package]]
++name = "ansi_term"
++version = "0.11.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
++dependencies = [
++ "winapi",
++]
++
++[[package]]
++name = "atty"
++version = "0.2.14"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
++dependencies = [
++ "hermit-abi",
++ "libc",
++ "winapi",
++]
++
++[[package]]
++name = "bitflags"
++version = "1.2.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
++
++[[package]]
++name = "cbindgen"
++version = "0.9.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "9daec6140ab4dcd38c3dd57e580b59a621172a526ac79f1527af760a55afeafd"
++dependencies = [
++ "clap",
++ "log",
++ "proc-macro2",
++ "quote",
++ "serde",
++ "serde_json",
++ "syn",
++ "tempfile",
++ "toml",
++]
++
++[[package]]
++name = "cfg-if"
++version = "0.1.10"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
++
++[[package]]
++name = "cfg-if"
++version = "1.0.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
++
++[[package]]
++name = "clap"
++version = "2.33.3"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
++dependencies = [
++ "ansi_term",
++ "atty",
++ "bitflags",
++ "strsim",
++ "textwrap",
++ "unicode-width",
++ "vec_map",
++]
++
++[[package]]
++name = "getrandom"
++version = "0.1.16"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
++dependencies = [
++ "cfg-if 1.0.0",
++ "libc",
++ "wasi",
++]
++
++[[package]]
++name = "hermit-abi"
++version = "0.1.17"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8"
++dependencies = [
++ "libc",
++]
++
++[[package]]
++name = "itoa"
++version = "0.4.7"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
++
++[[package]]
++name = "libc"
++version = "0.2.82"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
++
++[[package]]
++name = "librslapd"
++version = "0.1.0"
++dependencies = [
++ "cbindgen",
++]
++
++[[package]]
++name = "log"
++version = "0.4.13"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "fcf3805d4480bb5b86070dcfeb9e2cb2ebc148adb753c5cca5f884d1d65a42b2"
++dependencies = [
++ "cfg-if 0.1.10",
++]
++
++[[package]]
++name = "ppv-lite86"
++version = "0.2.10"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
++
++[[package]]
++name = "proc-macro2"
++version = "1.0.24"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71"
++dependencies = [
++ "unicode-xid",
++]
++
++[[package]]
++name = "quote"
++version = "1.0.8"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df"
++dependencies = [
++ "proc-macro2",
++]
++
++[[package]]
++name = "rand"
++version = "0.7.3"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
++dependencies = [
++ "getrandom",
++ "libc",
++ "rand_chacha",
++ "rand_core",
++ "rand_hc",
++]
++
++[[package]]
++name = "rand_chacha"
++version = "0.2.2"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
++dependencies = [
++ "ppv-lite86",
++ "rand_core",
++]
++
++[[package]]
++name = "rand_core"
++version = "0.5.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
++dependencies = [
++ "getrandom",
++]
++
++[[package]]
++name = "rand_hc"
++version = "0.2.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
++dependencies = [
++ "rand_core",
++]
++
++[[package]]
++name = "redox_syscall"
++version = "0.1.57"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
++
++[[package]]
++name = "remove_dir_all"
++version = "0.5.3"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
++dependencies = [
++ "winapi",
++]
++
++[[package]]
++name = "ryu"
++version = "1.0.5"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
++
++[[package]]
++name = "serde"
++version = "1.0.119"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "9bdd36f49e35b61d49efd8aa7fc068fd295961fd2286d0b2ee9a4c7a14e99cc3"
++dependencies = [
++ "serde_derive",
++]
++
++[[package]]
++name = "serde_derive"
++version = "1.0.119"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "552954ce79a059ddd5fd68c271592374bd15cab2274970380c000118aeffe1cd"
++dependencies = [
++ "proc-macro2",
++ "quote",
++ "syn",
++]
++
++[[package]]
++name = "serde_json"
++version = "1.0.61"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
++dependencies = [
++ "itoa",
++ "ryu",
++ "serde",
++]
++
++[[package]]
++name = "strsim"
++version = "0.8.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
++
++[[package]]
++name = "syn"
++version = "1.0.58"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5"
++dependencies = [
++ "proc-macro2",
++ "quote",
++ "unicode-xid",
++]
++
++[[package]]
++name = "tempfile"
++version = "3.1.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
++dependencies = [
++ "cfg-if 0.1.10",
++ "libc",
++ "rand",
++ "redox_syscall",
++ "remove_dir_all",
++ "winapi",
++]
++
++[[package]]
++name = "textwrap"
++version = "0.11.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
++dependencies = [
++ "unicode-width",
++]
++
++[[package]]
++name = "toml"
++version = "0.5.8"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa"
++dependencies = [
++ "serde",
++]
++
++[[package]]
++name = "unicode-width"
++version = "0.1.8"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
++
++[[package]]
++name = "unicode-xid"
++version = "0.2.1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564"
++
++[[package]]
++name = "vec_map"
++version = "0.8.2"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
++
++[[package]]
++name = "wasi"
++version = "0.9.0+wasi-snapshot-preview1"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
++
++[[package]]
++name = "winapi"
++version = "0.3.9"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
++dependencies = [
++ "winapi-i686-pc-windows-gnu",
++ "winapi-x86_64-pc-windows-gnu",
++]
++
++[[package]]
++name = "winapi-i686-pc-windows-gnu"
++version = "0.4.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
++
++[[package]]
++name = "winapi-x86_64-pc-windows-gnu"
++version = "0.4.0"
++source = "registry+https://github.com/rust-lang/crates.io-index"
++checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+diff --git a/src/librslapd/Cargo.toml b/src/librslapd/Cargo.toml
+new file mode 100644
+index 000000000..15c00a47b
+--- /dev/null
++++ b/src/librslapd/Cargo.toml
+@@ -0,0 +1,22 @@
++[package]
++name = "librslapd"
++version = "0.1.0"
++authors = ["William Brown <william@blackhats.net.au>"]
++edition = "2018"
++build = "build.rs"
++
++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
++
++[lib]
++path = "src/lib.rs"
++name = "rslapd"
++crate-type = ["staticlib", "lib"]
++
++[dependencies]
++slapd = { path = "../slapd" }
++libc = "0.2"
++concread =  "^0.2.20"
++
++[build-dependencies]
++cbindgen = "0.9"
++
+diff --git a/src/librslapd/README.md b/src/librslapd/README.md
+new file mode 100644
+index 000000000..b7fdbcaea
+--- /dev/null
++++ b/src/librslapd/README.md
+@@ -0,0 +1,3 @@
++This is the librslapd wrapper - it's a rust -> c bindgen stub. It does
++not provide any logic, but exists to resolve linking issues that
++exist between autotools and rust. For all the Rust logic, see ../slapd.
+diff --git a/src/librslapd/build.rs b/src/librslapd/build.rs
+new file mode 100644
+index 000000000..84aff156b
+--- /dev/null
++++ b/src/librslapd/build.rs
+@@ -0,0 +1,16 @@
++extern crate cbindgen;
++
++use std::env;
++
++fn main() {
++    if let Ok(crate_dir) = env::var("CARGO_MANIFEST_DIR") {
++        if let Ok(out_dir) = env::var("SLAPD_HEADER_DIR") {
++            cbindgen::Builder::new()
++                .with_language(cbindgen::Language::C)
++                .with_crate(crate_dir)
++                .generate()
++                .expect("Unable to generate bindings")
++                .write_to_file(format!("{}/rust-slapi-private.h", out_dir));
++        }
++    }
++}
+diff --git a/src/librslapd/src/cache.rs b/src/librslapd/src/cache.rs
+new file mode 100644
+index 000000000..092c81d83
+--- /dev/null
++++ b/src/librslapd/src/cache.rs
+@@ -0,0 +1,204 @@
++// This exposes C-FFI capable bindings for the concread concurrently readable cache.
++use concread::arcache::{ARCache, ARCacheBuilder, ARCacheReadTxn, ARCacheWriteTxn};
++use std::borrow::Borrow;
++use std::convert::TryInto;
++use std::ffi::{CStr, CString};
++use std::os::raw::c_char;
++
++pub struct ARCacheChar {
++    inner: ARCache<CString, CString>,
++}
++
++pub struct ARCacheCharRead<'a> {
++    inner: ARCacheReadTxn<'a, CString, CString>,
++}
++
++pub struct ARCacheCharWrite<'a> {
++    inner: ARCacheWriteTxn<'a, CString, CString>,
++}
++
++#[no_mangle]
++pub extern "C" fn cache_char_create(max: usize, read_max: usize) -> *mut ARCacheChar {
++    let inner = if let Some(cache) = ARCacheBuilder::new().set_size(max, read_max).build() {
++        cache
++    } else {
++        return std::ptr::null_mut();
++    };
++    let cache: Box<ARCacheChar> = Box::new(ARCacheChar { inner });
++    Box::into_raw(cache)
++}
++
++#[no_mangle]
++pub extern "C" fn cache_char_free(cache: *mut ARCacheChar) {
++    // Should we be responsible to drain and free everything?
++    debug_assert!(!cache.is_null());
++    unsafe {
++        let _drop = Box::from_raw(cache);
++    }
++}
++
++#[no_mangle]
++pub extern "C" fn cache_char_stats(
++    cache: *mut ARCacheChar,
++    reader_hits: &mut u64,
++    reader_includes: &mut u64,
++    write_hits: &mut u64,
++    write_inc_or_mod: &mut u64,
++    shared_max: &mut u64,
++    freq: &mut u64,
++    recent: &mut u64,
++    freq_evicts: &mut u64,
++    recent_evicts: &mut u64,
++    p_weight: &mut u64,
++    all_seen_keys: &mut u64,
++) {
++    let cache_ref = unsafe {
++        debug_assert!(!cache.is_null());
++        &(*cache) as &ARCacheChar
++    };
++    let stat_rguard = cache_ref.inner.view_stats();
++    let stats = stat_rguard.borrow();
++    *reader_hits = stats.reader_hits.try_into().unwrap();
++    *reader_includes = stats.reader_includes.try_into().unwrap();
++    *write_hits = stats.write_hits.try_into().unwrap();
++    *write_inc_or_mod = (stats.write_includes + stats.write_modifies)
++        .try_into()
++        .unwrap();
++    *shared_max = stats.shared_max.try_into().unwrap();
++    *freq = stats.freq.try_into().unwrap();
++    *recent = stats.recent.try_into().unwrap();
++    *freq_evicts = stats.freq_evicts.try_into().unwrap();
++    *recent_evicts = stats.recent_evicts.try_into().unwrap();
++    *p_weight = stats.p_weight.try_into().unwrap();
++    *all_seen_keys = stats.all_seen_keys.try_into().unwrap();
++}
++
++// start read
++#[no_mangle]
++pub extern "C" fn cache_char_read_begin(cache: *mut ARCacheChar) -> *mut ARCacheCharRead<'static> {
++    let cache_ref = unsafe {
++        debug_assert!(!cache.is_null());
++        &(*cache) as &ARCacheChar
++    };
++    let read_txn = Box::new(ARCacheCharRead {
++        inner: cache_ref.inner.read(),
++    });
++    Box::into_raw(read_txn)
++}
++
++#[no_mangle]
++pub extern "C" fn cache_char_read_complete(read_txn: *mut ARCacheCharRead) {
++    debug_assert!(!read_txn.is_null());
++    unsafe {
++        let _drop = Box::from_raw(read_txn);
++    }
++}
++
++#[no_mangle]
++pub extern "C" fn cache_char_read_get(
++    read_txn: *mut ARCacheCharRead,
++    key: *const c_char,
++) -> *const c_char {
++    let read_txn_ref = unsafe {
++        debug_assert!(!read_txn.is_null());
++        &mut (*read_txn) as &mut ARCacheCharRead
++    };
++
++    let key_ref = unsafe { CStr::from_ptr(key) };
++    let key_dup = CString::from(key_ref);
++
++    // Return a null pointer on miss.
++    read_txn_ref
++        .inner
++        .get(&key_dup)
++        .map(|v| v.as_ptr())
++        .unwrap_or(std::ptr::null())
++}
++
++#[no_mangle]
++pub extern "C" fn cache_char_read_include(
++    read_txn: *mut ARCacheCharRead,
++    key: *const c_char,
++    val: *const c_char,
++) {
++    let read_txn_ref = unsafe {
++        debug_assert!(!read_txn.is_null());
++        &mut (*read_txn) as &mut ARCacheCharRead
++    };
++
++    let key_ref = unsafe { CStr::from_ptr(key) };
++    let key_dup = CString::from(key_ref);
++
++    let val_ref = unsafe { CStr::from_ptr(val) };
++    let val_dup = CString::from(val_ref);
++    read_txn_ref.inner.insert(key_dup, val_dup);
++}
++
++#[no_mangle]
++pub extern "C" fn cache_char_write_begin(
++    cache: *mut ARCacheChar,
++) -> *mut ARCacheCharWrite<'static> {
++    let cache_ref = unsafe {
++        debug_assert!(!cache.is_null());
++        &(*cache) as &ARCacheChar
++    };
++    let write_txn = Box::new(ARCacheCharWrite {
++        inner: cache_ref.inner.write(),
++    });
++    Box::into_raw(write_txn)
++}
++
++#[no_mangle]
++pub extern "C" fn cache_char_write_commit(write_txn: *mut ARCacheCharWrite) {
++    debug_assert!(!write_txn.is_null());
++    let wr = unsafe { Box::from_raw(write_txn) };
++    (*wr).inner.commit();
++}
++
++#[no_mangle]
++pub extern "C" fn cache_char_write_rollback(write_txn: *mut ARCacheCharWrite) {
++    debug_assert!(!write_txn.is_null());
++    unsafe {
++        let _drop = Box::from_raw(write_txn);
++    }
++}
++
++#[no_mangle]
++pub extern "C" fn cache_char_write_include(
++    write_txn: *mut ARCacheCharWrite,
++    key: *const c_char,
++    val: *const c_char,
++) {
++    let write_txn_ref = unsafe {
++        debug_assert!(!write_txn.is_null());
++        &mut (*write_txn) as &mut ARCacheCharWrite
++    };
++
++    let key_ref = unsafe { CStr::from_ptr(key) };
++    let key_dup = CString::from(key_ref);
++
++    let val_ref = unsafe { CStr::from_ptr(val) };
++    let val_dup = CString::from(val_ref);
++    write_txn_ref.inner.insert(key_dup, val_dup);
++}
++
++#[cfg(test)]
++mod tests {
++    use crate::cache::*;
++
++    #[test]
++    fn test_cache_basic() {
++        let cache_ptr = cache_char_create(1024, 8);
++        let read_txn = cache_char_read_begin(cache_ptr);
++
++        let k1 = CString::new("Hello").unwrap();
++        let v1 = CString::new("Hello").unwrap();
++
++        assert!(cache_char_read_get(read_txn, k1.as_ptr()).is_null());
++        cache_char_read_include(read_txn, k1.as_ptr(), v1.as_ptr());
++        assert!(!cache_char_read_get(read_txn, k1.as_ptr()).is_null());
++
++        cache_char_read_complete(read_txn);
++        cache_char_free(cache_ptr);
++    }
++}
+diff --git a/src/librslapd/src/lib.rs b/src/librslapd/src/lib.rs
+new file mode 100644
+index 000000000..65a331b03
+--- /dev/null
++++ b/src/librslapd/src/lib.rs
+@@ -0,0 +1,55 @@
++#![deny(warnings)]
++// It's important that symbol names here are *unique* and do no conflict with symbol
++// names in ../../librnsslapd/src/lib.rs
++//
++// Remember this is just a c-bindgen stub, all logic should come from slapd!
++
++extern crate concread;
++extern crate libc;
++
++use slapd;
++
++use libc::c_char;
++use std::ffi::{CStr, CString};
++
++mod cache;
++
++#[no_mangle]
++pub extern "C" fn do_nothing_rust() -> usize {
++    0
++}
++
++#[no_mangle]
++pub extern "C" fn rust_free_string(s: *mut c_char) {
++    if !s.is_null() {
++        let _ = unsafe { CString::from_raw(s) };
++    }
++}
++
++#[no_mangle]
++pub extern "C" fn fernet_generate_new_key() -> *mut c_char {
++    // It's important to note, we can't return the cstring here, we have to strdup
++    // it so that the caller can free it.
++    let res_key = slapd::fernet::generate_new_key();
++    // While we have a rich error type, we can't do much with it over the ffi, so
++    // discard it here (for now). When we impl logging in rust it will be easier to
++    // then consume this error type.
++    match res_key {
++        Ok(key) => {
++            let raw = key.into_raw();
++            let dup_key = unsafe { libc::strdup(raw) };
++            rust_free_string(raw);
++            dup_key
++        }
++        Err(_) => std::ptr::null_mut(),
++    }
++}
++
++#[no_mangle]
++pub extern "C" fn fernet_validate_key(raw_key: *const c_char) -> bool {
++    let c_str_key = unsafe { CStr::from_ptr(raw_key) };
++    match slapd::fernet::new(c_str_key) {
++        Ok(_) => true,
++        Err(_) => false,
++    }
++}
+diff --git a/src/plugins/pwdchan/Cargo.toml b/src/plugins/pwdchan/Cargo.toml
+new file mode 100644
+index 000000000..40d8a54aa
+--- /dev/null
++++ b/src/plugins/pwdchan/Cargo.toml
+@@ -0,0 +1,23 @@
++[package]
++name = "pwdchan"
++version = "0.1.0"
++authors = ["William Brown <william@blackhats.net.au>"]
++edition = "2018"
++
++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
++
++[lib]
++path = "src/lib.rs"
++name = "pwdchan"
++crate-type = ["staticlib", "lib"]
++
++[dependencies]
++libc = "0.2"
++paste = "0.1"
++slapi_r_plugin = { path="../../slapi_r_plugin" }
++uuid = { version = "0.8", features = [ "v4" ] }
++openssl = { version = "0.10" }
++base64 = "0.13"
++
++[build-dependencies]
++cc = { version = "1.0", features = ["parallel"] }
+diff --git a/src/plugins/pwdchan/src/lib.rs b/src/plugins/pwdchan/src/lib.rs
+new file mode 100644
+index 000000000..7f938fccb
+--- /dev/null
++++ b/src/plugins/pwdchan/src/lib.rs
+@@ -0,0 +1,264 @@
++#![deny(warnings)]
++#[macro_use]
++extern crate slapi_r_plugin;
++use base64;
++use openssl::{hash::MessageDigest, pkcs5::pbkdf2_hmac, rand::rand_bytes};
++use slapi_r_plugin::prelude::*;
++use std::fmt;
++
++const PBKDF2_ROUNDS: usize = 10_000;
++const PBKDF2_SALT_LEN: usize = 24;
++const PBKDF2_SHA1_EXTRACT: usize = 20;
++const PBKDF2_SHA256_EXTRACT: usize = 32;
++const PBKDF2_SHA512_EXTRACT: usize = 64;
++
++pub mod pbkdf2;
++pub mod pbkdf2_sha1;
++pub mod pbkdf2_sha256;
++pub mod pbkdf2_sha512;
++
++struct PwdChanCrypto;
++
++// OpenLDAP based their PBKDF2 implementation on passlib from python, that uses a
++// non-standard base64 altchar set and padding that is not supported by
++// anything else in the world. To manage this, we only ever encode to base64 with
++// no pad but we have to remap ab64 to b64. This function allows b64 standard with
++// padding to pass, and remaps ab64 to b64 standard with padding.
++macro_rules! ab64_to_b64 {
++    ($ab64:expr) => {{
++        let mut s = $ab64.replace(".", "+");
++        match s.len() & 3 {
++            0 => {
++                // Do nothing
++            }
++            1 => {
++                // One is invalid, do nothing, we'll error in base64
++            }
++            2 => s.push_str("=="),
++            3 => s.push_str("="),
++            _ => unreachable!(),
++        }
++        s
++    }};
++}
++
++impl PwdChanCrypto {
++    #[inline(always)]
++    fn pbkdf2_decompose(encrypted: &str) -> Result<(usize, Vec<u8>, Vec<u8>), PluginError> {
++        let mut part_iter = encrypted.split('$');
++
++        let iter = part_iter
++            .next()
++            .ok_or(PluginError::MissingValue)
++            .and_then(|iter_str| {
++                usize::from_str_radix(iter_str, 10).map_err(|e| {
++                    log_error!(ErrorLevel::Error, "Invalid Integer {} -> {:?}", iter_str, e);
++                    PluginError::InvalidStrToInt
++                })
++            })?;
++
++        let salt = part_iter
++            .next()
++            .ok_or(PluginError::MissingValue)
++            .and_then(|ab64| {
++                let s = ab64_to_b64!(ab64);
++                base64::decode_config(&s, base64::STANDARD.decode_allow_trailing_bits(true))
++                    .map_err(|e| {
++                        log_error!(ErrorLevel::Error, "Invalid Base 64 {} -> {:?}", s, e);
++                        PluginError::InvalidBase64
++                    })
++            })?;
++
++        let hash = part_iter
++            .next()
++            .ok_or(PluginError::MissingValue)
++            .and_then(|ab64| {
++                let s = ab64_to_b64!(ab64);
++                base64::decode_config(&s, base64::STANDARD.decode_allow_trailing_bits(true))
++                    .map_err(|e| {
++                        log_error!(ErrorLevel::Error, "Invalid Base 64 {} -> {:?}", s, e);
++                        PluginError::InvalidBase64
++                    })
++            })?;
++
++        Ok((iter, salt, hash))
++    }
++
++    fn pbkdf2_compare(
++        cleartext: &str,
++        encrypted: &str,
++        digest: MessageDigest,
++    ) -> Result<bool, PluginError> {
++        let (iter, salt, hash_expected) = Self::pbkdf2_decompose(encrypted).map_err(|e| {
++            // This means our DB content is flawed.
++            log_error!(ErrorLevel::Error, "invalid hashed pw -> {:?}", e);
++            e
++        })?;
++        // Need to pre-alloc the space as as_mut_slice can't resize.
++        let mut hash_input: Vec<u8> = (0..hash_expected.len()).map(|_| 0).collect();
++
++        pbkdf2_hmac(
++            cleartext.as_bytes(),
++            &salt,
++            iter,
++            digest,
++            hash_input.as_mut_slice(),
++        )
++        .map_err(|e| {
++            log_error!(ErrorLevel::Error, "OpenSSL Error -> {:?}", e);
++            PluginError::OpenSSL
++        })
++        .map(|()| hash_input == hash_expected)
++    }
++
++    fn pbkdf2_encrypt(cleartext: &str, digest: MessageDigest) -> Result<String, PluginError> {
++        let (hash_length, str_length, header) = if digest == MessageDigest::sha1() {
++            (PBKDF2_SHA1_EXTRACT, 80, "{PBKDF2-SHA1}")
++        } else if digest == MessageDigest::sha256() {
++            (PBKDF2_SHA256_EXTRACT, 100, "{PBKDF2-SHA256}")
++        } else if digest == MessageDigest::sha512() {
++            (PBKDF2_SHA512_EXTRACT, 140, "{PBKDF2-SHA512}")
++        } else {
++            return Err(PluginError::Unknown);
++        };
++
++        // generate salt
++        let mut salt: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| 0).collect();
++        rand_bytes(salt.as_mut_slice()).map_err(|e| {
++            log_error!(ErrorLevel::Error, "OpenSSL Error -> {:?}", e);
++            PluginError::OpenSSL
++        })?;
++
++        let mut hash_input: Vec<u8> = (0..hash_length).map(|_| 0).collect();
++
++        pbkdf2_hmac(
++            cleartext.as_bytes(),
++            &salt,
++            PBKDF2_ROUNDS,
++            digest,
++            hash_input.as_mut_slice(),
++        )
++        .map_err(|e| {
++            log_error!(ErrorLevel::Error, "OpenSSL Error -> {:?}", e);
++            PluginError::OpenSSL
++        })?;
++
++        let mut output = String::with_capacity(str_length);
++        // Write the header
++        output.push_str(header);
++        // The iter + delim
++        fmt::write(&mut output, format_args!("{}$", PBKDF2_ROUNDS)).map_err(|e| {
++            log_error!(ErrorLevel::Error, "Format Error -> {:?}", e);
++            PluginError::Format
++        })?;
++        // the base64 salt
++        base64::encode_config_buf(&salt, base64::STANDARD, &mut output);
++        // Push the delim
++        output.push_str("$");
++        // Finally the base64 hash
++        base64::encode_config_buf(&hash_input, base64::STANDARD, &mut output);
++        // Return it
++        Ok(output)
++    }
++
++    // Remember, encrypted does *not* have the scheme prepended.
++    #[inline(always)]
++    fn pbkdf2_sha1_compare(cleartext: &str, encrypted: &str) -> Result<bool, PluginError> {
++        Self::pbkdf2_compare(cleartext, encrypted, MessageDigest::sha1())
++    }
++
++    #[inline(always)]
++    fn pbkdf2_sha256_compare(cleartext: &str, encrypted: &str) -> Result<bool, PluginError> {
++        Self::pbkdf2_compare(cleartext, encrypted, MessageDigest::sha256())
++    }
++
++    #[inline(always)]
++    fn pbkdf2_sha512_compare(cleartext: &str, encrypted: &str) -> Result<bool, PluginError> {
++        Self::pbkdf2_compare(cleartext, encrypted, MessageDigest::sha512())
++    }
++
++    #[inline(always)]
++    fn pbkdf2_sha1_encrypt(cleartext: &str) -> Result<String, PluginError> {
++        Self::pbkdf2_encrypt(cleartext, MessageDigest::sha1())
++    }
++
++    #[inline(always)]
++    fn pbkdf2_sha256_encrypt(cleartext: &str) -> Result<String, PluginError> {
++        Self::pbkdf2_encrypt(cleartext, MessageDigest::sha256())
++    }
++
++    #[inline(always)]
++    fn pbkdf2_sha512_encrypt(cleartext: &str) -> Result<String, PluginError> {
++        Self::pbkdf2_encrypt(cleartext, MessageDigest::sha512())
++    }
++}
++
++#[cfg(test)]
++mod tests {
++    use crate::PwdChanCrypto;
++    /*
++     * '{PBKDF2}10000$IlfapjA351LuDSwYC0IQ8Q$saHqQTuYnjJN/tmAndT.8mJt.6w'
++     * '{PBKDF2-SHA1}10000$ZBEH6B07rgQpJSikyvMU2w$TAA03a5IYkz1QlPsbJKvUsTqNV'
++     * '{PBKDF2-SHA256}10000$henZGfPWw79Cs8ORDeVNrQ$1dTJy73v6n3bnTmTZFghxHXHLsAzKaAy8SksDfZBPIw'
++     * '{PBKDF2-SHA512}10000$Je1Uw19Bfv5lArzZ6V3EPw$g4T/1sqBUYWl9o93MVnyQ/8zKGSkPbKaXXsT8WmysXQJhWy8MRP2JFudSL.N9RklQYgDPxPjnfum/F2f/TrppA'
++     * '{ARGON2}$argon2id$v=19$m=65536,t=2,p=1$IyTQMsvzB2JHDiWx8fq7Ew$VhYOA7AL0kbRXI5g2kOyyp8St1epkNj7WZyUY4pAIQQ'
++     */
++
++    #[test]
++    fn pwdchan_pbkdf2_sha1_basic() {
++        let encrypted = "10000$IlfapjA351LuDSwYC0IQ8Q$saHqQTuYnjJN/tmAndT.8mJt.6w";
++        assert!(PwdChanCrypto::pbkdf2_sha1_compare("password", encrypted) == Ok(true));
++        assert!(PwdChanCrypto::pbkdf2_sha1_compare("password!", encrypted) == Ok(false));
++        assert!(PwdChanCrypto::pbkdf2_sha1_compare("incorrect", encrypted) == Ok(false));
++
++        // this value gave some invalid b64 errors due to trailing bits and padding.
++        let encrypted = "10000$ZBEH6B07rgQpJSikyvMU2w$TAA03a5IYkz1QlPsbJKvUsTqNV";
++        assert!(PwdChanCrypto::pbkdf2_sha1_compare("password", encrypted) == Ok(true));
++        assert!(PwdChanCrypto::pbkdf2_sha1_compare("password!", encrypted) == Ok(false));
++
++        let test_enc = PwdChanCrypto::pbkdf2_sha1_encrypt("password").expect("Failed to hash");
++        // Remove the header and check.
++        let test_enc = test_enc.replace("{PBKDF2-SHA1}", "");
++        assert!(PwdChanCrypto::pbkdf2_sha1_compare("password", &test_enc) == Ok(true));
++        assert!(PwdChanCrypto::pbkdf2_sha1_compare("password!", &test_enc) == Ok(false));
++    }
++
++    #[test]
++    fn pwdchan_pbkdf2_sha256_basic() {
++        let encrypted = "10000$henZGfPWw79Cs8ORDeVNrQ$1dTJy73v6n3bnTmTZFghxHXHLsAzKaAy8SksDfZBPIw";
++        assert!(PwdChanCrypto::pbkdf2_sha256_compare("password", encrypted) == Ok(true));
++        assert!(PwdChanCrypto::pbkdf2_sha256_compare("password!", encrypted) == Ok(false));
++        assert!(PwdChanCrypto::pbkdf2_sha256_compare("incorrect", encrypted) == Ok(false));
++
++        // This is a django password with their pbkdf2_sha256$ type.
++        // "pbkdf2_sha256$36000$xIEozuZVAoYm$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w="
++        //            salt -->  xIEozuZVAoYm
++        // django doesn't base64 it's salt, so you need to base64 it to:
++        //                      eElFb3p1WlZBb1lt
++        let encrypted = "36000$eElFb3p1WlZBb1lt$uW1b35DUKyhvQAf1mBqMvoBDcqSD06juzyO/nmyV0+w=";
++        assert!(
++            PwdChanCrypto::pbkdf2_sha256_compare("eicieY7ahchaoCh0eeTa", encrypted) == Ok(true)
++        );
++        assert!(PwdChanCrypto::pbkdf2_sha256_compare("password!", encrypted) == Ok(false));
++
++        let test_enc = PwdChanCrypto::pbkdf2_sha256_encrypt("password").expect("Failed to hash");
++        // Remove the header and check.
++        let test_enc = test_enc.replace("{PBKDF2-SHA256}", "");
++        assert!(PwdChanCrypto::pbkdf2_sha256_compare("password", &test_enc) == Ok(true));
++        assert!(PwdChanCrypto::pbkdf2_sha256_compare("password!", &test_enc) == Ok(false));
++    }
++
++    #[test]
++    fn pwdchan_pbkdf2_sha512_basic() {
++        let encrypted = "10000$Je1Uw19Bfv5lArzZ6V3EPw$g4T/1sqBUYWl9o93MVnyQ/8zKGSkPbKaXXsT8WmysXQJhWy8MRP2JFudSL.N9RklQYgDPxPjnfum/F2f/TrppA";
++        assert!(PwdChanCrypto::pbkdf2_sha512_compare("password", encrypted) == Ok(true));
++        assert!(PwdChanCrypto::pbkdf2_sha512_compare("password!", encrypted) == Ok(false));
++        assert!(PwdChanCrypto::pbkdf2_sha512_compare("incorrect", encrypted) == Ok(false));
++
++        let test_enc = PwdChanCrypto::pbkdf2_sha512_encrypt("password").expect("Failed to hash");
++        // Remove the header and check.
++        let test_enc = test_enc.replace("{PBKDF2-SHA512}", "");
++        assert!(PwdChanCrypto::pbkdf2_sha512_compare("password", &test_enc) == Ok(true));
++        assert!(PwdChanCrypto::pbkdf2_sha512_compare("password!", &test_enc) == Ok(false));
++    }
++}
+diff --git a/src/plugins/pwdchan/src/pbkdf2.rs b/src/plugins/pwdchan/src/pbkdf2.rs
+new file mode 100644
+index 000000000..e8ee87b82
+--- /dev/null
++++ b/src/plugins/pwdchan/src/pbkdf2.rs
+@@ -0,0 +1,44 @@
++use crate::PwdChanCrypto;
++use slapi_r_plugin::prelude::*;
++use std::os::raw::c_char;
++
++/*
++ *                    /---- plugin ident
++ *                    |          /---- Struct name.
++ *                    V          V
++ */
++slapi_r_plugin_hooks!(pwdchan_pbkdf2, PwdChanPbkdf2);
++
++// PBKDF2 == PBKDF2-SHA1
++struct PwdChanPbkdf2;
++
++impl SlapiPlugin3 for PwdChanPbkdf2 {
++    // We require a newer rust for default associated types.
++    type TaskData = ();
++
++    fn start(_pb: &mut PblockRef) -> Result<(), PluginError> {
++        log_error!(ErrorLevel::Trace, "plugin start");
++        Ok(())
++    }
++
++    fn close(_pb: &mut PblockRef) -> Result<(), PluginError> {
++        log_error!(ErrorLevel::Trace, "plugin close");
++        Ok(())
++    }
++
++    fn has_pwd_storage() -> bool {
++        true
++    }
++
++    fn pwd_scheme_name() -> &'static str {
++        "PBKDF2"
++    }
++
++    fn pwd_storage_encrypt(cleartext: &str) -> Result<String, PluginError> {
++        PwdChanCrypto::pbkdf2_sha1_encrypt(cleartext)
++    }
++
++    fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result<bool, PluginError> {
++        PwdChanCrypto::pbkdf2_sha1_compare(cleartext, encrypted)
++    }
++}
+diff --git a/src/plugins/pwdchan/src/pbkdf2_sha1.rs b/src/plugins/pwdchan/src/pbkdf2_sha1.rs
+new file mode 100644
+index 000000000..67afef5cb
+--- /dev/null
++++ b/src/plugins/pwdchan/src/pbkdf2_sha1.rs
+@@ -0,0 +1,44 @@
++use crate::PwdChanCrypto;
++use slapi_r_plugin::prelude::*;
++use std::os::raw::c_char;
++
++/*
++ *                    /---- plugin ident
++ *                    |          /---- Struct name.
++ *                    V          V
++ */
++slapi_r_plugin_hooks!(pwdchan_pbkdf2_sha1, PwdChanPbkdf2Sha1);
++
++// PBKDF2 == PBKDF2-SHA1
++struct PwdChanPbkdf2Sha1;
++
++impl SlapiPlugin3 for PwdChanPbkdf2Sha1 {
++    // We require a newer rust for default associated types.
++    type TaskData = ();
++
++    fn start(_pb: &mut PblockRef) -> Result<(), PluginError> {
++        log_error!(ErrorLevel::Trace, "plugin start");
++        Ok(())
++    }
++
++    fn close(_pb: &mut PblockRef) -> Result<(), PluginError> {
++        log_error!(ErrorLevel::Trace, "plugin close");
++        Ok(())
++    }
++
++    fn has_pwd_storage() -> bool {
++        true
++    }
++
++    fn pwd_scheme_name() -> &'static str {
++        "PBKDF2-SHA1"
++    }
++
++    fn pwd_storage_encrypt(cleartext: &str) -> Result<String, PluginError> {
++        PwdChanCrypto::pbkdf2_sha1_encrypt(cleartext)
++    }
++
++    fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result<bool, PluginError> {
++        PwdChanCrypto::pbkdf2_sha1_compare(cleartext, encrypted)
++    }
++}
+diff --git a/src/plugins/pwdchan/src/pbkdf2_sha256.rs b/src/plugins/pwdchan/src/pbkdf2_sha256.rs
+new file mode 100644
+index 000000000..efe3fb383
+--- /dev/null
++++ b/src/plugins/pwdchan/src/pbkdf2_sha256.rs
+@@ -0,0 +1,43 @@
++use crate::PwdChanCrypto;
++use slapi_r_plugin::prelude::*;
++use std::os::raw::c_char;
++
++/*
++ *                    /---- plugin ident
++ *                    |          /---- Struct name.
++ *                    V          V
++ */
++slapi_r_plugin_hooks!(pwdchan_pbkdf2_sha256, PwdChanPbkdf2Sha256);
++
++struct PwdChanPbkdf2Sha256;
++
++impl SlapiPlugin3 for PwdChanPbkdf2Sha256 {
++    // We require a newer rust for default associated types.
++    type TaskData = ();
++
++    fn start(_pb: &mut PblockRef) -> Result<(), PluginError> {
++        log_error!(ErrorLevel::Trace, "plugin start");
++        Ok(())
++    }
++
++    fn close(_pb: &mut PblockRef) -> Result<(), PluginError> {
++        log_error!(ErrorLevel::Trace, "plugin close");
++        Ok(())
++    }
++
++    fn has_pwd_storage() -> bool {
++        true
++    }
++
++    fn pwd_scheme_name() -> &'static str {
++        "PBKDF2-SHA256"
++    }
++
++    fn pwd_storage_encrypt(cleartext: &str) -> Result<String, PluginError> {
++        PwdChanCrypto::pbkdf2_sha256_encrypt(cleartext)
++    }
++
++    fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result<bool, PluginError> {
++        PwdChanCrypto::pbkdf2_sha256_compare(cleartext, encrypted)
++    }
++}
+diff --git a/src/plugins/pwdchan/src/pbkdf2_sha512.rs b/src/plugins/pwdchan/src/pbkdf2_sha512.rs
+new file mode 100644
+index 000000000..b0ed2bea8
+--- /dev/null
++++ b/src/plugins/pwdchan/src/pbkdf2_sha512.rs
+@@ -0,0 +1,43 @@
++use crate::PwdChanCrypto;
++use slapi_r_plugin::prelude::*;
++use std::os::raw::c_char;
++
++/*
++ *                    /---- plugin ident
++ *                    |          /---- Struct name.
++ *                    V          V
++ */
++slapi_r_plugin_hooks!(pwdchan_pbkdf2_sha512, PwdChanPbkdf2Sha512);
++
++struct PwdChanPbkdf2Sha512;
++
++impl SlapiPlugin3 for PwdChanPbkdf2Sha512 {
++    // We require a newer rust for default associated types.
++    type TaskData = ();
++
++    fn start(_pb: &mut PblockRef) -> Result<(), PluginError> {
++        log_error!(ErrorLevel::Trace, "plugin start");
++        Ok(())
++    }
++
++    fn close(_pb: &mut PblockRef) -> Result<(), PluginError> {
++        log_error!(ErrorLevel::Trace, "plugin close");
++        Ok(())
++    }
++
++    fn has_pwd_storage() -> bool {
++        true
++    }
++
++    fn pwd_scheme_name() -> &'static str {
++        "PBKDF2-SHA512"
++    }
++
++    fn pwd_storage_encrypt(cleartext: &str) -> Result<String, PluginError> {
++        PwdChanCrypto::pbkdf2_sha512_encrypt(cleartext)
++    }
++
++    fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result<bool, PluginError> {
++        PwdChanCrypto::pbkdf2_sha512_compare(cleartext, encrypted)
++    }
++}
+diff --git a/src/slapd/Cargo.toml b/src/slapd/Cargo.toml
+new file mode 100644
+index 000000000..a18cb7626
+--- /dev/null
++++ b/src/slapd/Cargo.toml
+@@ -0,0 +1,10 @@
++[package]
++name = "slapd"
++version = "0.1.0"
++authors = ["William Brown <william@blackhats.net.au>"]
++edition = "2018"
++
++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
++
++[dependencies]
++fernet = "0.1"
+diff --git a/src/slapd/src/error.rs b/src/slapd/src/error.rs
+new file mode 100644
+index 000000000..6f4d782ee
+--- /dev/null
++++ b/src/slapd/src/error.rs
+@@ -0,0 +1,6 @@
++pub enum SlapdError {
++    // This occurs when a string contains an inner null byte
++    // that cstring can't handle.
++    CStringInvalidError,
++    FernetInvalidKey,
++}
+diff --git a/src/slapd/src/fernet.rs b/src/slapd/src/fernet.rs
+new file mode 100644
+index 000000000..1a3251fd9
+--- /dev/null
++++ b/src/slapd/src/fernet.rs
+@@ -0,0 +1,30 @@
++// Routines for managing fernet encryption
++
++use crate::error::SlapdError;
++use fernet::Fernet;
++use std::ffi::{CStr, CString};
++
++pub fn generate_new_key() -> Result<CString, SlapdError> {
++    let k = Fernet::generate_key();
++    CString::new(k).map_err(|_| SlapdError::CStringInvalidError)
++}
++
++pub fn new(c_str_key: &CStr) -> Result<Fernet, SlapdError> {
++    let str_key = c_str_key
++        .to_str()
++        .map_err(|_| SlapdError::CStringInvalidError)?;
++    Fernet::new(str_key).ok_or(SlapdError::FernetInvalidKey)
++}
++
++pub fn encrypt(fernet: &Fernet, dn: &CStr) -> Result<CString, SlapdError> {
++    let tok = fernet.encrypt(dn.to_bytes());
++    CString::new(tok).map_err(|_| SlapdError::CStringInvalidError)
++}
++
++pub fn decrypt(fernet: &Fernet, tok: &CStr, ttl: u64) -> Result<CString, SlapdError> {
++    let s = tok.to_str().map_err(|_| SlapdError::CStringInvalidError)?;
++    let r: Vec<u8> = fernet
++        .decrypt_with_ttl(s, ttl)
++        .map_err(|_| SlapdError::FernetInvalidKey)?;
++    CString::new(r).map_err(|_| SlapdError::CStringInvalidError)
++}
+diff --git a/src/slapd/src/lib.rs b/src/slapd/src/lib.rs
+new file mode 100644
+index 000000000..79f1600c2
+--- /dev/null
++++ b/src/slapd/src/lib.rs
+@@ -0,0 +1,2 @@
++pub mod error;
++pub mod fernet;
+diff --git a/src/slapi_r_plugin/Cargo.toml b/src/slapi_r_plugin/Cargo.toml
+new file mode 100644
+index 000000000..9d197ec85
+--- /dev/null
++++ b/src/slapi_r_plugin/Cargo.toml
+@@ -0,0 +1,22 @@
++[package]
++name = "slapi_r_plugin"
++version = "0.1.0"
++authors = ["William Brown <william@blackhats.net.au>"]
++edition = "2018"
++build = "build.rs"
++
++# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
++
++[features]
++test_log_direct = []
++
++[lib]
++path = "src/lib.rs"
++name = "slapi_r_plugin"
++crate-type = ["staticlib", "lib"]
++
++[dependencies]
++libc = "0.2"
++paste = "0.1"
++# lazy_static = "1.4"
++uuid = { version = "0.8", features = [ "v4" ] }
+diff --git a/src/slapi_r_plugin/README.md b/src/slapi_r_plugin/README.md
+new file mode 100644
+index 000000000..1c9bcbf17
+--- /dev/null
++++ b/src/slapi_r_plugin/README.md
+@@ -0,0 +1,216 @@
++
++# Slapi R(ust) Plugin Bindings
++
++If you are here, you are probably interested in the Rust bindings that allow plugins to be written
++in Rust for the 389 Directory Server project. If you are, you should use `cargo doc --workspace --no-deps`
++in `src`, as this contains the material you want for implementing safe plugins.
++
++This readme is intended for developers of the bindings that enable those plugins to work.
++
++As such it likely requires that you have an understanding both of C and
++the [Rust Nomicon](https://doc.rust-lang.org/nomicon/index.html)
++
++> **WARNING** This place is not a place of honor ... no highly esteemed deed is commemorated here
++> ... nothing valued is here. What is here is dangerous and repulsive to us. This message is a
++> warning about danger.
++
++This document will not detail the specifics of unsafe or the invariants you must adhere to for rust
++to work with C. Failure to uphold these invariants will lead to less than optimal consequences.
++
++If you still want to see more about the plugin bindings, go on ...
++
++## The Challenge
++
++Rust is a memory safe language - that means you may not dereference pointers or alter or interact
++with uninitialised memory. There are whole classes of problems that this resolves, but it means
++that Rust is opiniated about how it interacts with memory.
++
++C is an unsafe language - there are undefined behaviours all through out the specification, memory
++can be interacted with without bounds which leads to many kinds of issues ranging from crashes,
++silent data corruption, to code execution and explotation.
++
++While it would be nice to rewrite everything from C to Rust, this is a large task - instead we need
++a way to allow Rust and C to interact.
++
++## The Goal
++
++To be able to define, a pure Rust, 100% safe (in rust terms) plugin for 389 Directory Server that
++can perform useful tasks.
++
++## The 389 Directory Server Plugin API
++
++The 389-ds plugin system works by reading an ldap entry from cn=config, that directs to a shared
++library. That shared library path is dlopened and an init symbol read and activated. At that
++point the plugin is able to call-back into 389-ds to provide registration of function handlers for
++various tasks that the plugin may wish to perform at defined points in a operations execution.
++
++During the execution of a plugin callback, the context of the environment is passed through a
++parameter block (pblock). This pblock has a set of apis for accessing it's content, which may
++or may not be defined based on the execution state of the server.
++
++Common plugin tasks involve the transformation of entries during write operation paths to provide
++extra attributes to the entry or generation of other entries. Values in entries are represented by
++internal structures that may or may not have sorting of content.
++
++Already at this point it can be seen there is a lot of surface area to access. For clarity in
++our trivial example here we have required:
++
++* Pblock
++* Entry
++* ValueSet
++* Value
++* Sdn
++* Result Codes
++
++We need to be able to interact with all of these - and more - to make useful plugins.
++
++## Structure of the Rust Plugin bindings.
++
++As a result, there are a number of items we must be able to implement:
++
++* Creation of the plugin function callback points
++* Transformation of C pointer types into Rust structures that can be interacted with.
++* Ability to have Rust interact with structures to achieve side effects in the C server
++* Mapping of errors that C can understand
++* Make all of it safe.
++
++In order to design this, it's useful to see what a plugin from Rust should look like - by designing
++what the plugin should look like, we make the bindings that are preferable and ergonomic to rust
++rather than compromising on quality and developer experience.
++
++Here is a minimal example of a plugin - it may not compile or be complete, it serves as an
++example.
++
++```
++#[macro_use]
++extern crate slapi_r_plugin;
++use slapi_r_plugin::prelude::*;
++
++struct NewPlugin;
++
++slapi_r_plugin_hooks!(plugin_name, NewPlugin);
++
++impl SlapiPlugin3 for NewPlugin {
++    fn start(_pb: &mut PblockRef) -> Result<(), PluginError> {
++        log_error!(ErrorLevel::Trace, "plugin start");
++        Ok(())
++    }
++
++    fn close(_pb: &mut PblockRef) -> Result<(), PluginError> {
++        log_error!(ErrorLevel::Trace, "plugin close");
++        Ok(())
++    }
++
++    fn has_betxn_pre_add() -> bool {
++        true
++    }
++
++    fn betxn_pre_add(pb: &mut PblockRef) -> Result<(), PluginError> {
++        let mut e = pb.get_op_add_entryref().map_err(|_| PluginError::Pblock)?;
++        let sdn = e.get_sdnref();
++
++        log_error!(ErrorLevel::Trace, "betxn_pre_add -> {:?}", sdn);
++        Ok(())
++    }
++}
++```
++
++Important details - there is no unsafe, we use rust native error handling and functions, there
++is no indication of memory management, we are defined by a trait, error logging uses native
++formatting. There are probably other details too - I'll leave it as an exercise for the reader
++to play Where's Wally and find them all.
++
++With the end goal in mind, we can begin to look at the construction of the plugin system, and
++the design choices that were made.
++
++## The Plugin Trait
++
++A significant choice was the use of a trait to define the possible plugin function operations
++for rust implementors. This allows the compiler to guarantee that a plugin *will* have all
++associated functions.
++
++> Traits are synonomous with java interfaces, defining methods you "promise" to implement, unlike
++> object orientation with a class hierarchy.
++
++Now, you may notice that not all members of the trait are implemented. This is due to a feature
++of rust known as default trait impls. This allows the trait origin (src/plugin.rs) to provide
++template versions of these functions. If you "overwrite" them, your implementation is used. Unlike
++OO, you may not inherit or call the default function.
++
++If a default is not provided you *must* implement that function to be considered valid. Today (20200422)
++this only applies to `start` and `close`.
++
++The default implementations all return "false" to the presence of callbacks, and if they are used,
++they will always return an error.
++
++## Interface generation
++
++While it is nice to have this Rust interface for plugins, C is unable to call it (Rust uses a different
++stack calling syntax to C, as well as symbol mangaling). To expose these, we must provide `extern C`
++functions, where any function that requires a static symbol must be marked as no_mangle.
++
++Rather than ask all plugin authors to do this, we can use the rust macro system to generate these
++interfaces at compile time. This is the reason for this line:
++
++```
++slapi_r_plugin_hooks!(plugin_name, NewPlugin);
++```
++
++This macro is defined in src/macros.rs, and is "the bridge" from C to Rust. Given a plugin name
++and a struct of the trait SlapiPlugin3, this macro is able to generate all needed C compatible
++functions. Based on the calls to `has_<op_type>`, the generated functions are registered to the pblock
++that is provided.
++
++When a call back triggers, the function landing point is called. This then wraps all the pointer
++types from C into Rust structs, and then dispatches to the struct instance.
++
++When the struct function returns, the result is unpacked and turned into C compatible result codes -
++in some cases, the result codes are sanitised due to quirks in the C ds api - `[<$mod_ident _plugin_mr_filter_ava>]`
++is an excellent example of this, where Rust returns are `true`/`false`, which would normally
++be FFI safe to convert to 1/0 respectively, but 389-ds expects the inverse in this case, where
++0 is true and all other values are false. To present a sane api to rust, the macro layer does this
++(mind bending) transformation for us.
++
++## C Ptr Wrapper types
++
++This is likely the major, and important detail of the plugin api. By wrapping these C ptrs with
++Rust types, we can create types that perform as rust expects, and adheres to the invariants required,
++while providing safe - and useful interfaces to users.
++
++It's important to understand how Rust manages memory both on the stack and the heap - Please see
++[the Rust Book](https://doc.rust-lang.org/book/ch04-00-understanding-ownership.html) for more.
++
++As a result, this means that we must express in code, assertions about the proper ownership of memory
++and who is responsible for it (unlike C, where it can be hard to determine who or what is responsible
++for freeing some value.) Failure to handle this correctly, can and will lead to crashes, leaks or
++*hand waving* magical failures that are `eXtReMeLy FuN` to debug.
++
++### Reference Types
++
++There are a number of types, such as `SdnRef`, which have a suffix of `*Ref`. These types represent
++values whos content is owned by the C server - that is, it is the responsibility of 389-ds to free
++the content of the Pointer once it has been used. A majority of values that are provided to the
++function callback points fall into this class.
++
++### Owned Types
++
++These types contain a pointer from the C server, but it is the responsibility of the Rust library
++to indicate when that pointer and it's content should be disposed of. This is generally handled
++by the `drop` trait, which is executed ... well, when an item is dropped.
++
++### Dispatch from the wrapper to C
++
++When a rust function against a wrapper is called, the type internally accesses it Ref type and
++uses the ptr to dispatch into the C server. Any required invariants are upheld, and results are
++mapped as required to match what rust callers expect.
++
++As a result, this involves horrendous amounts of unsafe, and a detailed analysis of both the DS C
++api, what it expects, and the Rust nomicon to ensure you maintain all the invariants.
++
++## Conclusion
++
++Providing a bridge between C and Rust is challenging - but achievable - the result is plugins that
++are clean, safe, efficent.
++
++
++
+diff --git a/src/slapi_r_plugin/build.rs b/src/slapi_r_plugin/build.rs
+new file mode 100644
+index 000000000..ed773d45a
+--- /dev/null
++++ b/src/slapi_r_plugin/build.rs
+@@ -0,0 +1,9 @@
++use std::env;
++
++fn main() {
++    if let Ok(lib_dir) = env::var("SLAPD_DYLIB_DIR") {
++        println!("cargo:rustc-link-lib=dylib=slapd");
++        println!("cargo:rustc-link-search=native={}", lib_dir);
++        println!("cargo:rustc-link-search=native={}/.libs", lib_dir);
++    }
++}
+diff --git a/src/slapi_r_plugin/src/backend.rs b/src/slapi_r_plugin/src/backend.rs
+new file mode 100644
+index 000000000..6164b2f70
+--- /dev/null
++++ b/src/slapi_r_plugin/src/backend.rs
+@@ -0,0 +1,72 @@
++use crate::dn::SdnRef;
++use crate::pblock::Pblock;
++// use std::ops::Deref;
++
++extern "C" {
++    fn slapi_back_transaction_begin(pb: *const libc::c_void) -> i32;
++    fn slapi_back_transaction_commit(pb: *const libc::c_void);
++    fn slapi_back_transaction_abort(pb: *const libc::c_void);
++    fn slapi_be_select_exact(sdn: *const libc::c_void) -> *const libc::c_void;
++}
++
++pub struct BackendRef {
++    raw_be: *const libc::c_void,
++}
++
++impl BackendRef {
++    pub fn new(dn: &SdnRef) -> Result<Self, ()> {
++        let raw_be = unsafe { slapi_be_select_exact(dn.as_ptr()) };
++        if raw_be.is_null() {
++            Err(())
++        } else {
++            Ok(BackendRef { raw_be })
++        }
++    }
++
++    pub(crate) fn as_ptr(&self) -> *const libc::c_void {
++        self.raw_be
++    }
++
++    pub fn begin_txn(self) -> Result<BackendRefTxn, ()> {
++        let mut pb = Pblock::new();
++        if pb.set_op_backend(&self) != 0 {
++            return Err(());
++        }
++        let rc = unsafe { slapi_back_transaction_begin(pb.as_ptr()) };
++        if rc != 0 {
++            Err(())
++        } else {
++            Ok(BackendRefTxn {
++                pb,
++                _be: self,
++                committed: false,
++            })
++        }
++    }
++}
++
++pub struct BackendRefTxn {
++    pb: Pblock,
++    // Used to keep lifetimes in check.
++    _be: BackendRef,
++    committed: bool,
++}
++
++impl BackendRefTxn {
++    pub fn commit(mut self) {
++        self.committed = true;
++        unsafe {
++            slapi_back_transaction_commit(self.pb.as_ptr());
++        }
++    }
++}
++
++impl Drop for BackendRefTxn {
++    fn drop(&mut self) {
++        if self.committed == false {
++            unsafe {
++                slapi_back_transaction_abort(self.pb.as_ptr());
++            }
++        }
++    }
++}
+diff --git a/src/slapi_r_plugin/src/ber.rs b/src/slapi_r_plugin/src/ber.rs
+new file mode 100644
+index 000000000..83f87dcf4
+--- /dev/null
++++ b/src/slapi_r_plugin/src/ber.rs
+@@ -0,0 +1,92 @@
++#[cfg(not(feature = "test_log_direct"))]
++use crate::log::{log_error, ErrorLevel};
++
++use libc;
++use std::ffi::CString;
++// use std::ptr;
++use std::slice;
++
++use std::convert::TryFrom;
++use uuid::Uuid;
++
++use crate::error::PluginError;
++
++#[repr(C)]
++pub(crate) struct ol_berval {
++    pub len: usize,
++    pub data: *const u8,
++}
++
++#[derive(Debug)]
++pub struct BerValRef {
++    pub(crate) raw_berval: *const ol_berval,
++}
++
++impl BerValRef {
++    pub fn new(raw_berval: *const libc::c_void) -> Self {
++        // so we retype this
++        let raw_berval = raw_berval as *const ol_berval;
++        BerValRef { raw_berval }
++    }
++
++    pub(crate) fn into_cstring(&self) -> Option<CString> {
++        // Cstring does not need a trailing null, so if we have one, ignore it.
++        let l: usize = unsafe { (*self.raw_berval).len };
++        let d_slice = unsafe { slice::from_raw_parts((*self.raw_berval).data, l) };
++        CString::new(d_slice)
++            .or_else(|e| {
++                // Try it again, but with one byte less to trim a potential trailing null that
++                // could have been allocated, and ensure it has at least 1 byte of good data
++                // remaining.
++                if l > 1 {
++                    let d_slice = unsafe { slice::from_raw_parts((*self.raw_berval).data, l - 1) };
++                    CString::new(d_slice)
++                } else {
++                    Err(e)
++                }
++            })
++            .map_err(|_| {
++                log_error!(
++                    ErrorLevel::Trace,
++                    "invalid ber parse attempt, may contain a null byte? -> {:?}",
++                    self
++                );
++                ()
++            })
++            .ok()
++    }
++
++    pub fn into_string(&self) -> Option<String> {
++        // Convert a Some to a rust string.
++        self.into_cstring().and_then(|v| {
++            v.into_string()
++                .map_err(|_| {
++                    log_error!(
++                        ErrorLevel::Trace,
++                        "failed to convert cstring to string -> {:?}",
++                        self
++                    );
++                    ()
++                })
++                .ok()
++        })
++    }
++}
++
++impl TryFrom<&BerValRef> for Uuid {
++    type Error = PluginError;
++
++    fn try_from(value: &BerValRef) -> Result<Self, Self::Error> {
++        let val_string = value.into_string().ok_or(PluginError::BervalString)?;
++
++        Uuid::parse_str(val_string.as_str())
++            .map(|r| {
++                log_error!(ErrorLevel::Trace, "valid uuid -> {:?}", r);
++                r
++            })
++            .map_err(|_e| {
++                log_error!(ErrorLevel::Plugin, "Invalid uuid");
++                PluginError::InvalidSyntax
++            })
++    }
++}
+diff --git a/src/slapi_r_plugin/src/charray.rs b/src/slapi_r_plugin/src/charray.rs
+new file mode 100644
+index 000000000..9da66534f
+--- /dev/null
++++ b/src/slapi_r_plugin/src/charray.rs
+@@ -0,0 +1,32 @@
++use std::ffi::CString;
++use std::iter::once;
++use std::os::raw::c_char;
++use std::ptr;
++
++pub struct Charray {
++    _pin: Vec<CString>,
++    charray: Vec<*const c_char>,
++}
++
++impl Charray {
++    pub fn new(input: &[&str]) -> Result<Self, ()> {
++        let pin: Result<Vec<_>, ()> = input
++            .iter()
++            .map(|s| CString::new(*s).map_err(|_e| ()))
++            .collect();
++
++        let pin = pin?;
++
++        let charray: Vec<_> = pin
++            .iter()
++            .map(|s| s.as_ptr())
++            .chain(once(ptr::null()))
++            .collect();
++
++        Ok(Charray { _pin: pin, charray })
++    }
++
++    pub fn as_ptr(&self) -> *const *const c_char {
++        self.charray.as_ptr()
++    }
++}
+diff --git a/src/slapi_r_plugin/src/constants.rs b/src/slapi_r_plugin/src/constants.rs
+new file mode 100644
+index 000000000..1da9bcb70
+--- /dev/null
++++ b/src/slapi_r_plugin/src/constants.rs
+@@ -0,0 +1,212 @@
++use crate::error::RPluginError;
++use std::convert::TryFrom;
++use std::os::raw::c_char;
++
++pub const LDAP_SUCCESS: i32 = 0;
++pub const PLUGIN_DEFAULT_PRECEDENCE: i32 = 50;
++
++#[repr(i32)]
++/// The set of possible function handles we can register via the pblock. These
++/// values correspond to slapi-plugin.h.
++pub enum PluginFnType {
++    /// SLAPI_PLUGIN_DESTROY_FN
++    Destroy = 11,
++    /// SLAPI_PLUGIN_CLOSE_FN
++    Close = 210,
++    /// SLAPI_PLUGIN_START_FN
++    Start = 212,
++    /// SLAPI_PLUGIN_PRE_BIND_FN
++    PreBind = 401,
++    /// SLAPI_PLUGIN_PRE_UNBIND_FN
++    PreUnbind = 402,
++    /// SLAPI_PLUGIN_PRE_SEARCH_FN
++    PreSearch = 403,
++    /// SLAPI_PLUGIN_PRE_COMPARE_FN
++    PreCompare = 404,
++    /// SLAPI_PLUGIN_PRE_MODIFY_FN
++    PreModify = 405,
++    /// SLAPI_PLUGIN_PRE_MODRDN_FN
++    PreModRDN = 406,
++    /// SLAPI_PLUGIN_PRE_ADD_FN
++    PreAdd = 407,
++    /// SLAPI_PLUGIN_PRE_DELETE_FN
++    PreDelete = 408,
++    /// SLAPI_PLUGIN_PRE_ABANDON_FN
++    PreAbandon = 409,
++    /// SLAPI_PLUGIN_PRE_ENTRY_FN
++    PreEntry = 410,
++    /// SLAPI_PLUGIN_PRE_REFERRAL_FN
++    PreReferal = 411,
++    /// SLAPI_PLUGIN_PRE_RESULT_FN
++    PreResult = 412,
++    /// SLAPI_PLUGIN_PRE_EXTOP_FN
++    PreExtop = 413,
++    /// SLAPI_PLUGIN_BE_PRE_ADD_FN
++    BeTxnPreAdd = 460,
++    /// SLAPI_PLUGIN_BE_TXN_PRE_MODIFY_FN
++    BeTxnPreModify = 461,
++    /// SLAPI_PLUGIN_BE_TXN_PRE_MODRDN_FN
++    BeTxnPreModRDN = 462,
++    /// SLAPI_PLUGIN_BE_TXN_PRE_DELETE_FN
++    BeTxnPreDelete = 463,
++    /// SLAPI_PLUGIN_BE_TXN_PRE_DELETE_TOMBSTONE_FN
++    BeTxnPreDeleteTombstone = 464,
++    /// SLAPI_PLUGIN_POST_SEARCH_FN
++    PostSearch = 503,
++    /// SLAPI_PLUGIN_BE_POST_ADD_FN
++    BeTxnPostAdd = 560,
++    /// SLAPI_PLUGIN_BE_POST_MODIFY_FN
++    BeTxnPostModify = 561,
++    /// SLAPI_PLUGIN_BE_POST_MODRDN_FN
++    BeTxnPostModRDN = 562,
++    /// SLAPI_PLUGIN_BE_POST_DELETE_FN
++    BeTxnPostDelete = 563,
++
++    /// SLAPI_PLUGIN_MR_FILTER_CREATE_FN
++    MRFilterCreate = 600,
++    /// SLAPI_PLUGIN_MR_INDEXER_CREATE_FN
++    MRIndexerCreate = 601,
++    /// SLAPI_PLUGIN_MR_FILTER_AVA
++    MRFilterAva = 618,
++    /// SLAPI_PLUGIN_MR_FILTER_SUB
++    MRFilterSub = 619,
++    /// SLAPI_PLUGIN_MR_VALUES2KEYS
++    MRValuesToKeys = 620,
++    /// SLAPI_PLUGIN_MR_ASSERTION2KEYS_AVA
++    MRAssertionToKeysAva = 621,
++    /// SLAPI_PLUGIN_MR_ASSERTION2KEYS_SUB
++    MRAssertionToKeysSub = 622,
++    /// SLAPI_PLUGIN_MR_COMPARE
++    MRCompare = 625,
++    /// SLAPI_PLUGIN_MR_NORMALIZE
++    MRNormalize = 626,
++
++    /// SLAPI_PLUGIN_SYNTAX_FILTER_AVA
++    SyntaxFilterAva = 700,
++    /// SLAPI_PLUGIN_SYNTAX_FILTER_SUB
++    SyntaxFilterSub = 701,
++    /// SLAPI_PLUGIN_SYNTAX_VALUES2KEYS
++    SyntaxValuesToKeys = 702,
++    /// SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_AVA
++    SyntaxAssertion2KeysAva = 703,
++    /// SLAPI_PLUGIN_SYNTAX_ASSERTION2KEYS_SUB
++    SyntaxAssertion2KeysSub = 704,
++    /// SLAPI_PLUGIN_SYNTAX_FLAGS
++    SyntaxFlags = 707,
++    /// SLAPI_PLUGIN_SYNTAX_COMPARE
++    SyntaxCompare = 708,
++    /// SLAPI_PLUGIN_SYNTAX_VALIDATE
++    SyntaxValidate = 710,
++    /// SLAPI_PLUGIN_SYNTAX_NORMALIZE
++    SyntaxNormalize = 711,
++
++    /// SLAPI_PLUGIN_PWD_STORAGE_SCHEME_ENC_FN
++    PwdStorageEncrypt = 800,
++    /// SLAPI_PLUGIN_PWD_STORAGE_SCHEME_CMP_FN
++    PwdStorageCompare = 802,
++}
++
++static SV01: [u8; 3] = [b'0', b'1', b'\0'];
++static SV02: [u8; 3] = [b'0', b'2', b'\0'];
++static SV03: [u8; 3] = [b'0', b'3', b'\0'];
++
++/// Corresponding plugin versions
++pub enum PluginVersion {
++    /// SLAPI_PLUGIN_VERSION_01
++    V01,
++    /// SLAPI_PLUGIN_VERSION_02
++    V02,
++    /// SLAPI_PLUGIN_VERSION_03
++    V03,
++}
++
++impl PluginVersion {
++    pub fn to_char_ptr(&self) -> *const c_char {
++        match self {
++            PluginVersion::V01 => &SV01 as *const _ as *const c_char,
++            PluginVersion::V02 => &SV02 as *const _ as *const c_char,
++            PluginVersion::V03 => &SV03 as *const _ as *const c_char,
++        }
++    }
++}
++
++static SMATCHINGRULE: [u8; 13] = [
++    b'm', b'a', b't', b'c', b'h', b'i', b'n', b'g', b'r', b'u', b'l', b'e', b'\0',
++];
++
++pub enum PluginType {
++    MatchingRule,
++}
++
++impl PluginType {
++    pub fn to_char_ptr(&self) -> *const c_char {
++        match self {
++            PluginType::MatchingRule => &SMATCHINGRULE as *const _ as *const c_char,
++        }
++    }
++}
++
++#[repr(i32)]
++/// data types that we can get or retrieve from the pblock. This is only
++/// used internally.
++pub(crate) enum PblockType {
++    /// SLAPI_PLUGIN_PRIVATE
++    _PrivateData = 4,
++    /// SLAPI_PLUGIN_VERSION
++    Version = 8,
++    /// SLAPI_PLUGIN_DESCRIPTION
++    _Description = 12,
++    /// SLAPI_PLUGIN_IDENTITY
++    Identity = 13,
++    /// SLAPI_PLUGIN_INTOP_RESULT
++    OpResult = 15,
++    /// SLAPI_ADD_ENTRY
++    AddEntry = 60,
++    /// SLAPI_BACKEND
++    Backend = 130,
++    /// SLAPI_IS_REPLICATED_OPERATION
++    IsReplicationOperation = 142,
++    /// SLAPI_PLUGIN_MR_NAMES
++    MRNames = 624,
++    /// SLAPI_PLUGIN_SYNTAX_NAMES
++    SyntaxNames = 705,
++    /// SLAPI_PLUGIN_SYNTAX_OID
++    SyntaxOid = 706,
++    /// SLAPI_PLUGIN_PWD_STORAGE_SCHEME_NAME
++    PwdStorageSchemeName = 810,
++}
++
++/// See ./ldap/include/ldaprot.h
++#[derive(PartialEq)]
++pub enum FilterType {
++    And = 0xa0,
++    Or = 0xa1,
++    Not = 0xa2,
++    Equality = 0xa3,
++    Substring = 0xa4,
++    Ge = 0xa5,
++    Le = 0xa6,
++    Present = 0x87,
++    Approx = 0xa8,
++    Extended = 0xa9,
++}
++
++impl TryFrom<i32> for FilterType {
++    type Error = RPluginError;
++
++    fn try_from(value: i32) -> Result<Self, Self::Error> {
++        match value {
++            0xa0 => Ok(FilterType::And),
++            0xa1 => Ok(FilterType::Or),
++            0xa2 => Ok(FilterType::Not),
++            0xa3 => Ok(FilterType::Equality),
++            0xa4 => Ok(FilterType::Substring),
++            0xa5 => Ok(FilterType::Ge),
++            0xa6 => Ok(FilterType::Le),
++            0x87 => Ok(FilterType::Present),
++            0xa8 => Ok(FilterType::Approx),
++            0xa9 => Ok(FilterType::Extended),
++            _ => Err(RPluginError::FilterType),
++        }
++    }
++}
+diff --git a/src/slapi_r_plugin/src/dn.rs b/src/slapi_r_plugin/src/dn.rs
+new file mode 100644
+index 000000000..5f8a65743
+--- /dev/null
++++ b/src/slapi_r_plugin/src/dn.rs
+@@ -0,0 +1,108 @@
++use std::convert::TryFrom;
++use std::ffi::{CStr, CString};
++use std::ops::Deref;
++use std::os::raw::c_char;
++
++extern "C" {
++    fn slapi_sdn_get_dn(sdn: *const libc::c_void) -> *const c_char;
++    fn slapi_sdn_new_dn_byval(dn: *const c_char) -> *const libc::c_void;
++    fn slapi_sdn_issuffix(sdn: *const libc::c_void, suffix_sdn: *const libc::c_void) -> i32;
++    fn slapi_sdn_free(sdn: *const *const libc::c_void);
++    fn slapi_sdn_dup(sdn: *const libc::c_void) -> *const libc::c_void;
++}
++
++#[derive(Debug)]
++pub struct SdnRef {
++    raw_sdn: *const libc::c_void,
++}
++
++#[derive(Debug)]
++pub struct NdnRef {
++    raw_ndn: *const c_char,
++}
++
++#[derive(Debug)]
++pub struct Sdn {
++    value: SdnRef,
++}
++
++unsafe impl Send for Sdn {}
++
++impl From<&CStr> for Sdn {
++    fn from(value: &CStr) -> Self {
++        Sdn {
++            value: SdnRef {
++                raw_sdn: unsafe { slapi_sdn_new_dn_byval(value.as_ptr()) },
++            },
++        }
++    }
++}
++
++impl TryFrom<&str> for Sdn {
++    type Error = ();
++
++    fn try_from(value: &str) -> Result<Self, Self::Error> {
++        let cstr = CString::new(value).map_err(|_| ())?;
++        Ok(Self::from(cstr.as_c_str()))
++    }
++}
++
++impl Clone for Sdn {
++    fn clone(&self) -> Self {
++        let raw_sdn = unsafe { slapi_sdn_dup(self.value.raw_sdn) };
++        Sdn {
++            value: SdnRef { raw_sdn },
++        }
++    }
++}
++
++impl Drop for Sdn {
++    fn drop(&mut self) {
++        unsafe { slapi_sdn_free(&self.value.raw_sdn as *const *const libc::c_void) }
++    }
++}
++
++impl Deref for Sdn {
++    type Target = SdnRef;
++
++    fn deref(&self) -> &Self::Target {
++        &self.value
++    }
++}
++
++impl SdnRef {
++    pub fn new(raw_sdn: *const libc::c_void) -> Self {
++        SdnRef { raw_sdn }
++    }
++
++    /// This is unsafe, as you need to ensure that the SdnRef associated lives at
++    /// least as long as the NdnRef, else this may cause a use-after-free.
++    pub unsafe fn as_ndnref(&self) -> NdnRef {
++        let raw_ndn = slapi_sdn_get_dn(self.raw_sdn);
++        NdnRef { raw_ndn }
++    }
++
++    pub fn to_dn_string(&self) -> String {
++        let dn_raw = unsafe { slapi_sdn_get_dn(self.raw_sdn) };
++        let dn_cstr = unsafe { CStr::from_ptr(dn_raw) };
++        dn_cstr.to_string_lossy().to_string()
++    }
++
++    pub(crate) fn as_ptr(&self) -> *const libc::c_void {
++        self.raw_sdn
++    }
++
++    pub fn is_below_suffix(&self, other: &SdnRef) -> bool {
++        if unsafe { slapi_sdn_issuffix(self.raw_sdn, other.raw_sdn) } == 0 {
++            false
++        } else {
++            true
++        }
++    }
++}
++
++impl NdnRef {
++    pub(crate) fn as_ptr(&self) -> *const c_char {
++        self.raw_ndn
++    }
++}
+diff --git a/src/slapi_r_plugin/src/entry.rs b/src/slapi_r_plugin/src/entry.rs
+new file mode 100644
+index 000000000..22ae45189
+--- /dev/null
++++ b/src/slapi_r_plugin/src/entry.rs
+@@ -0,0 +1,100 @@
++use crate::dn::SdnRef;
++use crate::value::{slapi_value, ValueArrayRef, ValueRef};
++use std::ffi::CString;
++use std::os::raw::c_char;
++
++extern "C" {
++    fn slapi_entry_get_sdn(e: *const libc::c_void) -> *const libc::c_void;
++    fn slapi_entry_add_value(
++        e: *const libc::c_void,
++        a: *const c_char,
++        v: *const slapi_value,
++    ) -> i32;
++    fn slapi_entry_attr_get_valuearray(
++        e: *const libc::c_void,
++        a: *const c_char,
++    ) -> *const *const slapi_value;
++}
++
++pub struct EntryRef {
++    raw_e: *const libc::c_void,
++}
++
++/*
++pub struct Entry {
++    value: EntryRef,
++}
++
++impl Drop for Entry {
++    fn drop(&mut self) {
++        ()
++    }
++}
++
++impl Deref for Entry {
++    type Target = EntryRef;
++
++    fn deref(&self) -> &Self::Target {
++        &self.value
++    }
++}
++
++impl Entry {
++    // Forget about this value, and get a pointer back suitable for providing to directory
++    // server to take ownership.
++    pub unsafe fn forget(self) -> *mut libc::c_void {
++        unimplemented!();
++    }
++}
++*/
++
++impl EntryRef {
++    pub fn new(raw_e: *const libc::c_void) -> Self {
++        EntryRef { raw_e }
++    }
++
++    // get the sdn
++    pub fn get_sdnref(&self) -> SdnRef {
++        let sdn_ptr = unsafe { slapi_entry_get_sdn(self.raw_e) };
++        SdnRef::new(sdn_ptr)
++    }
++
++    pub fn get_attr(&self, name: &str) -> Option<ValueArrayRef> {
++        let cname = CString::new(name).expect("invalid attr name");
++        let va = unsafe { slapi_entry_attr_get_valuearray(self.raw_e, cname.as_ptr()) };
++
++        if va.is_null() {
++            None
++        } else {
++            Some(ValueArrayRef::new(va as *const libc::c_void))
++        }
++    }
++
++    pub fn contains_attr(&self, name: &str) -> bool {
++        let cname = CString::new(name).expect("invalid attr name");
++        let va = unsafe { slapi_entry_attr_get_valuearray(self.raw_e, cname.as_ptr()) };
++
++        // If it's null, it's not present, so flip the logic.
++        !va.is_null()
++    }
++
++    pub fn add_value(&mut self, a: &str, v: &ValueRef) {
++        // turn the attr to a c string.
++        // TODO FIX
++        let attr_name = CString::new(a).expect("Invalid attribute name");
++        // Get the raw ptr.
++        let raw_value_ref = unsafe { v.as_ptr() };
++        // We ignore the return because it always returns 0.
++        let _ = unsafe {
++            // By default, this clones.
++            slapi_entry_add_value(self.raw_e, attr_name.as_ptr(), raw_value_ref)
++        };
++    }
++
++    /*
++    pub fn replace_value(&mut self, a: &str, v: &ValueRef) {
++        // slapi_entry_attr_replace(e, SLAPI_ATTR_ENTRYUSN, new_bvals);
++        unimplemented!();
++    }
++    */
++}
+diff --git a/src/slapi_r_plugin/src/error.rs b/src/slapi_r_plugin/src/error.rs
+new file mode 100644
+index 000000000..228568efa
+--- /dev/null
++++ b/src/slapi_r_plugin/src/error.rs
+@@ -0,0 +1,66 @@
++// use std::convert::TryFrom;
++
++#[derive(Debug, PartialEq, Eq)]
++#[repr(i32)]
++pub enum RPluginError {
++    Unknown = 500,
++    Unimplemented = 501,
++    FilterType = 502,
++}
++
++#[derive(Debug, PartialEq, Eq)]
++#[repr(i32)]
++pub enum PluginError {
++    GenericFailure = -1,
++    Unknown = 1000,
++    Unimplemented = 1001,
++    Pblock = 1002,
++    BervalString = 1003,
++    InvalidSyntax = 1004,
++    InvalidFilter = 1005,
++    TxnFailure = 1006,
++    MissingValue = 1007,
++    InvalidStrToInt = 1008,
++    InvalidBase64 = 1009,
++    OpenSSL = 1010,
++    Format = 1011,
++}
++
++#[derive(Debug)]
++#[repr(i32)]
++pub enum LDAPError {
++    Success = 0,
++    Operation = 1,
++    ObjectClassViolation = 65,
++    Other = 80,
++    Unknown = 999,
++}
++
++impl From<i32> for LDAPError {
++    fn from(value: i32) -> Self {
++        match value {
++            0 => LDAPError::Success,
++            1 => LDAPError::Operation,
++            65 => LDAPError::ObjectClassViolation,
++            80 => LDAPError::Other,
++            _ => LDAPError::Unknown,
++        }
++    }
++}
++
++// if we make debug impl, we can use this.
++// errmsg = ldap_err2string(result);
++
++#[derive(Debug)]
++#[repr(i32)]
++pub enum DseCallbackStatus {
++    DoNotApply = 0,
++    Ok = 1,
++    Error = -1,
++}
++
++#[derive(Debug)]
++pub enum LoggingError {
++    Unknown,
++    CString(String),
++}
+diff --git a/src/slapi_r_plugin/src/init.c b/src/slapi_r_plugin/src/init.c
+new file mode 100644
+index 000000000..86d1235b8
+--- /dev/null
++++ b/src/slapi_r_plugin/src/init.c
+@@ -0,0 +1,8 @@
++
++#include <inttypes.h>
++
++int32_t
++do_nothing_really_well_abcdef() {
++    return 0;
++}
++
+diff --git a/src/slapi_r_plugin/src/lib.rs b/src/slapi_r_plugin/src/lib.rs
+new file mode 100644
+index 000000000..542a31397
+--- /dev/null
++++ b/src/slapi_r_plugin/src/lib.rs
+@@ -0,0 +1,41 @@
++#![deny(warnings)]
++// #[macro_use]
++// extern crate lazy_static;
++
++#[macro_use]
++pub mod macros;
++pub mod backend;
++pub mod ber;
++pub mod charray;
++mod constants;
++pub mod dn;
++pub mod entry;
++pub mod error;
++pub mod log;
++pub mod modify;
++pub mod pblock;
++pub mod plugin;
++pub mod search;
++pub mod syntax_plugin;
++pub mod task;
++pub mod value;
++
++pub mod prelude {
++    pub use crate::backend::{BackendRef, BackendRefTxn};
++    pub use crate::ber::BerValRef;
++    pub use crate::charray::Charray;
++    pub use crate::constants::{FilterType, PluginFnType, PluginType, PluginVersion, LDAP_SUCCESS};
++    pub use crate::dn::{Sdn, SdnRef};
++    pub use crate::entry::EntryRef;
++    pub use crate::error::{DseCallbackStatus, LDAPError, PluginError, RPluginError};
++    pub use crate::log::{log_error, ErrorLevel};
++    pub use crate::modify::{ModType, Modify, SlapiMods};
++    pub use crate::pblock::{Pblock, PblockRef};
++    pub use crate::plugin::{register_plugin_ext, PluginIdRef, SlapiPlugin3};
++    pub use crate::search::{Search, SearchScope};
++    pub use crate::syntax_plugin::{
++        matchingrule_register, SlapiOrdMr, SlapiSubMr, SlapiSyntaxPlugin1,
++    };
++    pub use crate::task::{task_register_handler_fn, task_unregister_handler_fn, Task, TaskRef};
++    pub use crate::value::{Value, ValueArray, ValueArrayRef, ValueRef};
++}
+diff --git a/src/slapi_r_plugin/src/log.rs b/src/slapi_r_plugin/src/log.rs
+new file mode 100644
+index 000000000..f686ecd1a
+--- /dev/null
++++ b/src/slapi_r_plugin/src/log.rs
+@@ -0,0 +1,87 @@
++use std::ffi::CString;
++use std::os::raw::c_char;
++
++use crate::constants;
++use crate::error::LoggingError;
++
++extern "C" {
++    fn slapi_log_error(level: i32, system: *const c_char, message: *const c_char) -> i32;
++}
++
++pub fn log_error(
++    level: ErrorLevel,
++    subsystem: String,
++    message: String,
++) -> Result<(), LoggingError> {
++    let c_subsystem = CString::new(subsystem)
++        .map_err(|e| LoggingError::CString(format!("failed to convert subsystem -> {:?}", e)))?;
++    let c_message = CString::new(message)
++        .map_err(|e| LoggingError::CString(format!("failed to convert message -> {:?}", e)))?;
++
++    match unsafe { slapi_log_error(level as i32, c_subsystem.as_ptr(), c_message.as_ptr()) } {
++        constants::LDAP_SUCCESS => Ok(()),
++        _ => Err(LoggingError::Unknown),
++    }
++}
++
++#[repr(i32)]
++#[derive(Debug)]
++/// This is a safe rust representation of the values from slapi-plugin.h
++/// such as SLAPI_LOG_FATAL, SLAPI_LOG_TRACE, SLAPI_LOG_ ... These vaulues
++/// must matche their counter parts in slapi-plugin.h
++pub enum ErrorLevel {
++    /// Always log messages at this level. Soon to go away, see EMERG, ALERT, CRIT, ERR, WARNING, NOTICE, INFO, DEBUG
++    Fatal = 0,
++    /// Log detailed messages.
++    Trace = 1,
++    /// Log packet tracing.
++    Packets = 2,
++    /// Log argument tracing.
++    Args = 3,
++    /// Log connection tracking.
++    Conns = 4,
++    /// Log BER parsing.
++    Ber = 5,
++    /// Log filter processing.
++    Filter = 6,
++    /// Log configuration processing.
++    Config = 7,
++    /// Log access controls
++    Acl = 8,
++    /// Log .... ???
++    Shell = 9,
++    /// Log .... ???
++    Parse = 10,
++    /// Log .... ???
++    House = 11,
++    /// Log detailed replication information.
++    Repl = 12,
++    /// Log cache management.
++    Cache = 13,
++    /// Log detailed plugin operations.
++    Plugin = 14,
++    /// Log .... ???
++    Timing = 15,
++    /// Log backend infomation.
++    BackLDBM = 16,
++    /// Log ACL processing.
++    AclSummary = 17,
++    /// Log nuncstans processing.
++    NuncStansDONOTUSE = 18,
++    /// Emergency messages. Server is bursting into flame.
++    Emerg = 19,
++    /// Important alerts, server may explode soon.
++    Alert = 20,
++    /// Critical messages, but the server isn't going to explode. Admin should intervene.
++    Crit = 21,
++    /// Error has occured, but we can keep going. Could indicate misconfiguration.
++    Error = 22,
++    /// Warning about an issue that isn't very important. Good to resolve though.
++    Warning = 23,
++    /// Inform the admin of something that they should know about, IE server is running now.
++    Notice = 24,
++    /// Informational messages that are nice to know.
++    Info = 25,
++    /// Debugging information from the server.
++    Debug = 26,
++}
+diff --git a/src/slapi_r_plugin/src/macros.rs b/src/slapi_r_plugin/src/macros.rs
+new file mode 100644
+index 000000000..7d72e3720
+--- /dev/null
++++ b/src/slapi_r_plugin/src/macros.rs
+@@ -0,0 +1,940 @@
++#[macro_export]
++#[cfg(not(feature = "test_log_direct"))]
++macro_rules! log_error {
++    ($level:expr, $($arg:tt)*) => ({
++        use std::fmt;
++        match log_error(
++            $level,
++            format!("{}:{}", file!(), line!()),
++            format!("{}\n", fmt::format(format_args!($($arg)*)))
++        ) {
++            Ok(_) => {},
++            Err(e) => {
++                eprintln!("A logging error occured {}, {} -> {:?}", file!(), line!(), e);
++            }
++        };
++    })
++}
++
++#[macro_export]
++#[cfg(feature = "test_log_direct")]
++macro_rules! log_error {
++    ($level:expr, $($arg:tt)*) => ({
++        use std::fmt;
++        eprintln!("{}:{} -> {}", file!(), line!(), fmt::format(format_args!($($arg)*)));
++    })
++}
++
++#[macro_export]
++macro_rules! slapi_r_plugin_hooks {
++    ($mod_ident:ident, $hooks_ident:ident) => (
++        paste::item! {
++            use libc;
++            use std::ffi::{CString, CStr};
++
++            static mut [<$mod_ident _PLUGINID>]: *const libc::c_void = std::ptr::null();
++
++            pub(crate) fn plugin_id() -> PluginIdRef {
++                PluginIdRef {
++                    raw_pid: unsafe { [<$mod_ident _PLUGINID>] }
++                }
++            }
++
++            #[no_mangle]
++            pub extern "C" fn [<$mod_ident _plugin_init>](raw_pb: *const libc::c_void) -> i32 {
++                let mut pb = PblockRef::new(raw_pb);
++                log_error!(ErrorLevel::Trace, "it's alive!\n");
++
++                match pb.set_plugin_version(PluginVersion::V03) {
++                    0 => {},
++                    e => return e,
++                };
++
++                // Setup the plugin id.
++                unsafe {
++                    [<$mod_ident _PLUGINID>] = pb.get_plugin_identity();
++                }
++
++                if $hooks_ident::has_betxn_pre_modify() {
++                    match pb.register_betxn_pre_modify_fn([<$mod_ident _plugin_betxn_pre_modify>]) {
++                        0 => {},
++                        e => return e,
++                    };
++                }
++
++                if $hooks_ident::has_betxn_pre_add() {
++                    match pb.register_betxn_pre_add_fn([<$mod_ident _plugin_betxn_pre_add>]) {
++                        0 => {},
++                        e => return e,
++                    };
++                }
++
++                if $hooks_ident::has_pwd_storage() {
++                    // SLAPI_PLUGIN_DESCRIPTION pbkdf2_sha256_pdesc
++                    match pb.register_pwd_storage_encrypt_fn([<$mod_ident _plugin_pwd_storage_encrypt_fn>]) {
++                        0 => {},
++                        e => return e,
++                    };
++                    match pb.register_pwd_storage_compare_fn([<$mod_ident _plugin_pwd_storage_compare_fn>]) {
++                        0 => {},
++                        e => return e,
++                    };
++                    let scheme_name = CString::new($hooks_ident::pwd_scheme_name()).expect("invalid password scheme name");
++                    // DS strdups this for us, so it owns it now.
++                    match pb.register_pwd_storage_scheme_name(scheme_name.as_ptr()) {
++                        0 => {},
++                        e => return e,
++                    };
++
++                }
++
++                // set the start fn
++                match pb.register_start_fn([<$mod_ident _plugin_start>]) {
++                    0 => {},
++                    e => return e,
++                };
++
++                // set the close fn
++                match pb.register_close_fn([<$mod_ident _plugin_close>]) {
++                    0 => {},
++                    e => return e,
++                };
++
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_start>](raw_pb: *const libc::c_void) -> i32 {
++                let mut pb = PblockRef::new(raw_pb);
++
++                if let Some(task_ident) = $hooks_ident::has_task_handler() {
++                    match task_register_handler_fn(task_ident, [<$mod_ident _plugin_task_handler>], &mut pb) {
++                        0 => {},
++                        e => return e,
++                    };
++                };
++
++                match $hooks_ident::start(&mut pb) {
++                    Ok(()) => {
++                        0
++                    }
++                    Err(e) => {
++                        log_error!(ErrorLevel::Error, "-> {:?}", e);
++                        1
++                    }
++                }
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_close>](raw_pb: *const libc::c_void) -> i32 {
++                let mut pb = PblockRef::new(raw_pb);
++
++                if let Some(task_ident) = $hooks_ident::has_task_handler() {
++                    match task_unregister_handler_fn(task_ident, [<$mod_ident _plugin_task_handler>]) {
++                        0 => {},
++                        e => return e,
++                    };
++                };
++
++                match $hooks_ident::close(&mut pb) {
++                    Ok(()) => {
++                        0
++                    }
++                    Err(e) => {
++                        log_error!(ErrorLevel::Error, "-> {:?}", e);
++                        1
++                    }
++                }
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_betxn_pre_modify>](raw_pb: *const libc::c_void) -> i32 {
++                let mut pb = PblockRef::new(raw_pb);
++                match $hooks_ident::betxn_pre_modify(&mut pb) {
++                    Ok(()) => {
++                        0
++                    }
++                    Err(e) => {
++                        log_error!(ErrorLevel::Error, "-> {:?}", e);
++                        1
++                    }
++                }
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_betxn_pre_add>](raw_pb: *const libc::c_void) -> i32 {
++                let mut pb = PblockRef::new(raw_pb);
++                match $hooks_ident::betxn_pre_add(&mut pb) {
++                    Ok(()) => {
++                        0
++                    }
++                    Err(e) => {
++                        log_error!(ErrorLevel::Error, "-> {:?}", e);
++                        1
++                    }
++                }
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_task_handler>](
++                raw_pb: *const libc::c_void,
++                raw_e_before: *const libc::c_void,
++                _raw_e_after: *const libc::c_void,
++                raw_returncode: *mut i32,
++                _raw_returntext: *mut c_char,
++                raw_arg: *const libc::c_void,
++            ) -> i32 {
++                let mut pb = PblockRef::new(raw_pb);
++
++                let e_before = EntryRef::new(raw_e_before);
++                // let e_after = EntryRef::new(raw_e_after);
++
++                let task_data = match $hooks_ident::task_validate(
++                    &e_before
++                ) {
++                    Ok(data) => data,
++                    Err(retcode) => {
++                        unsafe { *raw_returncode = retcode as i32 };
++                        return DseCallbackStatus::Error as i32
++                    }
++                };
++
++                let mut task = Task::new(&e_before, raw_arg);
++                task.register_destructor_fn([<$mod_ident _plugin_task_destructor>]);
++
++                // Setup the task thread and then run it. Remember, because Rust is
++                // smarter about memory, the move statement here moves the task wrapper and
++                // task_data to the thread, so they drop on thread close. No need for a
++                // destructor beyond blocking on the thread to complete.
++                std::thread::spawn(move || {
++                    log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_task_thread => begin"));
++                    // Indicate the task is begun
++                    task.begin();
++                    // Start a txn
++                    let be: Option<BackendRef> = match $hooks_ident::task_be_dn_hint(&task_data)
++                        .map(|be_dn| {
++                            BackendRef::new(&be_dn)
++                        })
++                        .transpose() {
++                            Ok(v) => v,
++                            Err(_) => {
++                                log_error!(ErrorLevel::Error, concat!(stringify!($mod_ident), "_plugin_task_thread => task error -> selected dn does not exist"));
++                                task.error(PluginError::TxnFailure as i32);
++                                return;
++                            }
++                        };
++                    let be_txn: Option<BackendRefTxn> = match be {
++                        Some(b) => {
++                            match b.begin_txn() {
++                                Ok(txn) => Some(txn),
++                                Err(_) => {
++                                    log_error!(ErrorLevel::Error, concat!(stringify!($mod_ident), "_plugin_task_thread => task error -> unable to begin txn"));
++                                    task.error(PluginError::TxnFailure as i32);
++                                    return;
++                                }
++                            }
++                        }
++                        None => None,
++                    };
++
++                    // Abort or commit the txn here.
++                    match $hooks_ident::task_handler(&mut task, task_data) {
++                        Ok(_data) => {
++                            match be_txn {
++                                Some(be_txn) => be_txn.commit(),
++                                None => {}
++                            };
++                            // These will set the status, and guarantee the drop
++                            task.success();
++                        }
++                        Err(e) => {
++                            log_error!(ErrorLevel::Error, "{}_plugin_task_thread => task error -> {:?}", stringify!($mod_ident), e);
++                            // These will set the status, and guarantee the drop
++                            task.error(e as i32);
++                            // On drop, be_txn implicitly aborts.
++                        }
++                    };
++
++                    log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_task_thread <= complete"));
++                });
++
++                // Indicate that the thread started just fine.
++                unsafe { *raw_returncode = LDAP_SUCCESS };
++                DseCallbackStatus::Ok as i32
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_task_destructor>](
++                raw_task: *const libc::c_void,
++            ) {
++                // Simply block until the task refcount drops to 0.
++                let task = TaskRef::new(raw_task);
++                task.block();
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_pwd_storage_encrypt_fn>](
++                cleartext: *const c_char,
++            ) -> *const c_char {
++                let cleartext = unsafe { CStr::from_ptr(cleartext) };
++                let clear_str = match cleartext.to_str() {
++                    Ok(s) => s,
++                    Err(e) => {
++                        log_error!(ErrorLevel::Error, "{}_plugin_pwd_storage_encrypt_fn => error -> {:?}", stringify!($mod_ident), e);
++                        return std::ptr::null();
++                    }
++                };
++                match $hooks_ident::pwd_storage_encrypt(clear_str)
++                    // do some conversions.
++                    .and_then(|s| {
++                        // DS needs to own the returned value, so we have to alloc it for ds.
++                        CString::new(s)
++                            .map_err(|e| {
++                                PluginError::GenericFailure
++                            })
++                            .map(|cs| {
++                                unsafe { libc::strdup(cs.as_ptr()) }
++                            })
++                    })
++                {
++                    Ok(s_ptr) => {
++                        s_ptr
++                    }
++                    Err(e) => {
++                        log_error!(ErrorLevel::Error, "{}_plugin_pwd_storage_encrypt_fn => error -> {:?}", stringify!($mod_ident), e);
++                        std::ptr::null()
++                    }
++                }
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_pwd_storage_compare_fn>](
++                cleartext: *const c_char,
++                encrypted: *const c_char,
++            ) -> i32 {
++                // 1 is failure, 0 success.
++                let cleartext = unsafe { CStr::from_ptr(cleartext) };
++                let clear_str = match cleartext.to_str() {
++                    Ok(s) => s,
++                    Err(e) => {
++                        log_error!(ErrorLevel::Error, "{}_plugin_pwd_storage_compare_fn => error -> {:?}", stringify!($mod_ident), e);
++                        return 1;
++                    }
++                };
++
++                let encrypted = unsafe { CStr::from_ptr(encrypted) };
++                let enc_str = match encrypted.to_str() {
++                    Ok(s) => s,
++                    Err(e) => {
++                        log_error!(ErrorLevel::Error, "{}_plugin_pwd_storage_compare_fn => error -> {:?}", stringify!($mod_ident), e);
++                        return 1;
++                    }
++                };
++
++                match $hooks_ident::pwd_storage_compare(clear_str, enc_str) {
++                    Ok(r) => {
++                        if r {
++                            0
++                        } else {
++                            1
++                        }
++                    }
++                    Err(e) => {
++                        log_error!(ErrorLevel::Error, "{}_plugin_pwd_storage_compare_fn => error -> {:?}", stringify!($mod_ident), e);
++                        1
++                    }
++                }
++            }
++
++        } // end paste
++    )
++} // end macro
++
++#[macro_export]
++macro_rules! slapi_r_syntax_plugin_hooks {
++    (
++        $mod_ident:ident,
++        $hooks_ident:ident
++    ) => (
++        paste::item! {
++            use libc;
++            use std::convert::TryFrom;
++            use std::ffi::CString;
++
++            #[no_mangle]
++            pub extern "C" fn [<$mod_ident _plugin_init>](raw_pb: *const libc::c_void) -> i32 {
++                let mut pb = PblockRef::new(raw_pb);
++                log_error!(ErrorLevel::Trace, "slapi_r_syntax_plugin_hooks => begin");
++                // Setup our plugin
++                match pb.set_plugin_version(PluginVersion::V01) {
++                    0 => {},
++                    e => return e,
++                };
++
++                // Setup the names/oids that this plugin provides syntaxes for.
++                // DS will clone these, so they can be ephemeral to this function.
++                let name_vec = Charray::new($hooks_ident::attr_supported_names().as_slice()).expect("invalid supported names");
++                match pb.register_syntax_names(name_vec.as_ptr()) {
++                    0 => {},
++                    e => return e,
++                };
++
++                let attr_oid = CString::new($hooks_ident::attr_oid()).expect("invalid attr oid");
++                match pb.register_syntax_oid(attr_oid.as_ptr()) {
++                    0 => {},
++                    e => return e,
++                };
++
++                match pb.register_syntax_validate_fn([<$mod_ident _plugin_syntax_validate>]) {
++                    0 => {},
++                    e => return e,
++                };
++
++                // Now setup the MR's
++                match register_plugin_ext(
++                    PluginType::MatchingRule,
++                    $hooks_ident::eq_mr_name(),
++                    concat!(stringify!($mod_ident), "_plugin_eq_mr_init"),
++                    [<$mod_ident _plugin_eq_mr_init>]
++                ) {
++                    0 => {},
++                    e => return e,
++                };
++
++                if $hooks_ident::sub_mr_oid().is_some() {
++                    match register_plugin_ext(
++                        PluginType::MatchingRule,
++                        $hooks_ident::sub_mr_name(),
++                        concat!(stringify!($mod_ident), "_plugin_ord_mr_init"),
++                        [<$mod_ident _plugin_ord_mr_init>]
++                    ) {
++                        0 => {},
++                        e => return e,
++                    };
++                }
++
++                if $hooks_ident::ord_mr_oid().is_some() {
++                    match register_plugin_ext(
++                        PluginType::MatchingRule,
++                        $hooks_ident::ord_mr_name(),
++                        concat!(stringify!($mod_ident), "_plugin_ord_mr_init"),
++                        [<$mod_ident _plugin_ord_mr_init>]
++                    ) {
++                        0 => {},
++                        e => return e,
++                    };
++                }
++
++                log_error!(ErrorLevel::Trace, "slapi_r_syntax_plugin_hooks <= success");
++
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_syntax_validate>](
++                raw_berval: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_syntax_validate => begin"));
++
++                let bval = BerValRef::new(raw_berval);
++
++                match $hooks_ident::syntax_validate(&bval) {
++                    Ok(()) => {
++                        log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_syntax_validate <= success"));
++                        LDAP_SUCCESS
++                    }
++                    Err(e) => {
++                        log_error!(ErrorLevel::Warning,
++                            "{}_plugin_syntax_validate error -> {:?}", stringify!($mod_ident), e
++                        );
++                        e as i32
++                    }
++                }
++            }
++
++            // All the MR types share this.
++            pub extern "C" fn [<$mod_ident _plugin_mr_filter_ava>](
++                raw_pb: *const libc::c_void,
++                raw_bvfilter: *const libc::c_void,
++                raw_bvals: *const libc::c_void,
++                i_ftype: i32,
++                _retval: *mut libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_mr_filter_ava => begin"));
++                let mut pb = PblockRef::new(raw_pb);
++                let bvfilter = BerValRef::new(raw_bvfilter);
++                let bvals = ValueArrayRef::new(raw_bvals);
++                let ftype = match FilterType::try_from(i_ftype) {
++                    Ok(f) => f,
++                    Err(e) => {
++                        log_error!(ErrorLevel::Error, "{}_plugin_ord_mr_filter_ava Error -> {:?}",
++                        stringify!($mod_ident), e);
++                        return e as i32
++                    }
++                };
++
++                let r: Result<bool, PluginError> = match ftype {
++                    FilterType::And | FilterType::Or | FilterType::Not => {
++                        Err(PluginError::InvalidFilter)
++                    }
++                    FilterType::Equality => {
++                        $hooks_ident::filter_ava_eq(&mut pb, &bvfilter, &bvals)
++                    }
++                    FilterType::Substring => {
++                        Err(PluginError::Unimplemented)
++                    }
++                    FilterType::Ge => {
++                        $hooks_ident::filter_ava_ord(&mut pb, &bvfilter, &bvals)
++                            .map(|o_ord| {
++                                match o_ord {
++                                    Some(Ordering::Greater) | Some(Ordering::Equal) => true,
++                                    Some(Ordering::Less) | None => false,
++                                }
++                            })
++                    }
++                    FilterType::Le => {
++                        $hooks_ident::filter_ava_ord(&mut pb, &bvfilter, &bvals)
++                            .map(|o_ord| {
++                                match o_ord {
++                                    Some(Ordering::Less) | Some(Ordering::Equal) => true,
++                                    Some(Ordering::Greater) | None => false,
++                                }
++                            })
++                    }
++                    FilterType::Present => {
++                        Err(PluginError::Unimplemented)
++                    }
++                    FilterType::Approx => {
++                        Err(PluginError::Unimplemented)
++                    }
++                    FilterType::Extended => {
++                        Err(PluginError::Unimplemented)
++                    }
++                };
++
++                match r {
++                    Ok(b) => {
++                        log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_mr_filter_ava <= success"));
++                        // rust bool into i32 will become 0 false, 1 true. However, ds expects 0 true and 1 false for
++                        // for the filter_ava match. So we flip the bool, and send it back.
++                        (!b) as i32
++                    }
++                    Err(e) => {
++                        log_error!(ErrorLevel::Warning,
++                            "{}_plugin_mr_filter_ava error -> {:?}",
++                            stringify!($mod_ident), e
++                        );
++                        e as i32
++                    }
++                }
++            }
++
++
++            // EQ MR plugin hooks
++            #[no_mangle]
++            pub extern "C" fn [<$mod_ident _plugin_eq_mr_init>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                let mut pb = PblockRef::new(raw_pb);
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_init => begin"));
++                match pb.set_plugin_version(PluginVersion::V01) {
++                    0 => {},
++                    e => return e,
++                };
++
++                let name_vec = Charray::new($hooks_ident::eq_mr_supported_names().as_slice()).expect("invalid mr supported names");
++                let name_ptr = name_vec.as_ptr();
++                // SLAPI_PLUGIN_MR_NAMES
++                match pb.register_mr_names(name_ptr) {
++                    0 => {},
++                    e => return e,
++                };
++
++                // description
++                // SLAPI_PLUGIN_MR_FILTER_CREATE_FN
++                match pb.register_mr_filter_create_fn([<$mod_ident _plugin_eq_mr_filter_create>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_INDEXER_CREATE_FN
++                match pb.register_mr_indexer_create_fn([<$mod_ident _plugin_eq_mr_indexer_create>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_FILTER_AVA
++                match pb.register_mr_filter_ava_fn([<$mod_ident _plugin_mr_filter_ava>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_FILTER_SUB
++                match pb.register_mr_filter_sub_fn([<$mod_ident _plugin_eq_mr_filter_sub>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_VALUES2KEYS
++                match pb.register_mr_values2keys_fn([<$mod_ident _plugin_eq_mr_filter_values2keys>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_ASSERTION2KEYS_AVA
++                match pb.register_mr_assertion2keys_ava_fn([<$mod_ident _plugin_eq_mr_filter_assertion2keys_ava>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_ASSERTION2KEYS_SUB
++                match pb.register_mr_assertion2keys_sub_fn([<$mod_ident _plugin_eq_mr_filter_assertion2keys_sub>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_COMPARE
++                match pb.register_mr_compare_fn([<$mod_ident _plugin_eq_mr_filter_compare>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_NORMALIZE
++
++                // Finaly, register the MR
++                match unsafe { matchingrule_register($hooks_ident::eq_mr_oid(), $hooks_ident::eq_mr_name(), $hooks_ident::eq_mr_desc(), $hooks_ident::attr_oid(), &$hooks_ident::attr_compat_oids()) } {
++                    0 => {},
++                    e => return e,
++                };
++
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_init <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_create>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_create => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_create <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_eq_mr_indexer_create>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_indexer_create => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_indexer_create <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_sub>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_sub => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_sub <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_values2keys>](
++                raw_pb: *const libc::c_void,
++                raw_vals: *const libc::c_void,
++                raw_ivals: *mut libc::c_void,
++                i_ftype: i32,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_values2keys => begin"));
++                let mut pb = PblockRef::new(raw_pb);
++                let vals = ValueArrayRef::new(raw_vals);
++                let ftype = match FilterType::try_from(i_ftype) {
++                    Ok(f) => f,
++                    Err(e) => {
++                        log_error!(ErrorLevel::Error,
++                        "{}_plugin_eq_mr_filter_values2keys Error -> {:?}",
++                        stringify!($mod_ident),
++                        e);
++                        return e as i32
++                    }
++                };
++
++                if (ftype != FilterType::Equality && ftype != FilterType::Approx) {
++                    log_error!(ErrorLevel::Error,
++                        "{}_plugin_eq_mr_filter_values2keys Error -> Invalid Filter type",
++                        stringify!($mod_ident),
++                        );
++                    return PluginError::InvalidFilter  as i32
++                }
++
++                let va = match $hooks_ident::eq_mr_filter_values2keys(&mut pb, &vals) {
++                    Ok(va) => va,
++                    Err(e) => {
++                        log_error!(ErrorLevel::Error,
++                        "{}_plugin_eq_mr_filter_values2keys Error -> {:?}",
++                        stringify!($mod_ident),
++                        e);
++                        return e as i32
++                    }
++                };
++
++                // Now, deconstruct the va, get the pointer, and put it into the ivals.
++                unsafe {
++                    let ivals_ptr: *mut *const libc::c_void = raw_ivals as *mut _;
++                    (*ivals_ptr) = va.take_ownership() as *const libc::c_void;
++                }
++
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_values2keys <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_assertion2keys_ava>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_assertion2keys_ava => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_assertion2keys_ava <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_assertion2keys_sub>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_assertion2keys_sub => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_assertion2keys_sub <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_names>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                // This is probably another char pointer.
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_eq_mr_filter_compare>](
++                raw_va: *const libc::c_void,
++                raw_vb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_compare => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_eq_mr_filter_compare <= success"));
++                0
++            }
++
++            // SUB MR plugin hooks
++
++            pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_create>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_create => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_create <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_sub_mr_indexer_create>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_indexer_create => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_indexer_create <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_sub>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_sub => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_sub <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_values2keys>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_values2keys => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_values2keys <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_assertion2keys_ava>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_assertion2keys_ava => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_assertion2keys_ava <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_assertion2keys_sub>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_assertion2keys_sub => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_assertion2keys_sub <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_names>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                // Probably a char array
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_sub_mr_filter_compare>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_compare => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_sub_mr_filter_compare <= success"));
++                0
++            }
++
++            // ORD MR plugin hooks
++            #[no_mangle]
++            pub extern "C" fn [<$mod_ident _plugin_ord_mr_init>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                let mut pb = PblockRef::new(raw_pb);
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_init => begin"));
++                match pb.set_plugin_version(PluginVersion::V01) {
++                    0 => {},
++                    e => return e,
++                };
++
++                let name_vec = Charray::new($hooks_ident::ord_mr_supported_names().as_slice()).expect("invalid ord supported names");
++                let name_ptr = name_vec.as_ptr();
++                // SLAPI_PLUGIN_MR_NAMES
++                match pb.register_mr_names(name_ptr) {
++                    0 => {},
++                    e => return e,
++                };
++
++                // description
++                // SLAPI_PLUGIN_MR_FILTER_CREATE_FN
++                match pb.register_mr_filter_create_fn([<$mod_ident _plugin_ord_mr_filter_create>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_INDEXER_CREATE_FN
++                match pb.register_mr_indexer_create_fn([<$mod_ident _plugin_ord_mr_indexer_create>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_FILTER_AVA
++                match pb.register_mr_filter_ava_fn([<$mod_ident _plugin_mr_filter_ava>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_FILTER_SUB
++                match pb.register_mr_filter_sub_fn([<$mod_ident _plugin_ord_mr_filter_sub>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_VALUES2KEYS
++                /*
++                match pb.register_mr_values2keys_fn([<$mod_ident _plugin_ord_mr_filter_values2keys>]) {
++                    0 => {},
++                    e => return e,
++                };
++                */
++                // SLAPI_PLUGIN_MR_ASSERTION2KEYS_AVA
++                match pb.register_mr_assertion2keys_ava_fn([<$mod_ident _plugin_ord_mr_filter_assertion2keys_ava>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_ASSERTION2KEYS_SUB
++                match pb.register_mr_assertion2keys_sub_fn([<$mod_ident _plugin_ord_mr_filter_assertion2keys_sub>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_COMPARE
++                match pb.register_mr_compare_fn([<$mod_ident _plugin_ord_mr_filter_compare>]) {
++                    0 => {},
++                    e => return e,
++                };
++                // SLAPI_PLUGIN_MR_NORMALIZE
++
++                // Finaly, register the MR
++                match unsafe { matchingrule_register($hooks_ident::ord_mr_oid().unwrap(), $hooks_ident::ord_mr_name(), $hooks_ident::ord_mr_desc(), $hooks_ident::attr_oid(), &$hooks_ident::attr_compat_oids()) } {
++                    0 => {},
++                    e => return e,
++                };
++
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_init <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_create>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_create => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_create <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_ord_mr_indexer_create>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_indexer_create => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_indexer_create <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_sub>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_sub => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_sub <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_values2keys>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_values2keys => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_values2keys <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_assertion2keys_ava>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_assertion2keys_ava => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_assertion2keys_ava <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_assertion2keys_sub>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_assertion2keys_sub => begin"));
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_assertion2keys_sub <= success"));
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_names>](
++                raw_pb: *const libc::c_void,
++            ) -> i32 {
++                // probably char pointers
++                0
++            }
++
++            pub extern "C" fn [<$mod_ident _plugin_ord_mr_filter_compare>](
++                raw_va: *const libc::c_void,
++                raw_vb: *const libc::c_void,
++            ) -> i32 {
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_compare => begin"));
++                let va = BerValRef::new(raw_va);
++                let vb = BerValRef::new(raw_vb);
++                let rc = match $hooks_ident::filter_compare(&va, &vb) {
++                    Ordering::Less => -1,
++                    Ordering::Equal => 0,
++                    Ordering::Greater => 1,
++                };
++                log_error!(ErrorLevel::Trace, concat!(stringify!($mod_ident), "_plugin_ord_mr_filter_compare <= success"));
++                rc
++            }
++
++        } // end paste
++    )
++} // end macro
++
++#[macro_export]
++macro_rules! slapi_r_search_callback_mapfn {
++    (
++        $mod_ident:ident,
++        $cb_target_ident:ident,
++        $cb_mod_ident:ident
++    ) => {
++        paste::item! {
++            #[no_mangle]
++            pub extern "C" fn [<$cb_target_ident>](
++                raw_e: *const libc::c_void,
++                raw_data: *const libc::c_void,
++            ) -> i32 {
++                let e = EntryRef::new(raw_e);
++                let data_ptr = raw_data as *const _;
++                let data = unsafe { &(*data_ptr) };
++                match $cb_mod_ident(&e, data) {
++                    Ok(_) => LDAPError::Success as i32,
++                    Err(e) => e as i32,
++                }
++            }
++        } // end paste
++    };
++} // end macro
+diff --git a/src/slapi_r_plugin/src/modify.rs b/src/slapi_r_plugin/src/modify.rs
+new file mode 100644
+index 000000000..4d5c111c0
+--- /dev/null
++++ b/src/slapi_r_plugin/src/modify.rs
+@@ -0,0 +1,117 @@
++use crate::dn::SdnRef;
++use crate::error::{LDAPError, PluginError};
++use crate::pblock::Pblock;
++use crate::plugin::PluginIdRef;
++use crate::value::{slapi_value, ValueArray};
++
++use std::ffi::CString;
++use std::ops::Deref;
++use std::os::raw::c_char;
++
++extern "C" {
++    fn slapi_modify_internal_set_pb_ext(
++        pb: *const libc::c_void,
++        dn: *const libc::c_void,
++        mods: *const *const libc::c_void,
++        controls: *const *const libc::c_void,
++        uniqueid: *const c_char,
++        plugin_ident: *const libc::c_void,
++        op_flags: i32,
++    );
++    fn slapi_modify_internal_pb(pb: *const libc::c_void);
++    fn slapi_mods_free(smods: *const *const libc::c_void);
++    fn slapi_mods_get_ldapmods_byref(smods: *const libc::c_void) -> *const *const libc::c_void;
++    fn slapi_mods_new() -> *const libc::c_void;
++    fn slapi_mods_add_mod_values(
++        smods: *const libc::c_void,
++        mtype: i32,
++        attrtype: *const c_char,
++        value: *const *const slapi_value,
++    );
++}
++
++#[derive(Debug)]
++#[repr(i32)]
++pub enum ModType {
++    Add = 0,
++    Delete = 1,
++    Replace = 2,
++}
++
++pub struct SlapiMods {
++    inner: *const libc::c_void,
++    vas: Vec<ValueArray>,
++}
++
++impl Drop for SlapiMods {
++    fn drop(&mut self) {
++        unsafe { slapi_mods_free(&self.inner as *const *const libc::c_void) }
++    }
++}
++
++impl SlapiMods {
++    pub fn new() -> Self {
++        SlapiMods {
++            inner: unsafe { slapi_mods_new() },
++            vas: Vec::new(),
++        }
++    }
++
++    pub fn append(&mut self, modtype: ModType, attrtype: &str, values: ValueArray) {
++        // We can get the value array pointer here to push to the inner
++        // because the internal pointers won't change even when we push them
++        // to the list to preserve their lifetime.
++        let vas = values.as_ptr();
++        // We take ownership of this to ensure it lives as least as long as our
++        // slapimods structure.
++        self.vas.push(values);
++        // now we can insert these to the modes.
++        let c_attrtype = CString::new(attrtype).expect("failed to allocate attrtype");
++        unsafe { slapi_mods_add_mod_values(self.inner, modtype as i32, c_attrtype.as_ptr(), vas) };
++    }
++}
++
++pub struct Modify {
++    pb: Pblock,
++    mods: SlapiMods,
++}
++
++pub struct ModifyResult {
++    _pb: Pblock,
++}
++
++impl Modify {
++    pub fn new(dn: &SdnRef, mods: SlapiMods, plugin_id: PluginIdRef) -> Result<Self, PluginError> {
++        let pb = Pblock::new();
++        let lmods = unsafe { slapi_mods_get_ldapmods_byref(mods.inner) };
++        // OP_FLAG_ACTION_LOG_ACCESS
++
++        unsafe {
++            slapi_modify_internal_set_pb_ext(
++                pb.deref().as_ptr(),
++                dn.as_ptr(),
++                lmods,
++                std::ptr::null(),
++                std::ptr::null(),
++                plugin_id.raw_pid,
++                0 as i32,
++            )
++        };
++
++        Ok(Modify { pb, mods })
++    }
++
++    pub fn execute(self) -> Result<ModifyResult, LDAPError> {
++        let Modify {
++            mut pb,
++            mods: _mods,
++        } = self;
++        unsafe { slapi_modify_internal_pb(pb.deref().as_ptr()) };
++        let result = pb.get_op_result();
++
++        match result {
++            0 => Ok(ModifyResult { _pb: pb }),
++            _e => Err(LDAPError::from(result)),
++        }
++    }
++}
+diff --git a/src/slapi_r_plugin/src/pblock.rs b/src/slapi_r_plugin/src/pblock.rs
+new file mode 100644
+index 000000000..3ff6b9f9c
+--- /dev/null
++++ b/src/slapi_r_plugin/src/pblock.rs
+@@ -0,0 +1,323 @@
++use libc;
++use std::ops::{Deref, DerefMut};
++use std::os::raw::c_char;
++use std::ptr;
++
++use crate::backend::BackendRef;
++use crate::constants::{PblockType, PluginFnType, PluginVersion};
++use crate::entry::EntryRef;
++pub use crate::log::{log_error, ErrorLevel};
++
++extern "C" {
++    fn slapi_pblock_set(pb: *const libc::c_void, arg: i32, value: *const libc::c_void) -> i32;
++    fn slapi_pblock_get(pb: *const libc::c_void, arg: i32, value: *const libc::c_void) -> i32;
++    fn slapi_pblock_destroy(pb: *const libc::c_void);
++    fn slapi_pblock_new() -> *const libc::c_void;
++}
++
++pub struct Pblock {
++    value: PblockRef,
++}
++
++impl Pblock {
++    pub fn new() -> Pblock {
++        let raw_pb = unsafe { slapi_pblock_new() };
++        Pblock {
++            value: PblockRef { raw_pb },
++        }
++    }
++}
++
++impl Deref for Pblock {
++    type Target = PblockRef;
++
++    fn deref(&self) -> &Self::Target {
++        &self.value
++    }
++}
++
++impl DerefMut for Pblock {
++    fn deref_mut(&mut self) -> &mut Self::Target {
++        &mut self.value
++    }
++}
++
++impl Drop for Pblock {
++    fn drop(&mut self) {
++        unsafe { slapi_pblock_destroy(self.value.raw_pb) }
++    }
++}
++
++pub struct PblockRef {
++    raw_pb: *const libc::c_void,
++}
++
++impl PblockRef {
++    pub fn new(raw_pb: *const libc::c_void) -> Self {
++        PblockRef { raw_pb }
++    }
++
++    pub unsafe fn as_ptr(&self) -> *const libc::c_void {
++        self.raw_pb
++    }
++
++    fn set_pb_char_arr_ptr(&mut self, req_type: PblockType, ptr: *const *const c_char) -> i32 {
++        let value_ptr: *const libc::c_void = ptr as *const libc::c_void;
++        unsafe { slapi_pblock_set(self.raw_pb, req_type as i32, value_ptr) }
++    }
++
++    fn set_pb_char_ptr(&mut self, req_type: PblockType, ptr: *const c_char) -> i32 {
++        let value_ptr: *const libc::c_void = ptr as *const libc::c_void;
++        unsafe { slapi_pblock_set(self.raw_pb, req_type as i32, value_ptr) }
++    }
++
++    fn set_pb_fn_ptr(
++        &mut self,
++        fn_type: PluginFnType,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        let value_ptr: *const libc::c_void = ptr as *const libc::c_void;
++        unsafe { slapi_pblock_set(self.raw_pb, fn_type as i32, value_ptr) }
++    }
++
++    fn get_value_ptr(&mut self, req_type: PblockType) -> Result<*const libc::c_void, ()> {
++        let mut value: *mut libc::c_void = ptr::null::<libc::c_void>() as *mut libc::c_void;
++        let value_ptr: *const libc::c_void = &mut value as *const _ as *const libc::c_void;
++        match unsafe { slapi_pblock_get(self.raw_pb, req_type as i32, value_ptr) } {
++            0 => Ok(value),
++            e => {
++                log_error!(ErrorLevel::Error, "enable to get from pblock -> {:?}", e);
++                Err(())
++            }
++        }
++    }
++
++    fn get_value_i32(&mut self, req_type: PblockType) -> Result<i32, ()> {
++        let mut value: i32 = 0;
++        let value_ptr: *const libc::c_void = &mut value as *const _ as *const libc::c_void;
++        match unsafe { slapi_pblock_get(self.raw_pb, req_type as i32, value_ptr) } {
++            0 => Ok(value),
++            e => {
++                log_error!(ErrorLevel::Error, "enable to get from pblock -> {:?}", e);
++                Err(())
++            }
++        }
++    }
++
++    pub fn register_start_fn(&mut self, ptr: extern "C" fn(*const libc::c_void) -> i32) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::Start, ptr)
++    }
++
++    pub fn register_close_fn(&mut self, ptr: extern "C" fn(*const libc::c_void) -> i32) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::Close, ptr)
++    }
++
++    pub fn register_betxn_pre_add_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::BeTxnPreAdd, ptr)
++    }
++
++    pub fn register_betxn_pre_modify_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::BeTxnPreModify, ptr)
++    }
++
++    pub fn register_syntax_filter_ava_fn(
++        &mut self,
++        ptr: extern "C" fn(
++            *const core::ffi::c_void,
++            *const core::ffi::c_void,
++            *const core::ffi::c_void,
++            i32,
++            *mut core::ffi::c_void,
++        ) -> i32,
++    ) -> i32 {
++        // We can't use self.set_pb_fn_ptr here as the fn type sig is different.
++        let value_ptr: *const libc::c_void = ptr as *const libc::c_void;
++        unsafe { slapi_pblock_set(self.raw_pb, PluginFnType::SyntaxFilterAva as i32, value_ptr) }
++    }
++
++    pub fn register_syntax_values2keys_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::SyntaxValuesToKeys, ptr)
++    }
++
++    pub fn register_syntax_assertion2keys_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::SyntaxAssertion2KeysAva, ptr)
++    }
++
++    pub fn register_syntax_flags_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::SyntaxFlags, ptr)
++    }
++
++    pub fn register_syntax_oid(&mut self, ptr: *const c_char) -> i32 {
++        self.set_pb_char_ptr(PblockType::SyntaxOid, ptr)
++    }
++
++    pub fn register_syntax_compare_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::SyntaxCompare, ptr)
++    }
++
++    pub fn register_syntax_validate_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::SyntaxValidate, ptr)
++    }
++
++    pub fn register_syntax_names(&mut self, arr_ptr: *const *const c_char) -> i32 {
++        self.set_pb_char_arr_ptr(PblockType::SyntaxNames, arr_ptr)
++    }
++
++    pub fn register_mr_filter_create_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::MRFilterCreate, ptr)
++    }
++
++    pub fn register_mr_indexer_create_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::MRIndexerCreate, ptr)
++    }
++
++    pub fn register_mr_filter_ava_fn(
++        &mut self,
++        ptr: extern "C" fn(
++            *const core::ffi::c_void,
++            *const core::ffi::c_void,
++            *const core::ffi::c_void,
++            i32,
++            *mut core::ffi::c_void,
++        ) -> i32,
++    ) -> i32 {
++        let value_ptr: *const libc::c_void = ptr as *const libc::c_void;
++        unsafe { slapi_pblock_set(self.raw_pb, PluginFnType::MRFilterAva as i32, value_ptr) }
++    }
++
++    pub fn register_mr_filter_sub_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::MRFilterSub, ptr)
++    }
++
++    pub fn register_mr_values2keys_fn(
++        &mut self,
++        ptr: extern "C" fn(
++            *const core::ffi::c_void,
++            *const core::ffi::c_void,
++            *mut core::ffi::c_void,
++            i32,
++        ) -> i32,
++    ) -> i32 {
++        let value_ptr: *const libc::c_void = ptr as *const libc::c_void;
++        unsafe { slapi_pblock_set(self.raw_pb, PluginFnType::MRValuesToKeys as i32, value_ptr) }
++    }
++
++    pub fn register_mr_assertion2keys_ava_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::MRAssertionToKeysAva, ptr)
++    }
++
++    pub fn register_mr_assertion2keys_sub_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void) -> i32,
++    ) -> i32 {
++        self.set_pb_fn_ptr(PluginFnType::MRAssertionToKeysSub, ptr)
++    }
++
++    pub fn register_mr_compare_fn(
++        &mut self,
++        ptr: extern "C" fn(*const libc::c_void, *const libc::c_void) -> i32,
++    ) -> i32 {
++        let value_ptr: *const libc::c_void = ptr as *const libc::c_void;
++        unsafe { slapi_pblock_set(self.raw_pb, PluginFnType::MRCompare as i32, value_ptr) }
++    }
++
++    pub fn register_mr_names(&mut self, arr_ptr: *const *const c_char) -> i32 {
++        self.set_pb_char_arr_ptr(PblockType::MRNames, arr_ptr)
++    }
++
++    pub fn register_pwd_storage_encrypt_fn(
++        &mut self,
++        ptr: extern "C" fn(*const c_char) -> *const c_char,
++    ) -> i32 {
++        let value_ptr: *const libc::c_void = ptr as *const libc::c_void;
++        unsafe {
++            slapi_pblock_set(
++                self.raw_pb,
++                PluginFnType::PwdStorageEncrypt as i32,
++                value_ptr,
++            )
++        }
++    }
++
++    pub fn register_pwd_storage_compare_fn(
++        &mut self,
++        ptr: extern "C" fn(*const c_char, *const c_char) -> i32,
++    ) -> i32 {
++        let value_ptr: *const libc::c_void = ptr as *const libc::c_void;
++        unsafe {
++            slapi_pblock_set(
++                self.raw_pb,
++                PluginFnType::PwdStorageCompare as i32,
++                value_ptr,
++            )
++        }
++    }
++
++    pub fn register_pwd_storage_scheme_name(&mut self, ptr: *const c_char) -> i32 {
++        self.set_pb_char_ptr(PblockType::PwdStorageSchemeName, ptr)
++    }
++
++    pub fn get_op_add_entryref(&mut self) -> Result<EntryRef, ()> {
++        self.get_value_ptr(PblockType::AddEntry)
++            .map(|ptr| EntryRef::new(ptr))
++    }
++
++    pub fn set_plugin_version(&mut self, vers: PluginVersion) -> i32 {
++        self.set_pb_char_ptr(PblockType::Version, vers.to_char_ptr())
++    }
++
++    pub fn set_op_backend(&mut self, be: &BackendRef) -> i32 {
++        unsafe { slapi_pblock_set(self.raw_pb, PblockType::Backend as i32, be.as_ptr()) }
++    }
++
++    pub fn get_plugin_identity(&mut self) -> *const libc::c_void {
++        self.get_value_ptr(PblockType::Identity)
++            .unwrap_or(std::ptr::null())
++    }
++
++    pub fn get_op_result(&mut self) -> i32 {
++        self.get_value_i32(PblockType::OpResult).unwrap_or(-1)
++    }
++
++    pub fn get_is_replicated_operation(&mut self) -> bool {
++        let i = self
++            .get_value_i32(PblockType::IsReplicationOperation)
++            .unwrap_or(0);
++        // Because rust returns the result of the last evaluation, we can
++        // just return if not equal 0.
++        i != 0
++    }
++}
+diff --git a/src/slapi_r_plugin/src/plugin.rs b/src/slapi_r_plugin/src/plugin.rs
+new file mode 100644
+index 000000000..3790c4043
+--- /dev/null
++++ b/src/slapi_r_plugin/src/plugin.rs
+@@ -0,0 +1,133 @@
++use crate::constants::{PluginType, PLUGIN_DEFAULT_PRECEDENCE};
++use crate::dn::Sdn;
++use crate::entry::EntryRef;
++use crate::error::LDAPError;
++use crate::error::PluginError;
++use crate::pblock::PblockRef;
++use crate::task::Task;
++use libc;
++use std::ffi::CString;
++use std::os::raw::c_char;
++use std::ptr;
++
++extern "C" {
++    fn slapi_register_plugin_ext(
++        plugintype: *const c_char,
++        enabled: i32,
++        initsymbol: *const c_char,
++        initfunc: *const libc::c_void,
++        name: *const c_char,
++        argv: *const *const c_char,
++        group_identity: *const libc::c_void,
++        precedence: i32,
++    ) -> i32;
++}
++
++pub struct PluginIdRef {
++    pub raw_pid: *const libc::c_void,
++}
++
++pub fn register_plugin_ext(
++    ptype: PluginType,
++    plugname: &str,
++    initfnname: &str,
++    initfn: extern "C" fn(*const libc::c_void) -> i32,
++) -> i32 {
++    let c_plugname = match CString::new(plugname) {
++        Ok(c) => c,
++        Err(_) => return 1,
++    };
++    let c_initfnname = match CString::new(initfnname) {
++        Ok(c) => c,
++        Err(_) => return 1,
++    };
++    let argv = [c_plugname.as_ptr(), ptr::null()];
++    let value_ptr: *const libc::c_void = initfn as *const libc::c_void;
++
++    unsafe {
++        slapi_register_plugin_ext(
++            ptype.to_char_ptr(),
++            1,
++            c_initfnname.as_ptr(),
++            value_ptr,
++            c_plugname.as_ptr(),
++            &argv as *const *const c_char,
++            ptr::null(),
++            PLUGIN_DEFAULT_PRECEDENCE,
++        )
++    }
++}
++
++pub trait SlapiPlugin3 {
++    // We require a newer rust for default associated types.
++    // type TaskData = ();
++    type TaskData;
++
++    fn has_pre_modify() -> bool {
++        false
++    }
++
++    fn has_post_modify() -> bool {
++        false
++    }
++
++    fn has_pre_add() -> bool {
++        false
++    }
++
++    fn has_post_add() -> bool {
++        false
++    }
++
++    fn has_betxn_pre_modify() -> bool {
++        false
++    }
++
++    fn has_betxn_pre_add() -> bool {
++        false
++    }
++
++    fn has_task_handler() -> Option<&'static str> {
++        None
++    }
++
++    fn has_pwd_storage() -> bool {
++        false
++    }
++
++    fn start(_pb: &mut PblockRef) -> Result<(), PluginError>;
++
++    fn close(_pb: &mut PblockRef) -> Result<(), PluginError>;
++
++    fn betxn_pre_modify(_pb: &mut PblockRef) -> Result<(), PluginError> {
++        Err(PluginError::Unimplemented)
++    }
++
++    fn betxn_pre_add(_pb: &mut PblockRef) -> Result<(), PluginError> {
++        Err(PluginError::Unimplemented)
++    }
++
++    fn task_validate(_e: &EntryRef) -> Result<Self::TaskData, LDAPError> {
++        Err(LDAPError::Other)
++    }
++
++    fn task_be_dn_hint(_data: &Self::TaskData) -> Option<Sdn> {
++        None
++    }
++
++    fn task_handler(_task: &Task, _data: Self::TaskData) -> Result<Self::TaskData, PluginError> {
++        Err(PluginError::Unimplemented)
++    }
++
++    fn pwd_scheme_name() -> &'static str {
++        panic!("Unimplemented pwd_scheme_name for password storage plugin")
++    }
++
++    fn pwd_storage_encrypt(_cleartext: &str) -> Result<String, PluginError> {
++        Err(PluginError::Unimplemented)
++    }
++
++    fn pwd_storage_compare(_cleartext: &str, _encrypted: &str) -> Result<bool, PluginError> {
++        Err(PluginError::Unimplemented)
++    }
++}
+diff --git a/src/slapi_r_plugin/src/search.rs b/src/slapi_r_plugin/src/search.rs
+new file mode 100644
+index 000000000..6e68e419f
+--- /dev/null
++++ b/src/slapi_r_plugin/src/search.rs
+@@ -0,0 +1,127 @@
++use crate::dn::SdnRef;
++use crate::error::{LDAPError, PluginError};
++use crate::pblock::Pblock;
++use crate::plugin::PluginIdRef;
++use std::ffi::CString;
++use std::ops::Deref;
++use std::os::raw::c_char;
++
++extern "C" {
++    fn slapi_search_internal_set_pb_ext(
++        pb: *const libc::c_void,
++        base: *const libc::c_void,
++        scope: i32,
++        filter: *const c_char,
++        attrs: *const *const c_char,
++        attrsonly: i32,
++        controls: *const *const libc::c_void,
++        uniqueid: *const c_char,
++        plugin_ident: *const libc::c_void,
++        op_flags: i32,
++    );
++    fn slapi_search_internal_callback_pb(
++        pb: *const libc::c_void,
++        cb_data: *const libc::c_void,
++        cb_result_ptr: *const libc::c_void,
++        cb_entry_ptr: *const libc::c_void,
++        cb_referral_ptr: *const libc::c_void,
++    ) -> i32;
++}
++
++#[derive(Debug)]
++#[repr(i32)]
++pub enum SearchScope {
++    Base = 0,
++    Onelevel = 1,
++    Subtree = 2,
++}
++
++enum SearchType {
++    InternalMapEntry(
++        extern "C" fn(*const core::ffi::c_void, *const core::ffi::c_void) -> i32,
++        *const libc::c_void,
++    ),
++    // InternalMapResult
++    // InternalMapReferral
++}
++
++pub struct Search {
++    pb: Pblock,
++    // This is so that the char * to the pb lives long enough as ds won't clone it.
++    filter: Option<CString>,
++    stype: SearchType,
++}
++
++pub struct SearchResult {
++    _pb: Pblock,
++}
++
++impl Search {
++    pub fn new_map_entry<T>(
++        basedn: &SdnRef,
++        scope: SearchScope,
++        filter: &str,
++        plugin_id: PluginIdRef,
++        cbdata: &T,
++        mapfn: extern "C" fn(*const libc::c_void, *const libc::c_void) -> i32,
++    ) -> Result<Self, PluginError>
++    where
++        T: Send,
++    {
++        // Configure a search based on the requested type.
++        let pb = Pblock::new();
++        let raw_filter = CString::new(filter).map_err(|_| PluginError::InvalidFilter)?;
++
++        unsafe {
++            slapi_search_internal_set_pb_ext(
++                pb.deref().as_ptr(),
++                basedn.as_ptr(),
++                scope as i32,
++                raw_filter.as_ptr(),
++                std::ptr::null(),
++                0,
++                std::ptr::null(),
++                std::ptr::null(),
++                plugin_id.raw_pid,
++                0,
++            )
++        };
++
++        Ok(Search {
++            pb,
++            filter: Some(raw_filter),
++            stype: SearchType::InternalMapEntry(mapfn, cbdata as *const _ as *const libc::c_void),
++        })
++    }
++
++    // Consume self, do the search
++    pub fn execute(self) -> Result<SearchResult, LDAPError> {
++        // Deconstruct self
++        let Search {
++            mut pb,
++            filter: _filter,
++            stype,
++        } = self;
++
++        // run the search based on the type.
++        match stype {
++            SearchType::InternalMapEntry(mapfn, cbdata) => unsafe {
++                slapi_search_internal_callback_pb(
++                    pb.deref().as_ptr(),
++                    cbdata,
++                    std::ptr::null(),
++                    mapfn as *const libc::c_void,
++                    std::ptr::null(),
++                );
++            },
++        };
++
++        // now check the result, and map to what we need.
++        let result = pb.get_op_result();
++
++        match result {
++            0 => Ok(SearchResult { _pb: pb }),
++            _e => Err(LDAPError::from(result)),
++        }
++    }
++}
+diff --git a/src/slapi_r_plugin/src/syntax_plugin.rs b/src/slapi_r_plugin/src/syntax_plugin.rs
+new file mode 100644
+index 000000000..86f84bdd8
+--- /dev/null
++++ b/src/slapi_r_plugin/src/syntax_plugin.rs
+@@ -0,0 +1,142 @@
++use crate::ber::BerValRef;
++// use crate::constants::FilterType;
++use crate::charray::Charray;
++use crate::error::PluginError;
++use crate::pblock::PblockRef;
++use crate::value::{ValueArray, ValueArrayRef};
++use std::cmp::Ordering;
++use std::ffi::CString;
++use std::os::raw::c_char;
++use std::ptr;
++
++// need a call to slapi_register_plugin_ext
++
++extern "C" {
++    fn slapi_matchingrule_register(mr: *const slapi_matchingRuleEntry) -> i32;
++}
++
++#[repr(C)]
++struct slapi_matchingRuleEntry {
++    mr_oid: *const c_char,
++    _mr_oidalias: *const c_char,
++    mr_name: *const c_char,
++    mr_desc: *const c_char,
++    mr_syntax: *const c_char,
++    _mr_obsolete: i32, // unused
++    mr_compat_syntax: *const *const c_char,
++}
++
++// oid - the oid of the matching rule
++// name - the name of the mr
++// desc - description
++// syntax - the syntax of the attribute we apply to
++// compat_syntax - extended syntaxes f other attributes we may apply to.
++pub unsafe fn matchingrule_register(
++    oid: &str,
++    name: &str,
++    desc: &str,
++    syntax: &str,
++    compat_syntax: &[&str],
++) -> i32 {
++    // Make everything CStrings that live long enough.
++
++    let oid_cs = CString::new(oid).expect("invalid oid");
++    let name_cs = CString::new(name).expect("invalid name");
++    let desc_cs = CString::new(desc).expect("invalid desc");
++    let syntax_cs = CString::new(syntax).expect("invalid syntax");
++
++    // We have to do this so the cstrings live long enough.
++    let compat_syntax_ca = Charray::new(compat_syntax).expect("invalid compat_syntax");
++
++    let new_mr = slapi_matchingRuleEntry {
++        mr_oid: oid_cs.as_ptr(),
++        _mr_oidalias: ptr::null(),
++        mr_name: name_cs.as_ptr(),
++        mr_desc: desc_cs.as_ptr(),
++        mr_syntax: syntax_cs.as_ptr(),
++        _mr_obsolete: 0,
++        mr_compat_syntax: compat_syntax_ca.as_ptr(),
++    };
++
++    let new_mr_ptr = &new_mr as *const _;
++    slapi_matchingrule_register(new_mr_ptr)
++}
++
++pub trait SlapiSyntaxPlugin1 {
++    fn attr_oid() -> &'static str;
++
++    fn attr_compat_oids() -> Vec<&'static str>;
++
++    fn attr_supported_names() -> Vec<&'static str>;
++
++    fn syntax_validate(bval: &BerValRef) -> Result<(), PluginError>;
++
++    fn eq_mr_oid() -> &'static str;
++
++    fn eq_mr_name() -> &'static str;
++
++    fn eq_mr_desc() -> &'static str;
++
++    fn eq_mr_supported_names() -> Vec<&'static str>;
++
++    fn filter_ava_eq(
++        _pb: &mut PblockRef,
++        _bval_filter: &BerValRef,
++        _vals: &ValueArrayRef,
++    ) -> Result<bool, PluginError> {
++        Ok(false)
++    }
++
++    fn eq_mr_filter_values2keys(
++        _pb: &mut PblockRef,
++        _vals: &ValueArrayRef,
++    ) -> Result<ValueArray, PluginError>;
++}
++
++pub trait SlapiOrdMr: SlapiSyntaxPlugin1 {
++    fn ord_mr_oid() -> Option<&'static str> {
++        None
++    }
++
++    fn ord_mr_name() -> &'static str {
++        panic!("Unimplemented ord_mr_name for SlapiOrdMr")
++    }
++
++    fn ord_mr_desc() -> &'static str {
++        panic!("Unimplemented ord_mr_desc for SlapiOrdMr")
++    }
++
++    fn ord_mr_supported_names() -> Vec<&'static str> {
++        panic!("Unimplemented ord_mr_supported_names for SlapiOrdMr")
++    }
++
++    fn filter_ava_ord(
++        _pb: &mut PblockRef,
++        _bval_filter: &BerValRef,
++        _vals: &ValueArrayRef,
++    ) -> Result<Option<Ordering>, PluginError> {
++        Ok(None)
++    }
++
++    fn filter_compare(_a: &BerValRef, _b: &BerValRef) -> Ordering {
++        panic!("Unimplemented filter_compare")
++    }
++}
++
++pub trait SlapiSubMr: SlapiSyntaxPlugin1 {
++    fn sub_mr_oid() -> Option<&'static str> {
++        None
++    }
++
++    fn sub_mr_name() -> &'static str {
++        panic!("Unimplemented sub_mr_name for SlapiSubMr")
++    }
++
++    fn sub_mr_desc() -> &'static str {
++        panic!("Unimplemented sub_mr_desc for SlapiSubMr")
++    }
++
++    fn sub_mr_supported_names() -> Vec<&'static str> {
++        panic!("Unimplemented sub_mr_supported_names for SlapiSubMr")
++    }
++}
+diff --git a/src/slapi_r_plugin/src/task.rs b/src/slapi_r_plugin/src/task.rs
+new file mode 100644
+index 000000000..251ae4d82
+--- /dev/null
++++ b/src/slapi_r_plugin/src/task.rs
+@@ -0,0 +1,148 @@
++use crate::constants::LDAP_SUCCESS;
++use crate::entry::EntryRef;
++use crate::pblock::PblockRef;
++use std::ffi::CString;
++use std::os::raw::c_char;
++use std::thread;
++use std::time::Duration;
++
++extern "C" {
++    fn slapi_plugin_new_task(ndn: *const c_char, arg: *const libc::c_void) -> *const libc::c_void;
++    fn slapi_task_dec_refcount(task: *const libc::c_void);
++    fn slapi_task_inc_refcount(task: *const libc::c_void);
++    fn slapi_task_get_refcount(task: *const libc::c_void) -> i32;
++    fn slapi_task_begin(task: *const libc::c_void, rc: i32);
++    fn slapi_task_finish(task: *const libc::c_void, rc: i32);
++
++    fn slapi_plugin_task_register_handler(
++        ident: *const c_char,
++        cb: extern "C" fn(
++            *const libc::c_void,
++            *const libc::c_void,
++            *const libc::c_void,
++            *mut i32,
++            *mut c_char,
++            *const libc::c_void,
++        ) -> i32,
++        pb: *const libc::c_void,
++    ) -> i32;
++    fn slapi_plugin_task_unregister_handler(
++        ident: *const c_char,
++        cb: extern "C" fn(
++            *const libc::c_void,
++            *const libc::c_void,
++            *const libc::c_void,
++            *mut i32,
++            *mut c_char,
++            *const libc::c_void,
++        ) -> i32,
++    ) -> i32;
++    fn slapi_task_set_destructor_fn(
++        task: *const libc::c_void,
++        cb: extern "C" fn(*const libc::c_void),
++    );
++}
++
++pub struct TaskRef {
++    raw_task: *const libc::c_void,
++}
++
++pub struct Task {
++    value: TaskRef,
++}
++
++// Because raw pointers are not send, but we need to send the task to a thread
++// as part of the task thread spawn, we need to convince the compiler this
++// action is okay. It's probably not because C is terrible, BUT provided the
++// server and framework only touch the ref count, we are okay.
++unsafe impl Send for Task {}
++
++pub fn task_register_handler_fn(
++    ident: &'static str,
++    cb: extern "C" fn(
++        *const libc::c_void,
++        *const libc::c_void,
++        *const libc::c_void,
++        *mut i32,
++        *mut c_char,
++        *const libc::c_void,
++    ) -> i32,
++    pb: &mut PblockRef,
++) -> i32 {
++    let cstr = CString::new(ident).expect("Invalid ident provided");
++    unsafe { slapi_plugin_task_register_handler(cstr.as_ptr(), cb, pb.as_ptr()) }
++}
++
++pub fn task_unregister_handler_fn(
++    ident: &'static str,
++    cb: extern "C" fn(
++        *const libc::c_void,
++        *const libc::c_void,
++        *const libc::c_void,
++        *mut i32,
++        *mut c_char,
++        *const libc::c_void,
++    ) -> i32,
++) -> i32 {
++    let cstr = CString::new(ident).expect("Invalid ident provided");
++    unsafe { slapi_plugin_task_unregister_handler(cstr.as_ptr(), cb) }
++}
++
++impl Task {
++    pub fn new(e: &EntryRef, arg: *const libc::c_void) -> Self {
++        let sdn = e.get_sdnref();
++        let ndn = unsafe { sdn.as_ndnref() };
++        let raw_task = unsafe { slapi_plugin_new_task(ndn.as_ptr(), arg) };
++        unsafe { slapi_task_inc_refcount(raw_task) };
++        Task {
++            value: TaskRef { raw_task },
++        }
++    }
++
++    pub fn begin(&self) {
++        // Indicate we begin
++        unsafe { slapi_task_begin(self.value.raw_task, 1) }
++    }
++
++    pub fn register_destructor_fn(&mut self, cb: extern "C" fn(*const libc::c_void)) {
++        unsafe {
++            slapi_task_set_destructor_fn(self.value.raw_task, cb);
++        }
++    }
++
++    pub fn success(self) {
++        unsafe {
++            slapi_task_finish(self.value.raw_task, LDAP_SUCCESS);
++        }
++    }
++
++    pub fn error(self, rc: i32) {
++        unsafe { slapi_task_finish(self.value.raw_task, rc) };
++    }
++}
++
++impl Drop for Task {
++    fn drop(&mut self) {
++        unsafe {
++            slapi_task_dec_refcount(self.value.raw_task);
++        }
++    }
++}
++
++impl TaskRef {
++    pub fn new(raw_task: *const libc::c_void) -> Self {
++        TaskRef { raw_task }
++    }
++
++    pub fn block(&self) {
++        // wait for the refcount to go to 0.
++        let d = Duration::from_millis(250);
++        loop {
++            if unsafe { slapi_task_get_refcount(self.raw_task) } > 0 {
++                thread::sleep(d);
++            } else {
++                return;
++            }
++        }
++    }
++}
+diff --git a/src/slapi_r_plugin/src/value.rs b/src/slapi_r_plugin/src/value.rs
+new file mode 100644
+index 000000000..cd565295c
+--- /dev/null
++++ b/src/slapi_r_plugin/src/value.rs
+@@ -0,0 +1,247 @@
++use crate::ber::{ol_berval, BerValRef};
++use crate::dn::Sdn;
++use std::convert::{From, TryFrom, TryInto};
++use std::ffi::CString;
++use std::iter::once;
++use std::iter::FromIterator;
++use std::mem;
++use std::ops::Deref;
++use std::ptr;
++use uuid::Uuid;
++
++extern "C" {
++    fn slapi_value_new() -> *mut slapi_value;
++    fn slapi_value_free(v: *mut *const libc::c_void);
++}
++
++#[repr(C)]
++/// From ./ldap/servers/slapd/slap.h
++pub struct slapi_value {
++    bv: ol_berval,
++    v_csnset: *const libc::c_void,
++    v_flags: u32,
++}
++
++pub struct ValueArrayRefIter<'a> {
++    idx: isize,
++    va_ref: &'a ValueArrayRef,
++}
++
++impl<'a> Iterator for ValueArrayRefIter<'a> {
++    type Item = ValueRef;
++
++    #[inline]
++    fn next(&mut self) -> Option<Self::Item> {
++        // So long as va_ref.raw_slapi_val + offset != NULL, continue.
++        // this is so wildly unsafe, but you know, that's just daily life of C anyway ...
++        unsafe {
++            let n_ptr: *const slapi_value = *(self.va_ref.raw_slapi_val.offset(self.idx));
++            if n_ptr.is_null() {
++                None
++            } else {
++                // Advance the iter.
++                self.idx = self.idx + 1;
++                let raw_berval: *const ol_berval = &(*n_ptr).bv as *const _;
++                Some(ValueRef {
++                    raw_slapi_val: n_ptr,
++                    bvr: BerValRef { raw_berval },
++                })
++            }
++        }
++    }
++}
++
++pub struct ValueArrayRef {
++    raw_slapi_val: *const *const slapi_value,
++}
++
++impl ValueArrayRef {
++    pub fn new(raw_slapi_val: *const libc::c_void) -> Self {
++        let raw_slapi_val = raw_slapi_val as *const _ as *const *const slapi_value;
++        ValueArrayRef { raw_slapi_val }
++    }
++
++    pub fn iter(&self) -> ValueArrayRefIter {
++        ValueArrayRefIter {
++            idx: 0,
++            va_ref: &self,
++        }
++    }
++
++    pub fn first(&self) -> Option<ValueRef> {
++        self.iter().next()
++    }
++}
++
++pub struct ValueArray {
++    data: Vec<*mut slapi_value>,
++    vrf: ValueArrayRef,
++}
++
++impl Deref for ValueArray {
++    type Target = ValueArrayRef;
++
++    fn deref(&self) -> &Self::Target {
++        &self.vrf
++    }
++}
++
++impl ValueArray {
++    /// Take ownership of this value array, returning the pointer to the inner memory
++    /// and forgetting about it for ourself. This prevents the drop handler from freeing
++    /// the slapi_value, ie we are giving this to the 389-ds framework to manage from now.
++    pub unsafe fn take_ownership(mut self) -> *const *const slapi_value {
++        let mut vs = Vec::new();
++        mem::swap(&mut self.data, &mut vs);
++        let bs = vs.into_boxed_slice();
++        Box::leak(bs) as *const _ as *const *const slapi_value
++    }
++
++    pub fn as_ptr(&self) -> *const *const slapi_value {
++        self.data.as_ptr() as *const *const slapi_value
++    }
++}
++
++impl FromIterator<Value> for ValueArray {
++    fn from_iter<I: IntoIterator<Item = Value>>(iter: I) -> Self {
++        let data: Vec<*mut slapi_value> = iter
++            .into_iter()
++            .map(|v| unsafe { v.take_ownership() })
++            .chain(once(ptr::null_mut() as *mut slapi_value))
++            .collect();
++        let vrf = ValueArrayRef {
++            raw_slapi_val: data.as_ptr() as *const *const slapi_value,
++        };
++        ValueArray { data, vrf }
++    }
++}
++
++impl Drop for ValueArray {
++    fn drop(&mut self) {
++        self.data.drain(0..).for_each(|mut v| unsafe {
++            slapi_value_free(&mut v as *mut _ as *mut *const libc::c_void);
++        })
++    }
++}
++
++#[derive(Debug)]
++pub struct ValueRef {
++    raw_slapi_val: *const slapi_value,
++    bvr: BerValRef,
++}
++
++impl ValueRef {
++    pub(crate) unsafe fn as_ptr(&self) -> *const slapi_value {
++        // This is unsafe as the *const may outlive the value ref.
++        self.raw_slapi_val
++    }
++}
++
++pub struct Value {
++    value: ValueRef,
++}
++
++impl Value {
++    pub unsafe fn take_ownership(mut self) -> *mut slapi_value {
++        let mut n_ptr = ptr::null();
++        mem::swap(&mut self.value.raw_slapi_val, &mut n_ptr);
++        n_ptr as *mut slapi_value
++        // Now drop will run and not care.
++    }
++}
++
++impl Drop for Value {
++    fn drop(&mut self) {
++        if self.value.raw_slapi_val != ptr::null() {
++            // free it
++            unsafe {
++                slapi_value_free(
++                    &mut self.value.raw_slapi_val as *mut _ as *mut *const libc::c_void,
++                );
++            }
++        }
++    }
++}
++
++impl Deref for Value {
++    type Target = ValueRef;
++
++    fn deref(&self) -> &Self::Target {
++        &self.value
++    }
++}
++
++impl From<&Uuid> for Value {
++    fn from(u: &Uuid) -> Self {
++        // turn the uuid to a str
++        let u_str = u.to_hyphenated().to_string();
++        let len = u_str.len();
++        let cstr = CString::new(u_str)
++            .expect("Invalid uuid, should never occur!")
++            .into_boxed_c_str();
++        let s_ptr = cstr.as_ptr();
++        Box::leak(cstr);
++
++        let mut v = unsafe { slapi_value_new() };
++        unsafe {
++            (*v).bv.len = len;
++            (*v).bv.data = s_ptr as *const u8;
++        }
++
++        Value {
++            value: ValueRef::new(v as *const libc::c_void),
++        }
++    }
++}
++
++impl ValueRef {
++    pub fn new(raw_slapi_val: *const libc::c_void) -> Self {
++        let raw_slapi_val = raw_slapi_val as *const _ as *const slapi_value;
++        let raw_berval: *const ol_berval = unsafe { &(*raw_slapi_val).bv as *const _ };
++        ValueRef {
++            raw_slapi_val,
++            bvr: BerValRef { raw_berval },
++        }
++    }
++}
++
++impl TryFrom<&ValueRef> for String {
++    type Error = ();
++
++    fn try_from(value: &ValueRef) -> Result<Self, Self::Error> {
++        value.bvr.into_string().ok_or(())
++    }
++}
++
++impl TryFrom<&ValueRef> for Uuid {
++    type Error = ();
++
++    fn try_from(value: &ValueRef) -> Result<Self, Self::Error> {
++        (&value.bvr).try_into().map_err(|_| ())
++    }
++}
++
++impl TryFrom<&ValueRef> for Sdn {
++    type Error = ();
++
++    fn try_from(value: &ValueRef) -> Result<Self, Self::Error> {
++        // We need to do a middle step of moving through a cstring as
++        // bervals may not always have a trailing NULL, and sdn expects one.
++        let cdn = value.bvr.into_cstring().ok_or(())?;
++        Ok(cdn.as_c_str().into())
++    }
++}
++
++impl AsRef<ValueRef> for ValueRef {
++    fn as_ref(&self) -> &ValueRef {
++        &self
++    }
++}
++
++impl Deref for ValueRef {
++    type Target = BerValRef;
++
++    fn deref(&self) -> &Self::Target {
++        &self.bvr
++    }
++}
+-- 
+2.39.1
+
diff --git a/SOURCES/0042-Issue-5440-memberof-is-slow-on-update-fixup-if-there.patch b/SOURCES/0042-Issue-5440-memberof-is-slow-on-update-fixup-if-there.patch
new file mode 100644
index 0000000..cd6fe2c
--- /dev/null
+++ b/SOURCES/0042-Issue-5440-memberof-is-slow-on-update-fixup-if-there.patch
@@ -0,0 +1,221 @@
+From 053fb02f73220be53d1fb93511684a6f7aa3226f Mon Sep 17 00:00:00 2001
+From: Thierry Bordaz <tbordaz@redhat.com>
+Date: Thu, 10 Nov 2022 16:54:40 +0100
+Subject: [PATCH 3/5] Issue 5440 - memberof is slow on update/fixup if there
+ are several 'groupattr' (#5455)
+
+Bug description:
+	When there are several groupattr (attr_1, attr_2,..) in memberof config
+	To fixup entry 'e1', memberof does an internal search
+	"(|(attr_1=e1)(attr_2=e1)...(attr_n=e1))"
+	This is not valid regarding membership relation and in
+	addition it prevents the server to bypass the filter evaluation.
+
+Fix description:
+	To fixup an entry iterate several internal searches
+	"(attr_1=e1)" , then "(attr_2=e1)", then "(attr_n=e1)"
+
+relates: #5440
+
+Reviewed by: Pierre Rogier, Mark Reynolds, Simon Pichugin (Thanks)
+---
+ ldap/servers/plugins/memberof/memberof.c | 155 +++++++++++------------
+ 1 file changed, 73 insertions(+), 82 deletions(-)
+
+diff --git a/ldap/servers/plugins/memberof/memberof.c b/ldap/servers/plugins/memberof/memberof.c
+index b54eb3977..541b27250 100644
+--- a/ldap/servers/plugins/memberof/memberof.c
++++ b/ldap/servers/plugins/memberof/memberof.c
+@@ -704,8 +704,6 @@ memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn
+     char *filter_str = NULL;
+     char *cookie = NULL;
+     int all_backends = config->allBackends;
+-    int types_name_len = 0;
+-    int num_types = 0;
+     int dn_len = slapi_sdn_get_ndn_len(sdn);
+     int free_it = 0;
+     int rc = 0;
+@@ -744,107 +742,100 @@ memberof_call_foreach_dn(Slapi_PBlock *pb __attribute__((unused)), Slapi_DN *sdn
+ #if MEMBEROF_CACHE_DEBUG
+     slapi_log_err(SLAPI_LOG_PLUGIN, MEMBEROF_PLUGIN_SUBSYSTEM, "memberof_call_foreach_dn: Ancestors of %s not cached\n", ndn);
+ #endif
+-    /* Count the number of types. */
+-    for (num_types = 0; types && types[num_types]; num_types++) {
+-        /* Add up the total length of all attribute names.
+-         * We need to know this for building the filter. */
+-        types_name_len += strlen(types[num_types]);
+-    }
+ 
+     /* Escape the dn, and build the search filter. */
+     escaped_filter_val = slapi_escape_filter_value((char *)slapi_sdn_get_dn(sdn), dn_len);
+     if (escaped_filter_val) {
+-        dn_len = strlen(escaped_filter_val);
+         free_it = 1;
+     } else {
+         escaped_filter_val = (char *)slapi_sdn_get_dn(sdn);
+     }
+ 
+-    if (num_types > 1) {
+-        int bytes_out = 0;
+-        int filter_str_len = types_name_len + (num_types * (3 + dn_len)) + 4;
+-
+-        /* Allocate enough space for the filter */
+-        filter_str = slapi_ch_malloc(filter_str_len);
+-
+-        /* Add beginning of filter. */
+-        bytes_out = snprintf(filter_str, filter_str_len - bytes_out, "(|");
+-
+-        /* Add filter section for each type. */
+-        for (i = 0; types[i]; i++) {
+-            bytes_out += snprintf(filter_str + bytes_out, filter_str_len - bytes_out,
+-                                  "(%s=%s)", types[i], escaped_filter_val);
+-        }
+-
+-        /* Add end of filter. */
+-        snprintf(filter_str + bytes_out, filter_str_len - bytes_out, ")");
+-    } else if (num_types == 1) {
+-        filter_str = slapi_ch_smprintf("(%s=%s)", types[0], escaped_filter_val);
+-    }
+-
+-    if (free_it)
+-        slapi_ch_free_string(&escaped_filter_val);
++    for (i = 0; types[i]; i++) {
++        /* Triggers one internal search per membership attribute.
++         * Assuming the attribute is indexed (eq), the search will
++         * bypass the evaluation of the filter (nsslapd-search-bypass-filter-test)
++         * against the candidates. This is important to bypass the filter
++         * because on large valueset (static group) it is very expensive
++         */
++        filter_str = slapi_ch_smprintf("(%s=%s)", types[i], escaped_filter_val);
+ 
+-    if (filter_str == NULL) {
+-        return rc;
+-    }
++        be = slapi_get_first_backend(&cookie);
++        while (be) {
++            PRBool do_suffix_search = PR_TRUE;
+ 
+-    search_pb = slapi_pblock_new();
+-    be = slapi_get_first_backend(&cookie);
+-    while (be) {
+-        Slapi_DN *scope_sdn = NULL;
++            if (!all_backends) {
++                be = slapi_be_select(sdn);
++                if (be == NULL) {
++                    break;
++                }
++            }
++            if ((base_sdn = (Slapi_DN *) slapi_be_getsuffix(be, 0)) == NULL) {
++                if (!all_backends) {
++                    break;
++                } else {
++                    /* its ok, goto the next backend */
++                    be = slapi_get_next_backend(cookie);
++                    continue;
++                }
++            }
+ 
+-        if (!all_backends) {
+-            be = slapi_be_select(sdn);
+-            if (be == NULL) {
+-                break;
++            search_pb = slapi_pblock_new();
++            if (config->entryScopes || config->entryScopeExcludeSubtrees) {
++                if (memberof_entry_in_scope(config, base_sdn)) {
++                    /* do nothing, entry scope is spanning
++                     * multiple suffixes, start at suffix */
++                } else if (config->entryScopes) {
++                    for (size_t i = 0; config->entryScopes[i]; i++) {
++                        if (slapi_sdn_issuffix(config->entryScopes[i], base_sdn)) {
++                            /* Search each include scope */
++                            slapi_search_internal_set_pb(search_pb, slapi_sdn_get_dn(config->entryScopes[i]),
++                                                         LDAP_SCOPE_SUBTREE, filter_str, 0, 0, 0, 0,
++                                                         memberof_get_plugin_id(), 0);
++                            slapi_search_internal_callback_pb(search_pb, callback_data, 0, callback, 0);
++                            /* We already did the search for this backend, don't
++                             * do it again when we fall through */
++                            do_suffix_search = PR_FALSE;
++                        }
++                    }
++                } else if (!all_backends) {
++                    slapi_pblock_destroy(search_pb);
++                    break;
++                } else {
++                    /* its ok, goto the next backend */
++                    be = slapi_get_next_backend(cookie);
++                    slapi_pblock_destroy(search_pb);
++                    continue;
++                }
+             }
+-        }
+-        if ((base_sdn = (Slapi_DN *)slapi_be_getsuffix(be, 0)) == NULL) {
+-            if (!all_backends) {
+-                break;
+-            } else {
+-                /* its ok, goto the next backend */
+-                be = slapi_get_next_backend(cookie);
+-                continue;
++
++            if (do_suffix_search) {
++                slapi_search_internal_set_pb(search_pb, slapi_sdn_get_dn(base_sdn),
++                                             LDAP_SCOPE_SUBTREE, filter_str, 0, 0, 0, 0,
++                                             memberof_get_plugin_id(), 0);
++                slapi_search_internal_callback_pb(search_pb, callback_data, 0, callback, 0);
++                slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
++                if (rc != LDAP_SUCCESS) {
++                    slapi_pblock_destroy(search_pb);
++                    break;
++                }
+             }
+-        }
+ 
+-        if (config->entryScopes || config->entryScopeExcludeSubtrees) {
+-            if (memberof_entry_in_scope(config, base_sdn)) {
+-                /* do nothing, entry scope is spanning
+-                 * multiple suffixes, start at suffix */
+-            } else if ((scope_sdn = memberof_scope_is_child_of_dn(config, base_sdn))) {
+-                /* scope is below suffix, set search base */
+-                base_sdn = scope_sdn;
+-            } else if (!all_backends) {
++            if (!all_backends) {
++                slapi_pblock_destroy(search_pb);
+                 break;
+-            } else {
+-                /* its ok, goto the next backend */
+-                be = slapi_get_next_backend(cookie);
+-                continue;
+             }
+-        }
+-
+-        slapi_search_internal_set_pb(search_pb, slapi_sdn_get_dn(base_sdn),
+-                                     LDAP_SCOPE_SUBTREE, filter_str, 0, 0, 0, 0, memberof_get_plugin_id(), 0);
+-        slapi_search_internal_callback_pb(search_pb, callback_data, 0, callback, 0);
+-        slapi_pblock_get(search_pb, SLAPI_PLUGIN_INTOP_RESULT, &rc);
+-        if (rc != LDAP_SUCCESS) {
+-            break;
+-        }
+ 
+-        if (!all_backends) {
+-            break;
++            be = slapi_get_next_backend(cookie);
++            slapi_pblock_destroy(search_pb);
+         }
+-        slapi_pblock_init(search_pb);
+-        be = slapi_get_next_backend(cookie);
++        slapi_ch_free((void **)&cookie);
++        slapi_ch_free_string(&filter_str);
+     }
+ 
+-    slapi_pblock_destroy(search_pb);
+-    slapi_ch_free((void **)&cookie);
+-    slapi_ch_free_string(&filter_str);
+-
++    if (free_it) {
++        slapi_ch_free_string(&escaped_filter_val);
++    }
+     return rc;
+ }
+ 
+-- 
+2.39.1
+
diff --git a/SOURCES/0043-Issue-5497-boolean-attributes-should-be-case-insensi.patch b/SOURCES/0043-Issue-5497-boolean-attributes-should-be-case-insensi.patch
new file mode 100644
index 0000000..2c66f8d
--- /dev/null
+++ b/SOURCES/0043-Issue-5497-boolean-attributes-should-be-case-insensi.patch
@@ -0,0 +1,294 @@
+From 36660f00bf11f89c632f581d6f82b7383b1aa190 Mon Sep 17 00:00:00 2001
+From: Mark Reynolds <mreynolds@redhat.com>
+Date: Thu, 26 Jan 2023 08:16:49 -0500
+Subject: [PATCH 4/5] Issue 5497 - boolean attributes should be case
+ insensitive
+
+Description:  Boolean values are supposed to be case insensitive, but in our
+              code it is senstiive even though the code is in the "cis" file.
+
+relates: https://github.com/389ds/389-ds-base/issues/5497
+
+Reviewed by: spichugi(Thanks!)
+---
+ .../tests/suites/syntax/acceptance_test.py    | 248 ++++++++++++++++++
+ ldap/servers/plugins/syntaxes/cis.c           |   4 +-
+ 2 files changed, 250 insertions(+), 2 deletions(-)
+ create mode 100644 dirsrvtests/tests/suites/syntax/acceptance_test.py
+
+diff --git a/dirsrvtests/tests/suites/syntax/acceptance_test.py b/dirsrvtests/tests/suites/syntax/acceptance_test.py
+new file mode 100644
+index 000000000..807936892
+--- /dev/null
++++ b/dirsrvtests/tests/suites/syntax/acceptance_test.py
+@@ -0,0 +1,248 @@
++# --- BEGIN COPYRIGHT BLOCK ---
++# Copyright (C) 2023 Red Hat, Inc.
++# All rights reserved.
++#
++# License: GPL (version 3 or any later version).
++# See LICENSE for details.
++# --- END COPYRIGHT BLOCK ---
++
++import ldap
++import pytest
++import os
++from lib389.schema import Schema
++from lib389.config import Config
++from lib389.idm.user import UserAccounts
++from lib389.idm.group import Group, Groups
++from lib389._constants import DEFAULT_SUFFIX
++from lib389.topologies import log, topology_st as topo
++
++pytestmark = pytest.mark.tier0
++
++log = log.getChild(__name__)
++
++
++@pytest.fixture(scope="function")
++def validate_syntax_off(topo, request):
++    config = Config(topo.standalone)
++    config.replace("nsslapd-syntaxcheck", "off")
++
++    def fin():
++        config.replace("nsslapd-syntaxcheck", "on")
++    request.addfinalizer(fin)
++
++
++def test_valid(topo, validate_syntax_off):
++    """Test syntax-validate task with valid entries
++
++    :id: ec402a5b-bfb1-494d-b751-71b0d31a4d83
++    :setup: Standalone instance
++    :steps:
++        1. Set nsslapd-syntaxcheck to off
++        2. Clean error log
++        3. Run syntax validate task
++        4. Assert that there are no errors in the error log
++        5. Set nsslapd-syntaxcheck to on
++    :expectedresults:
++        1. It should succeed
++        2. It should succeed
++        3. It should succeed
++        4. It should succeed
++        5. It should succeed
++    """
++
++    inst = topo.standalone
++
++    log.info('Clean the error log')
++    inst.deleteErrorLogs()
++
++    schema = Schema(inst)
++    log.info('Attempting to add task entry...')
++    validate_task = schema.validate_syntax(DEFAULT_SUFFIX)
++    validate_task.wait()
++    exitcode = validate_task.get_exit_code()
++    assert exitcode == 0
++    error_lines = inst.ds_error_log.match('.*Found 0 invalid entries.*')
++    assert (len(error_lines) == 1)
++    log.info('Found 0 invalid entries - Success')
++
++
++def test_invalid_uidnumber(topo, validate_syntax_off):
++    """Test syntax-validate task with invalid uidNumber attribute value
++
++    :id: 30fdcae6-ffa6-4ec4-8da9-6fb138fc1828
++    :setup: Standalone instance
++    :steps:
++        1. Set nsslapd-syntaxcheck to off
++        2. Clean error log
++        3. Add a user with uidNumber attribute set to an invalid value (string)
++        4. Run syntax validate task
++        5. Assert that there is corresponding error in the error log
++        6. Set nsslapd-syntaxcheck to on
++    :expectedresults:
++        1. It should succeed
++        2. It should succeed
++        3. It should succeed
++        4. It should succeed
++        5. It should succeed
++        6. It should succeed
++    """
++
++    inst = topo.standalone
++
++    log.info('Clean the error log')
++    inst.deleteErrorLogs()
++
++    users = UserAccounts(inst, DEFAULT_SUFFIX)
++    users.create_test_user(uid="invalid_value")
++
++    schema = Schema(inst)
++    log.info('Attempting to add task entry...')
++    validate_task = schema.validate_syntax(DEFAULT_SUFFIX)
++    validate_task.wait()
++    exitcode = validate_task.get_exit_code()
++    assert exitcode == 0
++    error_lines = inst.ds_error_log.match('.*uidNumber: value #0 invalid per syntax.*')
++    assert (len(error_lines) == 1)
++    log.info('Found an invalid entry with wrong uidNumber - Success')
++
++
++def test_invalid_dn_syntax_crash(topo):
++    """Add an entry with an escaped space, restart the server, and try to delete
++    it.  In this case the DN is not correctly parsed and causes cache revert to
++    to dereference a NULL pointer.  So the delete can fail as long as the server
++    does not crash.
++
++    :id: 62d87272-dfb8-4627-9ca1-dbe33082caf8
++    :setup: Standalone Instance
++    :steps:
++        1. Add entry with leading escaped space in the RDN
++        2. Restart the server so the entry is rebuilt from the database
++        3. Delete the entry
++        4. The server should still be running
++    :expectedresults:
++        1. Success
++        2. Success
++        3. Success
++        4. Success
++    """
++
++        # Create group
++    groups = Groups(topo.standalone, DEFAULT_SUFFIX)
++    group = groups.create(properties={'cn': ' test'})
++
++    # Restart the server
++    topo.standalone.restart()
++
++    # Delete group
++    try:
++        group.delete()
++    except ldap.NO_SUCH_OBJECT:
++        # This is okay in this case as we are only concerned about a crash
++        pass
++
++    # Make sure server is still running
++    groups.list()
++
++
++@pytest.mark.parametrize("props, rawdn", [
++                         ({'cn': ' leadingSpace'}, "cn=\\20leadingSpace,ou=Groups,dc=example,dc=com"),
++                         ({'cn': 'trailingSpace '}, "cn=trailingSpace\\20,ou=Groups,dc=example,dc=com")])
++def test_dn_syntax_spaces_delete(topo,  props,  rawdn):
++    """Test that an entry with a space as the first character in the DN can be
++    deleted without error.  We also want to make sure the indexes are properly
++    updated by repeatedly adding and deleting the entry, and that the entry cache
++    is properly maintained.
++
++    :id: b993f37c-c2b0-4312-992c-a9048ff98965
++    :customerscenario: True
++    :parametrized: yes
++    :setup: Standalone Instance
++    :steps:
++        1. Create a group with a DN that has a space as the first/last
++           character.
++        2. Delete group
++        3. Add group
++        4. Modify group
++        5. Restart server and modify entry
++        6. Delete group
++        7. Add group back
++        8. Delete group using specific DN
++    :expectedresults:
++        1. Success
++        2. Success
++        3. Success
++        4. Success
++        5. Success
++        6. Success
++        7. Success
++        8. Success
++    """
++
++    # Create group
++    groups = Groups(topo.standalone, DEFAULT_SUFFIX)
++    group = groups.create(properties=props.copy())
++
++    # Delete group (verifies DN/RDN parsing works and cache is correct)
++    group.delete()
++
++    # Add group again (verifies entryrdn index was properly updated)
++    groups = Groups(topo.standalone, DEFAULT_SUFFIX)
++    group = groups.create(properties=props.copy())
++
++    # Modify the group (verifies dn/rdn parsing is correct)
++    group.replace('description', 'escaped space group')
++
++    # Restart the server.  This will pull the entry from the database and
++    # convert it into a cache entry, which is different than how a client
++    # first adds an entry and is put into the cache before being written to
++    # disk.
++    topo.standalone.restart()
++
++    # Make sure we can modify the entry (verifies cache entry was created
++    # correctly)
++    group.replace('description', 'escaped space group after restart')
++
++    # Make sure it can still be deleted (verifies cache again).
++    group.delete()
++
++    # Add it back so we can delete it using a specific DN (sanity test to verify
++    # another DN/RDN parsing variation).
++    groups = Groups(topo.standalone, DEFAULT_SUFFIX)
++    group = groups.create(properties=props.copy())
++    group = Group(topo.standalone, dn=rawdn)
++    group.delete()
++
++
++def test_boolean_case(topo):
++    """Test that we can a boolean value in any case
++
++       :id: 56777c1d-b058-41e1-abd5-87a6f1512db2
++       :customerscenario: True
++       :setup: Standalone Instance
++       :steps:
++           1. Create test user
++           2. Add boolean attribute value that is lowercase "false"
++       :expectedresults:
++           1. Success
++           2. Success
++    """
++    inst = topo.standalone
++    users  = UserAccounts(inst, DEFAULT_SUFFIX)
++    user = users.create_test_user(uid=1011)
++
++    user.add('objectclass', 'extensibleObject')
++    user.add('pamsecure', 'false')
++    user.replace('pamsecure', 'FALSE')
++    user.replace('pamsecure', 'true')
++    user.replace('pamsecure', 'TRUE')
++
++    # Test some invalid syntax
++    with pytest.raises(ldap.INVALID_SYNTAX):
++        user.replace('pamsecure', 'blah')
++
++
++if __name__ == '__main__':
++    # Run isolated
++    # -s for DEBUG mode
++    CURRENT_FILE = os.path.realpath(__file__)
++    pytest.main("-s %s" % CURRENT_FILE)
+diff --git a/ldap/servers/plugins/syntaxes/cis.c b/ldap/servers/plugins/syntaxes/cis.c
+index e1242e3f4..c9274f37f 100644
+--- a/ldap/servers/plugins/syntaxes/cis.c
++++ b/ldap/servers/plugins/syntaxes/cis.c
+@@ -853,12 +853,12 @@ boolean_validate(
+      */
+     if (val != NULL) {
+         if (val->bv_len == 4) {
+-            if (strncmp(val->bv_val, "TRUE", 4) != 0) {
++            if (strncasecmp(val->bv_val, "TRUE", 4) != 0) {
+                 rc = 1;
+                 goto exit;
+             }
+         } else if (val->bv_len == 5) {
+-            if (strncmp(val->bv_val, "FALSE", 5) != 0) {
++            if (strncasecmp(val->bv_val, "FALSE", 5) != 0) {
+                 rc = 1;
+                 goto exit;
+             }
+-- 
+2.39.1
+
diff --git a/SOURCES/Cargo.lock b/SOURCES/Cargo.lock
new file mode 100644
index 0000000..50140e9
--- /dev/null
+++ b/SOURCES/Cargo.lock
@@ -0,0 +1,833 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
+
+[[package]]
+name = "base64"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+
+[[package]]
+name = "bitflags"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "cbindgen"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9daec6140ab4dcd38c3dd57e580b59a621172a526ac79f1527af760a55afeafd"
+dependencies = [
+ "clap",
+ "log",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "serde_json",
+ "syn",
+ "tempfile",
+ "toml",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.79"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "2.34.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "concread"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcc9816f5ac93ebd51c37f7f9a6bf2b40dfcd42978ad2aea5d542016e9244cf6"
+dependencies = [
+ "ahash",
+ "crossbeam",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+ "lru",
+ "parking_lot",
+ "rand",
+ "smallvec",
+ "tokio",
+]
+
+[[package]]
+name = "crossbeam"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c"
+dependencies = [
+ "cfg-if",
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-epoch",
+ "crossbeam-queue",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
+dependencies = [
+ "autocfg",
+ "cfg-if",
+ "crossbeam-utils",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-queue"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "fastrand"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
+dependencies = [
+ "instant",
+]
+
+[[package]]
+name = "fernet"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93804560e638370a8be6d59ce71ed803e55e230abdbf42598e666b41adda9b1f"
+dependencies = [
+ "base64",
+ "byteorder",
+ "getrandom",
+ "openssl",
+ "zeroize",
+]
+
+[[package]]
+name = "foreign-types"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
+dependencies = [
+ "foreign-types-shared",
+]
+
+[[package]]
+name = "foreign-types-shared"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
+
+[[package]]
+name = "getrandom"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440"
+
+[[package]]
+name = "jobserver"
+version = "0.1.25"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.139"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
+
+[[package]]
+name = "librnsslapd"
+version = "0.1.0"
+dependencies = [
+ "cbindgen",
+ "libc",
+ "slapd",
+]
+
+[[package]]
+name = "librslapd"
+version = "0.1.0"
+dependencies = [
+ "cbindgen",
+ "concread",
+ "libc",
+ "slapd",
+]
+
+[[package]]
+name = "lock_api"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
+dependencies = [
+ "autocfg",
+ "scopeguard",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "lru"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999beba7b6e8345721bd280141ed958096a2e4abdf74f67ff4ce49b4b54e47a"
+dependencies = [
+ "hashbrown",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.17.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
+
+[[package]]
+name = "openssl"
+version = "0.10.45"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1"
+dependencies = [
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "libc",
+ "once_cell",
+ "openssl-macros",
+ "openssl-sys",
+]
+
+[[package]]
+name = "openssl-macros"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.80"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7"
+dependencies = [
+ "autocfg",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
+]
+
+[[package]]
+name = "parking_lot"
+version = "0.11.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
+dependencies = [
+ "instant",
+ "lock_api",
+ "parking_lot_core",
+]
+
+[[package]]
+name = "parking_lot_core"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
+dependencies = [
+ "cfg-if",
+ "instant",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi",
+]
+
+[[package]]
+name = "paste"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45ca20c77d80be666aef2b45486da86238fabe33e38306bd3118fe4af33fa880"
+dependencies = [
+ "paste-impl",
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "paste-impl"
+version = "0.1.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d95a7db200b97ef370c8e6de0088252f7e0dfff7d047a28528e47456c0fc98b6"
+dependencies = [
+ "proc-macro-hack",
+]
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.20+deprecated"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.51"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "pwdchan"
+version = "0.1.0"
+dependencies = [
+ "base64",
+ "cc",
+ "libc",
+ "openssl",
+ "paste",
+ "slapi_r_plugin",
+ "uuid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.23"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "ryu"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde"
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "serde"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.152"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76"
+dependencies = [
+ "itoa",
+ "ryu",
+ "serde",
+]
+
+[[package]]
+name = "slapd"
+version = "0.1.0"
+dependencies = [
+ "fernet",
+]
+
+[[package]]
+name = "slapi_r_plugin"
+version = "0.1.0"
+dependencies = [
+ "libc",
+ "paste",
+ "uuid",
+]
+
+[[package]]
+name = "smallvec"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "syn"
+version = "1.0.107"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+dependencies = [
+ "cfg-if",
+ "fastrand",
+ "libc",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "tokio"
+version = "1.25.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af"
+dependencies = [
+ "autocfg",
+ "pin-project-lite",
+ "tokio-macros",
+ "windows-sys",
+]
+
+[[package]]
+name = "tokio-macros"
+version = "1.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "toml"
+version = "0.5.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
+dependencies = [
+ "serde",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
+
+[[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.42.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
+
+[[package]]
+name = "zeroize"
+version = "1.5.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f"
+dependencies = [
+ "zeroize_derive",
+]
+
+[[package]]
+name = "zeroize_derive"
+version = "1.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
+]
diff --git a/SPECS/389-ds-base.spec b/SPECS/389-ds-base.spec
index 153dc93..e3b2867 100644
--- a/SPECS/389-ds-base.spec
+++ b/SPECS/389-ds-base.spec
@@ -14,6 +14,7 @@
 # This is used in certain builds to help us know if it has extra features.
 %global variant base
 %global use_asan 0
+%global use_rust 1
 
 %if %{use_asan}
 %global use_tcmalloc 0
@@ -25,6 +26,7 @@
 %global use_tcmalloc 0
 %endif
 %endif
+%global rust_version      1.62
 
 # fedora 15 and later uses tmpfiles.d
 # otherwise, comment this out
@@ -38,8 +40,8 @@
 
 Summary:          389 Directory Server (%{variant})
 Name:             389-ds-base
-Version:          1.3.10.2
-Release:          %{?relprefix}17%{?prerel}%{?dist}
+Version:          1.3.11.1
+Release:          %{?relprefix}1%{?prerel}%{?dist}
 License:          GPLv3+
 URL:              https://www.port389.org/
 Group:            System Environment/Daemons
@@ -75,6 +77,13 @@ BuildRequires:    systemd-devel
 %if %{use_asan}
 BuildRequires:    libasan
 %endif
+# If rust is enabled
+%if %{use_rust}
+BuildRequires:    scl-utils
+BuildRequires:    rust-toolset-%{rust_version}
+BuildRequires:    rust-toolset-%{rust_version}-cargo
+BuildRequires:    rust-toolset-%{rust_version}-rust
+%endif
 # Needed to support regeneration of the autotool artifacts.
 BuildRequires:    autoconf
 BuildRequires:    automake
@@ -145,6 +154,10 @@ Requires:         gperftools-libs
 Source0:          https://releases.pagure.org/389-ds-base/%{name}-%{version}%{?prerel}.tar.bz2
 Source1:          %{name}-git.sh
 Source2:          %{name}-devel.README
+%if %{use_rust}
+Source3:          vendor-%{version}-1.tar.gz
+Source4:          Cargo.lock
+%endif
 Patch00:          0000-Issue-50800-wildcards-in-rootdn-allow-ip-attribute-a.patch
 Patch01:          0001-Issue-49437-Fix-memory-leak-with-indirect-COS.patch
 Patch02:          0002-Ticket-50905-intermittent-SSL-hang-with-rhds.patch
@@ -152,7 +165,7 @@ Patch03:          0003-Issue-51029-Add-db_home_dir-defaults.inf.patch
 Patch04:          0004-Ticket-51068-deadlock-when-updating-the-schema.patch
 Patch05:          0005-Issue-50745-ns-slapd-hangs-during-CleanAllRUV-tests.patch
 Patch06:          0006-Issue-51095-abort-operation-if-CSN-can-not-be-genera.patch
-Patch07:          0007-Issue-51132-Winsync-setting-winSyncWindowsFilter-not.patch 
+Patch07:          0007-Issue-51132-Winsync-setting-winSyncWindowsFilter-not.patch
 Patch08:          0008-Issue-4389-errors-log-with-incorrectly-formatted-mes.patch
 Patch09:          0009-Issue-4297-On-ADD-replication-URP-issue-internal-sea.patch
 Patch10:          0010-Issue-4379-allow-more-than-1-empty-AttributeDescript.patch
@@ -186,6 +199,9 @@ Patch37:          0037-Issue-5155-RFE-Provide-an-option-to-abort-an-Auto-Me.patc
 Patch38:          0038-Issue-5221-User-with-expired-password-can-still-logi.patch
 Patch39:          0039-Issue-5098-Multiple-issues-around-replication-and-CI.patch
 Patch40:          0040-Issue-5418-Sync_repl-may-crash-while-managing-invali.patch
+Patch41:          0041-Issue-5565-Change-default-password-storage-scheme-to.patch
+Patch42:          0042-Issue-5440-memberof-is-slow-on-update-fixup-if-there.patch
+Patch43:          0043-Issue-5497-boolean-attributes-should-be-case-insensi.patch
 
 
 %description
@@ -255,6 +271,10 @@ The lib389 CI tests that can be run against the Directory Server.
 
 %prep
 %autosetup -p1 -n %{name}-%{version}%{?prerel}
+%if %{use_rust}
+tar xvzf %{SOURCE3}
+cp %{SOURCE4} src/
+%endif
 cp %{SOURCE2} README.devel
 
 %build
@@ -271,6 +291,14 @@ TCMALLOC_FLAGS="--enable-tcmalloc"
 ASAN_FLAGS="--enable-asan --enable-debug"
 %endif
 
+%if %{use_rust}
+RUST_FLAGS="--enable-rust --enable-rust-offline"
+
+set +e
+source scl_source enable rust-toolset-%{rust_version}
+set -e
+%endif
+
 # Rebuild the autotool artifacts now.
 autoreconf -fiv
 
@@ -279,7 +307,7 @@ autoreconf -fiv
            --with-systemdsystemconfdir=%{_sysconfdir}/systemd/system \
            --with-perldir=/usr/bin \
            --with-systemdgroupname=%{groupname} $NSSARGS \
-           --with-systemd --enable-cmocka $TCMALLOC_FLAGS $ASAN_FLAGS
+           --with-systemd --enable-cmocka $RUST_FLAGS $TCMALLOC_FLAGS $ASAN_FLAGS
 
 # Generate symbolic info for debuggers
 export XCFLAGS=$RPM_OPT_FLAGS
@@ -540,6 +568,12 @@ fi
 %{_sysconfdir}/%{pkgname}/dirsrvtests
 
 %changelog
+* Tue Feb 21 2023 Simon Pichugin <spichugi@redhat.com> - 1.3.11.1-1
+- Bump version to 1.3.11.1-1
+- Resolves: Bug 2170224 - Backport Rust password storage PBKDF2 schemes
+- Resolves: Bug 2170221 - Boolean attributes should be case insensitive
+- Resolves: Bug 2170218 - Slow memberof fixup task for large static groups, high CPU use
+
 * Fri Sep 30 2022 Mark Reynolds <mreynolds@redhat.com> - 1.3.10-2-17
 - Bump version to 1.3.10.2-17
 - Resolves: Bug 2113056 - Import may break replication because changelog starting csn may not be created