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 +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 + ++#ifdef RUST_ENABLE ++#include ++#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 "] ++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 "] ++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, ++} ++ ++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 = 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 "] ++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, Vec), 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 { ++ 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 = (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 { ++ 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 = (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 = (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 { ++ Self::pbkdf2_compare(cleartext, encrypted, MessageDigest::sha1()) ++ } ++ ++ #[inline(always)] ++ fn pbkdf2_sha256_compare(cleartext: &str, encrypted: &str) -> Result { ++ Self::pbkdf2_compare(cleartext, encrypted, MessageDigest::sha256()) ++ } ++ ++ #[inline(always)] ++ fn pbkdf2_sha512_compare(cleartext: &str, encrypted: &str) -> Result { ++ Self::pbkdf2_compare(cleartext, encrypted, MessageDigest::sha512()) ++ } ++ ++ #[inline(always)] ++ fn pbkdf2_sha1_encrypt(cleartext: &str) -> Result { ++ Self::pbkdf2_encrypt(cleartext, MessageDigest::sha1()) ++ } ++ ++ #[inline(always)] ++ fn pbkdf2_sha256_encrypt(cleartext: &str) -> Result { ++ Self::pbkdf2_encrypt(cleartext, MessageDigest::sha256()) ++ } ++ ++ #[inline(always)] ++ fn pbkdf2_sha512_encrypt(cleartext: &str) -> Result { ++ 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 { ++ PwdChanCrypto::pbkdf2_sha1_encrypt(cleartext) ++ } ++ ++ fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result { ++ 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 { ++ PwdChanCrypto::pbkdf2_sha1_encrypt(cleartext) ++ } ++ ++ fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result { ++ 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 { ++ PwdChanCrypto::pbkdf2_sha256_encrypt(cleartext) ++ } ++ ++ fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result { ++ 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 { ++ PwdChanCrypto::pbkdf2_sha512_encrypt(cleartext) ++ } ++ ++ fn pwd_storage_compare(cleartext: &str, encrypted: &str) -> Result { ++ 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 "] ++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 { ++ let k = Fernet::generate_key(); ++ CString::new(k).map_err(|_| SlapdError::CStringInvalidError) ++} ++ ++pub fn new(c_str_key: &CStr) -> Result { ++ 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 { ++ let tok = fernet.encrypt(dn.to_bytes()); ++ CString::new(tok).map_err(|_| SlapdError::CStringInvalidError) ++} ++ ++pub fn decrypt(fernet: &Fernet, tok: &CStr, ttl: u64) -> Result { ++ let s = tok.to_str().map_err(|_| SlapdError::CStringInvalidError)?; ++ let r: Vec = 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 "] ++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_`, 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 { ++ 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 { ++ 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 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 { ++ // 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 { ++ 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, ++ charray: Vec<*const c_char>, ++} ++ ++impl Charray { ++ pub fn new(input: &[&str]) -> Result { ++ let pin: Result, ()> = 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 for FilterType { ++ type Error = RPluginError; ++ ++ fn try_from(value: i32) -> Result { ++ 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 { ++ 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 { ++ 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 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 ++ ++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 = 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 = 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 = 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, ++} ++ ++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 { ++ 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 { ++ 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::() 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 { ++ 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 { ++ 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 { ++ Err(LDAPError::Other) ++ } ++ ++ fn task_be_dn_hint(_data: &Self::TaskData) -> Option { ++ None ++ } ++ ++ fn task_handler(_task: &Task, _data: Self::TaskData) -> Result { ++ 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 { ++ Err(PluginError::Unimplemented) ++ } ++ ++ fn pwd_storage_compare(_cleartext: &str, _encrypted: &str) -> Result { ++ 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, ++ stype: SearchType, ++} ++ ++pub struct SearchResult { ++ _pb: Pblock, ++} ++ ++impl Search { ++ pub fn new_map_entry( ++ 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 ++ 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 { ++ // 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 { ++ Ok(false) ++ } ++ ++ fn eq_mr_filter_values2keys( ++ _pb: &mut PblockRef, ++ _vals: &ValueArrayRef, ++ ) -> Result; ++} ++ ++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, 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 { ++ // 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 { ++ 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 for ValueArray { ++ fn from_iter>(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 { ++ value.bvr.into_string().ok_or(()) ++ } ++} ++ ++impl TryFrom<&ValueRef> for Uuid { ++ type Error = (); ++ ++ fn try_from(value: &ValueRef) -> Result { ++ (&value.bvr).try_into().map_err(|_| ()) ++ } ++} ++ ++impl TryFrom<&ValueRef> for Sdn { ++ type Error = (); ++ ++ fn try_from(value: &ValueRef) -> Result { ++ // 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 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 +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 +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 - 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 - 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