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